I'm having a Local VPN app that using "NETunnelProvider / NetworkExtentsion", In my solution, I created a split tunnel on the device itself to track the DNS request, using NEKit I was able to peek inside the packets and filter the ongoing request based on the destination address (let's call ita UDP listener for DNS requests).
This solution was working fine on iOS 13.7 and less, recently apple release iOS 14, and my solution stop working, VPN connection still established but the user can't access any webSite, I debugged the code and found out the networkExtision does not receive any packets from user activity only.
I'm using the CocoaAsyncSocket library.
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
let host = GCDAsyncUdpSocket.host(fromAddress: address)
guard let message = DNSMessage(payload: data) else {
return
}
guard let session = pendingSession.removeValue(forKey: message.transactionID) else {
return
}
session.realResponseMessage = message
session.realIP = message.resolvedIPv4Address
let domain = session.requestMessage.queries[0].name
let udpParser = UDPProtocolParser()
udpParser.sourcePort = Port(port: dnsServerPort)
udpParser.destinationPort = (session.requestIPPacket!.protocolParser as! UDPProtocolParser).sourcePort
udpParser.payload = session.realResponseMessage!.payload
let ipPacket = IPPacket()
ipPacket.sourceAddress = IPAddress(fromString: dnsServerAddress)
ipPacket.destinationAddress = session.requestIPPacket!.sourceAddress
ipPacket.protocolParser = udpParser
ipPacket.transportProtocol = .udp
ipPacket.buildPacket()
packetFlow.writePackets([ipPacket.packetData], withProtocols: [NSNumber(value: AF_INET as Int32)])
}
let dummyTunnelAddress = "127.0.0.1"
let dnsServerAddress = "8.8.4.4"
let dnsServerPort: UInt16 = 53
// Tunnel confg.
let tunnelAddress = "192.168.0.1"
let tunnelSubnetMask = "255.255.255.0"
Regarding triggering"Local Network permissions" which is not the issue here (I don't think my solution need to have this permission), Based on the apple document some apps need to request local network permissions, I added the permission to the info.plist but local network permissions are not triggered.
==========================
Update #1
============================
I found out that I was able to capture the packets and do my own things then write packets back to the packetFlow packetFlow.writePackets, But on iOS 14 browsers not loading the websites and keep loading until show time out.
I have an idea, and maybe a solution for you. Starting in iOS version 14, the C function connect() started failing in network extensions, where VPNs have to run, with the following log message from the kernel:
Sandbox: VPN Extensio(8335) deny(1) network-outbound*:<port #>
However, it does work in the app, which is next to useless if you need it in the extension. GCDAsyncUdpSocket is a thin layer that right under the covers is calling socket() and connect().
NWConnection does work in a network extension, and it should work for you if it is feasible to port your code and you don't need the actual socket descriptor. But you will have to conditionally compile if you have to support devices < ios 12.
Related
I'm using tap device.
The problem is that you just can ->read() or ->write() one packet every system call.
After read the source file tun.c, I found there is struct socket in tun device and function tun_get_socket() can return it.
struct socket *tun_get_socket(struct file *file)
{
struct tun_file *tfile;
if (file->f_op != &tun_fops)
return ERR_PTR(-EINVAL);
tfile = file->private_data;
if (!tfile)
return ERR_PTR(-EBADFD);
return &tfile->socket;
}
EXPORT_SYMBOL_GPL(tun_get_socket);
The socket.ops is set with tun_socket_ops which have ->sendmsg() and ->recvmsg().
static const struct proto_ops tun_socket_ops = {
.sendmsg = tun_sendmsg,
.recvmsg = tun_recvmsg,
.release = tun_release,
};
The questions:
How to create one file descriptor to connect with this socket? That makes me to use ->sendmsg() and ->recvmsg() in userspace. My OS is CentOS7.9 with kernel version 3.10.0-1160.el7.x86_64.
Or is there other ways I can read or write multiple packets at one time from tap device?
Writing new kernel module is acceptable.
I am trying to capture a UDP video stream within a (fresh) vapor application running in Xcode. The data is being streamed by ffmpeg and I can successfully view the stream on the target machine using VLC, which is also the one running the vapor application, using udp://0.0.0.0:5000. I have used various bits of Apple documentation to get to the code below. When I run it, I get these lines of output on the console log, but I wonder if they are not relevant:
2021-07-07 17:59:27.102681+0100 Run[10550:2494617] [si_destination_compare] send failed: Invalid argument
2021-07-07 17:59:27.104056+0100 Run[10550:2494617] [si_destination_compare] send failed: Undefined error: 0
In configure.swift:
try setupClient()
This is the client code:
final class FrameHandler : ChannelInboundHandler {
typealias InboundIn = AddressedEnvelope<ByteBuffer>
typealias OutboundOut = AddressedEnvelope<ByteBuffer>
func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
// minimal for question
}
func errorCaught(ctx: ChannelHandlerContext, error: Error) {
// minimal for question
}
}
func setupClient() throws {
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let bootstrap = DatagramBootstrap(group: group)
.channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.channelInitializer { channel in
channel.pipeline.addHandler(FrameHandler())
}
defer {
try! group.syncShutdownGracefully()
}
let channel = try bootstrap.bind(host: "0.0.0.0", port: 5000).wait()
try channel.closeFuture.wait()
}
The problem is that although channelRegistered and channelActive are called, followed by a never-ending stream of readComplete, the important one channelRead never gets called - neither does errorCaught. If I comment out the call to setupClient then there is no network activity, however, if it runs then Xcode's network monitor shows activity consistent with the levels in ffmpeg. So, I believe the connection is being set up.
I wonder if the problem is in the way I am setting the handler up? All the examples use echo or reflecting chat examples, so the inbound handler is set up in the closure of the data-writing function using the context rather than adding it in the initialiser (although, the outbound handler is set up in this way).
I'm assuming you're using Vapor 4 which is based on SwiftNIO 2. And in NIO 2, the ChannelHandlerContext variable is called context and not ctx. So if you just rename all your ctx to context, I'd assume it'll work.
I am trying to write a macOS application that connects to a radio scanner on a network connection via a UDP socket. The app sends commands to the scanner for remote control. I can get the app to work fine when I hard code the NWConnection initiation but when I put a variable that represents the port or host I get an Xcode error.
I want the user to be able to input their specific Host and Port address therefore I set up textFields for the user to input these values. Then I wanted to retrieve the values and enter them into the Host and Port NWConnection fields when initializing the connection.
Here is a code snippet:
func makeConnection(){
var myHost = "192.168.7.239"
var myPort = 50536 as UInt16
myConnection = NWConnection(host: myHost, port: myPort, using: .udp)
myConnection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("ready")
self.send()
self.receive()
case .setup:
print("setup")
case .cancelled:
print("cancelled")
case .preparing:
print("preparing")
default:
print("waiting or failed")
}
}
In this case, if I replace myHost with "192.168.7.239" and I replace myPort with 50536 then Xcode is happy and everything works. However as soon as I put a variable in the host and port fields I get this complaint from Xcode.
"Type of expression is ambiguous without more context" and the build fails.
Why is this happening and how can I make an NWConnection without hard coding a Host or Port? what good is it is you can't get the port and host from the user and use those values to make the connection.
Any help would be greatly appreciated.
When calling NWConnection.init(host:port:using:), the parameter host needs to be of type NWEndpoint.Host, port needs to be NWEndpoint.Port.
Please try this:
var myHost: NWEndpoint.Host = "192.168.7.239"
var myPort: NWEndpoint.Port = 50536
myConnection = NWConnection(host: myHost, port: myPort, using: .udp)
When you want to use the texts in textFields, you need to covert the texts into NWEndpoint.Host and NWEndpoint.Port.
XCode 14.0.1/Swift 5.7
I think it could be useful to complement OOPer's answer a little bit.
var hostAddress: NWEndpoint.Host = "192.168.7.239"
Here, directly affecting a Stringvalue is OK.
I think it's expected since Apple's documentation specificies
typealias NWEndpoint.Host.StringLiteralType = String
But if you use an intermediate variable to store the host IP address (for example if you get it through a user input in a TextField), be careful :
var hostAddress = "192.168.7.239"
var myHost = NWEndpoint.Host(hostAddress)
is OK too. Expected since initializer expects a String.
But:
var hostAddress = "192.168.7.239"
var myHost: NWEndpoint.Host = hostAddress
returns a "Cannot convert value of type 'String' to specified type 'NWEndpoint.Host'" error.
Seems confusing since it looks to be the same at first example, but with an intermediate variable.
I am creating a macOS app that will update the firmware of a USB mass storage device. There are only two steps to do this, copy the files to the storage device (done with this) and send a reboot command to the device using SCSI command sets.
I've tried the library below to send a command, however, the device didn't reboot. I am not sure if there's something wrong with my request.
https://github.com/Arti3DPlayer/USBDeviceSwift
I reached to the developer of this library, however, he replied to me "Sorry, I didn't work with USB and Swift about a year, and don't remember this things:)"
The reboot command is 0xdf 0x40.
This is the request I sent to the device.
guard let deviceInterface = self.deviceInfo.deviceInterfacePtrPtr?.pointee?.pointee else {
throw STM32DeviceError.DeviceInterfaceNotFound
}
var kr:Int32 = 0
let length:Int = 6
var requestPtr:[UInt8] = [UInt8](repeating: 0, count: length)
// Creating request
var request = IOUSBDevRequest(
bmRequestType: USBmakebmRequestType(direction: kUSBIn, type: kUSBDevice, recipient: kUSBStandard),
bRequest: STM32REQUEST.DETACH.rawValue,
wValue: UInt16(0xdf),
wIndex: 0,
wLength: UInt16(length),
pData: &requestPtr,
wLenDone: 255)
kr = deviceInterface.DeviceRequest(self.deviceInfo.deviceInterfacePtrPtr, &request)
if (kr != kIOReturnSuccess) {
throw STM32DeviceError.RequestError(desc: "Get device status request error: \(kr)")
}
I'm working on a UWP application which should communicate via TCP/UDP to a remote device. My issue is that the UWP app successfully sends UDP messages to the remote device, but does not receive the replies.
Here follows the code extracted from the app (simplified):
async Task TestUdpIP()
{
// Writer to the DatagramSocket
DataWriter writer;
using (var udpClient = new DatagramSocket())
{
try
{
// UDP Socket binding
udpClient.MessageReceived += UdpClient_MessageReceived;
var controllerName = new Windows.Networking.HostName(controllerIpAddress.ToString());
await udpClient.BindEndpointAsync(controllerName, controllerIpPort.ToString());
var remoteHostName = new Windows.Networking.HostName(hostIpAddres.ToString());
await udpClient.ConnectAsync(remoteHostName, remoteHostPort.ToString());
// Create a message to send
string message = "Some message";
// Reset the counter of messages received back from the remote robot
messagesReceived = 0;
// Send the message
writer = new DataWriter(udpClient.OutputStream);
writer.WriteString(message);
await writer.StoreAsync();
// Wait for robot status messages
await Task.Delay(5000);
}
catch
{
}
}
}
void UdpClient_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
// Just increment the number of messages received
messagesReceived++;
}
However the UdpClient_MessageReceived handler does not fire. I'm sure that UDP messages are correctly sent from the UWP app and that the remote device replies back as shown in the following screenshot from Wireshark (the test has been taken on the same PC where the UWP app is running)
.
(IP Address, Port) details are shown in the following list to better explain the picture above
UWP application: (192.168.1.108, 19000) to send and receive.
Remote device: (192.168.1.152, 15999) to receive and (192.168.1.152, 54697) to send
Note: this is a similar question, where the answer says that for whatever reason the DatagramSocket should fire some messages before being able to receive. In my example a message is sent out however the message received handler does not fire anyway.
Note: The UWP app has been granted the internet (client) and the internet (client and server) capabilities
Note: I've tried also this (more readable) approach to bind the inbound/outbound datagram socket on the UWP app obtaining the same results:
// UDP Socket binding
var controllerName = new HostName(controllerIpAddress.ToString());
var remoteHostName = new HostName(hostIpAddres.ToString());
EndpointPair endpointpar = new EndpointPair(controllerName,
controllerIpPort.ToString(),
remoteHostName,
remoteHostPort.ToString());
udpClient.MessageReceived += UdpClient_MessageReceived;
await udpClient.ConnectAsync(endpointpar);
Where is the issue? Many thanks!
Please try to also add the Private Networks (Client & Server) capability in your app's manifest.
On other hand, you can try the Official DatagramSocket sample to see whether the both devices can communicate with each other.
After some experimenting, I came out with the idea of using two different DatagramSocket instances: one to send out UDP messages, and one to listen for incoming messages. With this code, I've been able to send UDP messages to the remote device (as before) and also I've been able to receive UDP messages from the remote device.
async Task TestUdpIP_DifferentPorts()
{
// Writer to the DatagramSocket
DataWriter writer;
// Inbound and outbound DatagramSocket
DatagramSocket udpListener = new DatagramSocket();
DatagramSocket udpSender = new DatagramSocket();
try
{
// String containing the serializaed message
string serializedMessage = "Some message";
var controllerName = new HostName(controllerIpAddress.ToString());
var remoteHostName = new HostName(hostIpAddres.ToString());
// Bind listener
udpListener.MessageReceived += UdpClient_MessageReceived;
await udpListener.BindEndpointAsync(controllerName, controllerIpPort.ToString());
// Connect sender
await udpSender.ConnectAsync(remoteHostName, remoteHostPort.ToString());
// Reset the counter of messages received back from the remote robot
messagesReceived = 0;
// Send the message
writer = new DataWriter(udpSender.OutputStream);
writer.WriteString(JsonConvert.SerializeObject(message));
await writer.StoreAsync();
// Wait for robot status messages
await Task.Delay(1000);
}
catch (Exception ex)
{
// Some exception handling
}
udpSender.Dispose();
udpListener.Dispose();
}
void UdpClient_MessageReceived(DatagramSocket sender, DatagramSocketMessageReceivedEventArgs args)
{
// Just increment the number of messages received
messagesReceived++;
}
With this approach only the IP port (or service name) of the inbound listener needs to be specified. The framework will choose the next available outbound IP port.
Note: If I understand well the DatagramSocket.ConnectAsync(EndpointPair) documentation, the same DatagramSocket instance can be used to send and listen for incoming message, so I could not figure out the need for two different instances. From the documentation:
This ConnectAsync(EndPointPair) method on a DatagramSocket is used to define the local and remote endpoint where datagrams will be sent when using the OutputStream property. This method also restricts remote IP addresses of packets that will be accepted to the remote hostname in the endpointPair parameter. Only incoming packets that match the remote endpoint in the endpointPair parameter will trigger the MessageReceived event on the DatagramSocket.