Some confusion about VPN global routing

I am currently developing a custom-protocol VPN application for iOS using PacketTunnelProvider. I have also integrated an HTTP proxy service, which is launched via a dylib.

The overall flow is as follows: App -> VPN TUN -> Local HTTP Proxy -> External Network

I have a question: I am capturing all traffic, and normally, requests sent out by the HTTP proxy are also captured again by the VPN. However, when I send requests using createUdpSession in my code, they are not being captured by the virtual interface (TUN).

What could be the reason for this?


override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {

        let tunnelNetworkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "192.168.18.0")
        tunnelNetworkSettings.mtu=1400
        
        let ipv4Settings = NEIPv4Settings(addresses: ["192.169.10.10"], subnetMasks: ["255.255.255.0"])
        
        ipv4Settings.includedRoutes=[NEIPv4Route.default()]

        ipv4Settings.excludedRoutes = [NEIPv4Route(destinationAddress: "10.0.0.0", subnetMask: "255.0.0.0"),
                                       NEIPv4Route(destinationAddress: "172.16.0.0", subnetMask: "255.240.0.0"),
                                       NEIPv4Route(destinationAddress: "192.168.0.0", subnetMask: "255.255.0.0"),
                                       NEIPv4Route(destinationAddress:"127.0.0.0", subnetMask: "255.0.0.0"),
                                       ]
 
        tunnelNetworkSettings.ipv4Settings = ipv4Settings
        
        // Configure proxy settings
        let proxySettings = NEProxySettings()
        proxySettings.httpEnabled = true
        proxySettings.httpServer = NEProxyServer(address: "127.0.0.1", port: 7890)
        proxySettings.httpsEnabled = true
        proxySettings.httpsServer = NEProxyServer(address: "127.0.0.1", port: 7890)
        proxySettings.excludeSimpleHostnames = true
        proxySettings.exceptionList=["localhost","127.0.0.1"]
        
        
        tunnelNetworkSettings.proxySettings = proxySettings
       
        setTunnelNetworkSettings(tunnelNetworkSettings) { [weak self] error in
            if  error != nil {
                completionHandler(error)
                return
            }
            completionHandler(nil)
            let stack = TUNInterface(packetFlow: self!.packetFlow)
            RawScoketFactory.TunnelProvider=self
            stack.register(stack: UDPDirectStack())
            stack.register(stack: TCPDirectStack())
            stack.start()
           
            
        }
    }

NWUdpSession.swift

//
//  NWUDPSocket.swift
//  supervpn
//
//  Created by TobbyQuinn on 2025/2/3.
//

import Foundation
import NetworkExtension
import CocoaLumberjack

public protocol NWUDPSocketDelegate: AnyObject{
    
    func didReceive(data:Data,from:NWUDPSocket)
    func didCancel(socket:NWUDPSocket)
    
}
public class NWUDPSocket:NSObject{
    private let session:NWUDPSession
    private let timeout:Int
    private var pendingWriteData: [Data] = []
    private var writing = false
    private let queue:DispatchQueue=QueueFactory.getQueue()
    public weak var delegate:NWUDPSocketDelegate?
    
    public init?(host:String,port:UInt16,timeout:Int=Opt.UDPSocketActiveTimeout){
        guard let udpSession =  RawScoketFactory.TunnelProvider?.createUDPSession(to: NWHostEndpoint(hostname: host, port: "\(port)"), from: nil) else{
            return nil
        }
        session = udpSession
        self.timeout=timeout
        super.init()
        session.addObserver(self, forKeyPath: #keyPath(NWUDPSession.state),options: [.new], context: nil)
        session.setReadHandler({ dataArray, error in
            
            self.queueCall{
                
                guard error == nil, let dataArray = dataArray else {
                    print("Error when reading from remote server or connection reset")
                    return
                }
                for data in dataArray{
                    self.delegate?.didReceive(data: data, from: self)
                }
            }
        }, maxDatagrams: 32)
        
    }
    /**
     Send data to remote.
     
     - parameter data: The data to send.
     */
    public func write(data: Data) {
        pendingWriteData.append(data)
        checkWrite()
    }
    
    public func disconnect() {
        session.cancel()
    }
    
    public override  func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        guard keyPath == "state" else {
            return
        }
        
        switch session.state {
        case .cancelled:
            queueCall {
                self.delegate?.didCancel(socket: self)
            }
        case .ready:
            checkWrite()
        default:
            break
        }
    }
    
    private func checkWrite() {
        
        guard session.state == .ready else {
            return
        }
        
        guard !writing else {
            return
        }
        
        guard pendingWriteData.count > 0 else {
            return
        }
        
        writing = true
        session.writeMultipleDatagrams(self.pendingWriteData) {_ in
            self.queueCall {
                self.writing = false
                self.checkWrite()
            }
        }
        self.pendingWriteData.removeAll(keepingCapacity: true)
    }
    
    private func queueCall(block:@escaping ()->Void){
        queue.async {
            block()
        }
    }
    deinit{
        session.removeObserver(self, forKeyPath: #keyPath(NWUDPSession.state))
    }
}

Lemme start by clarifying your architecture.

Written by QIYAO in 773911021
App -> VPN TUN -> Local HTTP Proxy -> External Network

Where is your VPN server is this setup?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Some confusion about VPN global routing
 
 
Q