iOS Socket cannot connect ipv6 address when use PacketTunnelProvider

I'm use iPad OS 17.5.1, when I try to use socket to connect to an ipv6 address created by PacketTunnelProvider in my iOS device, an error occurs. Here is the code to create socket server and client:

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int dx_create_ipv6_server(const char *ipv6_address, int port) {
    int server_fd;
    struct sockaddr_in6 server_addr;

    server_fd = socket(AF_INET6, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket() failed");
        return -1;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin6_family = AF_INET6;
    server_addr.sin6_port = htons(port);

    if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
        perror("inet_pton() failed");
        close(server_fd);
        return -1;
    }

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind() failed");
        close(server_fd);
        return -1;
    }

    if (listen(server_fd, 5) == -1) {
        perror("listen() failed");
        close(server_fd);
        return -1;
    }

    printf("Server is listening on [%s]:%d\n", ipv6_address, port);
    return server_fd;
}

int dx_accept_client_connection(int server_fd) {
    int client_fd;
    struct sockaddr_in6 client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
    if (client_fd == -1) {
        perror("accept() failed");
        return -1;
    }

    char client_ip[INET6_ADDRSTRLEN];
    inet_ntop(AF_INET6, &client_addr.sin6_addr, client_ip, sizeof(client_ip));
    printf("Client connected: [%s]\n", client_ip);

    return client_fd;
}

int dx_connect_to_ipv6_server(const char *ipv6_address, int port) {
    int client_fd;
    struct sockaddr_in6 server_addr;

    client_fd = socket(AF_INET6, SOCK_STREAM, 0);
    if (client_fd == -1) {
        perror("socket() failed");
        return -1;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin6_family = AF_INET6;
    server_addr.sin6_port = htons(port);

    if (inet_pton(AF_INET6, ipv6_address, &server_addr.sin6_addr) <= 0) {
        perror("inet_pton() failed");
        close(client_fd);
        return -1;
    }

    if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect() failed");
        close(client_fd);
        return -1;
    }

    printf("Connected to server [%s]:%d\n", ipv6_address, port);

    close(client_fd);
    return 0;
}

@implementation SocketTest

+ (void)startSever:(NSString *)addr port:(int)port {
    [[NSOperationQueue new] addOperationWithBlock:^{
        
        int server_fd = dx_create_ipv6_server(addr.UTF8String, port);
        if (server_fd == -1) {
            return;
        }

        int client_fd = dx_accept_client_connection(server_fd);
        if (client_fd == -1) {
            close(server_fd);
            return;
        }

        close(client_fd);
        close(server_fd);
    }];
}


+ (void)clientConnect:(NSString *)addr port:(int)port{
    [[NSOperationQueue new] addOperationWithBlock:^{
        dx_connect_to_ipv6_server(addr.UTF8String, port);
    }];
}


@end

PacketTunnelProvider code:

override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) {
        
        let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "fd84:306d:fc4e::1")
        
        let ipv6 = NEIPv6Settings(addresses: ["fd84:306d:fc4e::1"], networkPrefixLengths: 64)
        settings.ipv6Settings = ipv6
        
        
        setTunnelNetworkSettings(settings) { error in
            if error == nil {
                self.readPackets()
            }
            completionHandler(error)
        }
    }

private func readPackets() {
        // do nothing
        packetFlow.readPackets { [self] packets, protocols in
            self.packetFlow.writePackets(packets, withProtocols: protocols)
            self.readPackets()
        }
    }

At main target, in viewcontroller's viewDidAppear, after starting the VPN, executed following code:

[SocketTest startSever:@"fd84:306d:fc4e::1" port:12345];
sleep(3);
[SocketTest clientConnect:@"fd84:306d:fc4e::1" port:12345];

The startSever is executed correctly, but when executing:

connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))

in clientConnect, the code is blocked until it times out and returns -1.

**Even if I use GCDAsyncSocket or BlueSocket, I get the same error. The strange thing is that if I use the ipv4 address in PacketTunnelProvider, and change the above code to the ipv4 version and connect to ipv4 address, or use GCDAsyncSocket to perform the corresponding operation, it can be executed correctly. **

I tried to search Google for problems with ios-related ipv6 addresses, but I still couldn't find a solution. Is this a bug in the ios system or is there something wrong with my code? I hope to get your help!

Stackoverflow url: iOS Socket cannot connect ipv6 address when use PacketTunnelProvider

Answered by DTS Engineer in 814820022
it just print logs the source ip and dest ip, and then does nothing.

Interesting. I’m not sure how that works even with IPv4.

Packet tunnel providers are intended to be used to create VPN products. In a VPN product you typically open a tunnel to the VPN server and then forward any traffic you receive to the VPN server via that tunnel. If you use a packet tunnel provider for something that’s not a VPN product, you will, in the words of TN3120, encounter:

many edges cases and bugs during the development and deployment process.

That seems to be the case here.

I recommend that you rethink your approach to your product. DTS won’t be able to help you with the path that you’re on.

Share and Enjoy

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

Anybody help me

There isn’t anything fundamental that restricts a packet tunnel provider’s access to IPv6. There are, however, two common sticking points:

  • Packet tunnel providers are subject to NECP. I talked about this more in A Peek Behind the NECP Curtain.

  • IPv6 is much harder to implement correctly, especially when using a low-level API like BSD Sockets.

As to what’s going on in your case, it’s hard to say without knowing more about how the various bits fit together. You have client code and server code. You also mentioned a packet tunnel provider and a view controller. It seems like your server is in your view controller and your client is in your packet tunnel provider. Is that correct?

If so, that’s quite a weird way to set things up. What are you trying to achieve with your networking set up this way?

ps You might want to have a read of Quinn’s Top Ten DevForums Tips, and especially tip 3.

Share and Enjoy

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

Thank you for your reply.

My server and client are both in the viewcontroller.

What I want is to bind the ipv6 tunnel of PacketTunnelProvider to the server side of the socket, and then use the client side to request the server through ipv6 tunnel.

If the above code is changed to the ipv4 version, it runs correctly. What is the problem?

My server and client are both in the viewcontroller.

Hmmm. And your view controller is within your app, right?

Because adding a view controller to a packet tunnel provider weird be super weird.

Share and Enjoy

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

It’s better if you reply as a reply; see Quinn’s Top Ten DevForums Tips for this and other titbits.

At this point I think I need a better explanation of how the various parts of your product fit together:

  • What does your packet tunnel provider do?

  • Why does its container app have a client and a server in it?

  • When your tunnel is up, how are you expecting traffic to flow through the system?

Share and Enjoy

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

Thanks for your reply.

  • What does your packet tunnel provider do?

    I am making a case about how to use NetworkExtension to monitor ipv6 traffic to teach others how to use NetworkExtension to develop their own applications, such as the code listed above.

  • Why does its container app have a client and a server in it?

    Since it is a case, I wrote both the server and the client in the viewcontroller of the main target to facilitate others to run directly on iOS devices without a real remote server.

  • When your tunnel is up, how are you expecting traffic to flow through the system?

    In this case, I have not yet successfully connected from the client to the server, so the sample code is not implemented in the readPackets function

The current problem is that if I change the code provided above to the ipv4 version, it can be executed correctly.

After changing to ipv6, when the client connects to the ipv6 address bound to the server, the program times out and returns -1 when it runs to

connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr))

of dx_connect_to_ipv6_server, which is very confusing.

I am making a case about how to use NetworkExtension to monitor ipv6 traffic

Packet tunnel providers are intended to be use to support VPN clients. When you use them for non-VPN things, you inevitably run into problems. One of my former colleagues wrote TN3120 Expected use cases for Network Extension packet tunnel providers to explain this.

In this case, I have not yet successfully connected from the client to the server, so the sample code is not implemented in the readPackets function

But things are working in the IPv4, right? So, ignoring IPv6 for the moment, in the IPv4 case, how does traffic to flow through your system?

Share and Enjoy

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

Thank you for your reply

Yes, IPV4 works fine. To monitor traffic, I simply print out the source ip and dest ip contained in packets.

However, my case is for IPV6, and I have done a lot of preparation for it.

I need to re-state my problem so that you can understand it better. The xcode project contains 2 targets. One iOS App target contains some user interfaces, and starts VPN. The server and client are created in the App target. The other is the NetworkExtension plug-in, which contains the IPV6 address to be monitored by PacketTunnelProvider.

The process running steps are:

  1. Configure an IPV6 address in PacketTunnelProvider.

  2. In the iOS App target, after starting VPN, start the server side of the socket in viewDidAppear of viewcontroller and bind this IPV6 address. This is the correct work.

  3. The thread sleeps for 3 seconds to ensure that the server side is started, and then starts the client to connect to the IPV6 address bound to the server. At this time, the request timeout and return -1.

My question is why it cannot be executed when using IPV6. How do I modify my code to allow clinet to connect to the server? Or can you give me some professional suggestions?

Thanks for the providing more details. I think I’m starting to grok this. But I do wanna go back to your IPv4 case, just to make sure I understand how this works there.

Here’s what I have so far:

  1. Your container app saves a VPN configuration.

  2. And then starts the tunnel.

  3. The packet tunnel provider starts.

  4. It configures the tunnel to use IPv4 address X.

  5. The container app gets X from the packet tunnel provider.

  6. And it creates a listening socket and binds it to X.

  7. After things have settled down, your app starts a connection to X.

Am I right so far?

If so, what happens next? Presumably the packet tunnel provider receives TCP packets destined for X. What does it do with them?

Share and Enjoy

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

Thanks for your reply.

After connecting to X, I just analyze packets in PacketTunnelProvider, get the source IP and dest IP and print logs.

However, this is all done after the connection is successful. The current problem is that I cannot connect to X with an IPV6 address.

After connecting to X, I just analyze packets in PacketTunnelProvider, get the source IP and dest IP and print logs.

So, in your provider you have code that:

  1. Calls the readPacketObjects(completionHandler:) method to read packets that’ve been routed to your tunnel.

  2. Inspects the packets and logs the results.

Then what does it do? Drop the packets on the floor? Attempt to re-inject them somehow?

Share and Enjoy

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

Thanks for your reply.

Since I am doing a case, after the readPacketObjects(completionHandler:) call, it just print logs the source ip and dest ip, and then does nothing.

it just print logs the source ip and dest ip, and then does nothing.

Interesting. I’m not sure how that works even with IPv4.

Packet tunnel providers are intended to be used to create VPN products. In a VPN product you typically open a tunnel to the VPN server and then forward any traffic you receive to the VPN server via that tunnel. If you use a packet tunnel provider for something that’s not a VPN product, you will, in the words of TN3120, encounter:

many edges cases and bugs during the development and deployment process.

That seems to be the case here.

I recommend that you rethink your approach to your product. DTS won’t be able to help you with the path that you’re on.

Share and Enjoy

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

iOS Socket cannot connect ipv6 address when use PacketTunnelProvider
 
 
Q