Skip to content

iOS app connects to lighthouse (sort of) but cannot see other hosts or resolve their FQDNs #355

@rob3c

Description

@rob3c

Hi, I have a small nebula network that connects some linux hosts distributed between a few US states along with some cloud instances. All host firewalls are completely permissive for testing (port, proto, host all set to any for inbound and outbound). The network uses the 100.64.0.0/10 CGNAT range, all hosts can ping each other by IP and ".nebula" FQDN cert name, and SSH works well. The single lighthouse in this test has a nebula IP of 100.64.0.254 and only serves DNS over the nebula1 interface.

Unfortunately, I'm having trouble adding an iOS device (iphone 13 pro) to this setup that successfully allows ping by IP or FQDN on par with the other hosts. (I'm using the Net Analyzer app for ping tests.) I can see handshake log entries for the lighthouse and the phone, and the VPN on the phone thinks that it's successful and remains connected. However, some log entries suggest the handshake may be incomplete, and running list-hostmap in the lighthouse SSH interface doesn't show the phone.

Here's what I've tried so far:

I'm using the latest 0.9.0-127 app version. I created a config choosing the "from scratch" method. I exported the public key and had it signed by the CA. I used generated QR codes to import the signed cert as well as the CA cert. The phone's signed cert -networks entry was set to 100.64.0.13/10, similarly using the /10 prefix length that all other signed host certs used. In the app hosts section, there's a single lighthouse entry with its public IP and nebula IP:4242 values set. The lighthouse toggle enabled. Connection seems to work once it is given iOS permissions, and log entries appear in the lighthouse and phone logs.

Clue: the lighthouse shows both "Handshake message sent" and "Handshake message received" messages for the iphone's certName multiple times, but the iphone log only shows a single "Handshake message sent".

Regardless of that suspicious lack of a "...received" message in the phone log, since it stayed connected (which made me think the app thought everything was ok), I tried (unsuccessfully) ping via IP and FQDN using Net Analyzer, as mentioned above.

I originally thought I might not have the iOS firewall configured correctly for inbound traffic. The app only shows a rendered config that is read-only, and it shows an empty array for the inbound firewall that I assume means everything is blocked. I tried exporting the rendered config, modifying it to have fully permissive inbound entries, but it forced it back to the empty array when I imported it. I then saw the still open issue about editing inbound rules and realized that's the wrong approach.

After looking through the PacketTunnelProvider.swift source and related files, I decided to check for logs using the Console app on my mac. Here are the 2 JSON entries that seem relelvant (with some name and UUID redactions):

NESMVPNSession[Primary Tunnel:<nebula-connection-name>:<uuid-1-redacted>:(null)] starting with configuration: {
    name = <nebula-connection-name-redacted>
    identifier = <uuid-1-redacted>
    applicationName = Nebula
    application = net.defined.mobileNebula
    grade = 1
    VPN = {
        enabled = YES
        onDemandEnabled = NO
        disconnectOnDemandEnabled = NO
        onDemandUserOverrideDisabled = NO
        onDemandRules = (
            {
                action = connect
                interfaceTypeMatch = any
            },
        )
        protocol = {
            type = plugin
            identifier = <uuid-2-redacted>
            serverAddress = Nebula
            identityDataImported = NO
            disconnectOnSleep = NO
            disconnectOnIdle = NO
            disconnectOnIdleTimeout = 0
            disconnectOnWake = NO
            disconnectOnWakeTimeout = 0
            includeAllNetworks = NO
            excludeLocalNetworks = YES
            excludeCellularServices = YES
            excludeAPNs = YES
            excludeDeviceCommunication = YES
            enforceRoutes = NO
            pluginType = net.defined.mobileNebula
            authenticationMethod = 0
            providerConfiguration = {
                id = <uuid-3-redacted>,
            }
            providerBundleIdentifier = net.defined.mobileNebula.NebulaNetworkExtension
        }
        tunnelType = packet
    }
}

and

NESMVPNSession[Primary Tunnel:bigscore-test-cgnat:<guid-1-redacted>:(null)]: plugin set tunnel network settings: {
    tunnelRemoteAddress = 127.0.0.1
    DNSSettings = {
        protocol = cleartext
        server = (
            100.64.0.254,
        )
        matchDomainsNoSearch = NO
        allowFailover = NO
    }
    IPv4Settings = {
        configMethod = manual
        addresses = (
            100.64.0.13,
        )
        subnetMasks = (
            255.192.0.0,
        )
        includedRoutes = (
            {
                destinationAddress = 100.64.0.0
                destinationSubnetMask = 255.192.0.0
            },
        )
        overridePrimary = NO
    }
    IPv6Settings = {
        configMethod = manual
        addresses = ()
        networkPrefixLengths = ()
        includedRoutes = ()
    }
    MTU = 1300
}

I'm no mobile TUN or DNS expert, so I don't know if I caught all relevant config messages or if these look correct as-is or have obvious errors. For example, I'm surprised to not see anything filtering for ".nebula" domains (or even as a search domain, tho I don't want it used for that).

Also, I'm a bit suspicious of the subnet mask being used for both the interface and the destination addresses. I can't easily build the app locally, but if I could I'd try to see if changing v4Netmasks.append(network!.subnetMask) at line 138 in PacketTunnelProvider.swift to v4Netmasks.append("255.255.255.255") would make any difference:

if network!.ipLen == 4 {
        v4Addresses.append(network!.address)
        v4Netmasks.append(network!.subnetMask) // <-- is this right for iOS interfaces for a P2P network like Nebula?
        v4Routes.append(
          NEIPv4Route(
            destinationAddress: network!.maskedAddress,
            subnetMask: network!.subnetMask
          )
        )
      } else {
        v6Addresses.append(network!.address)
        v6PrefixLengths.append(network!.prefixLength as NSNumber)
        v6Routes.append(
          NEIPv6Route(
            destinationAddress: network!.maskedAddress,
            networkPrefixLength: network!.prefixLength as NSNumber
          )
        )
      }

But then I'd wonder how the app is working for anyone on iOS, and it seems like many people use it successfully despite not having to - or even being able to - configure the inbound firewall.

Any thoughts on what I'm doing wrong? What other info could I provide to help get this working if it's not obvious already?

Thanks in advance!
Robert

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions