jitsi / ice4j

A Java implementation of the ICE protocol
Apache License 2.0
437 stars 232 forks source link

ice4j 3.0 does not generate the IPv4 srflx candidate when android device APN is set to IPv4/IPv6 #255

Open cmeng-git opened 2 years ago

cmeng-git commented 2 years ago

See https://sanctum9.wordpress.com/2019/03/15/switch-to-ipv6-on-android/ on how to change network APN on android device.

The problem is observed with android device Note-10+ when the network APN is set to both 'IPv4/IPv6' (default); The observation is performed on ice4j v3.0-55-g32a8aad on aTalk between Note-10 and SM-J730GM android devices. The HostCandidateHarvester#harvest() returns 3 host candidates containing an IPv6 public address.

2022-03-13 07:05:47.138 15183-15469/org.atalk.android I/aTalk: [245] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:1 1 udp 2130706431 2401:7400:4005:e3d9:1:1:9694:173e 5000 typ host
2022-03-13 07:05:47.158 15183-15469/org.atalk.android I/aTalk: [245] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:2 1 udp 2130706431 10.134.146.187 5000 typ host
2022-03-13 07:05:47.179 15183-15469/org.atalk.android I/aTalk: [245] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:3 1 udp 2113932031 10.170.32.102 5000 typ host

when this happen, the jingle:transports:ice-udp stanza contains only the following candidates with srflx candidate missing. Because of the missing srflx candidate, the call setup failed.

<candidate port='58979' foundation='363454872' component='1' priority='2122262783' type='host' generation='0' protocol='udp' ip='2401:7400:4000:2e4a:1:2:93f1:b634' id='e49ea0a0-87e9-46a1-9c2e-1666b6ce8e32'/>
<candidate port='53151' foundation='2382162603' component='1' priority='2122194687' type='host' generation='0' protocol='udp' ip='10.142.116.152' id='97f487cc-5eb7-43a2-addf-9c2f4e782553'/>
<candidate rel-port='53151' port='53151' foundation='842163049' component='1' priority='1685987071' type='srflx' generation='0' protocol='udp' ip='119.56.101.12' rel-addr='10.142.116.152' id='f5b8f308-5810-49b5-bc14-f4d0d25953fc'/>
<candidate rel-port='53151' port='50438' foundation='1623785525' component='1' priority='41820671' type='relay' generation='0' protocol='udp' ip='42.60.77.13' rel-addr='119.56.101.12' id='3527fcc4-c91b-48f2-bce6-5bc283c320ef'/>
<candidate rel-port='53629' port='55128' foundation='776666309' component='1' priority='25042943' type='relay' generation='0' protocol='udp' ip='42.60.77.13' rel-addr='119.56.101.12' id='51efb2b2-fe49-4c15-ae80-a00a02d21617'/>

Not sure if there is such thing as IPv6 srflx candidate; or even work on android device that supports only IPv4 address if so. Running conversations on Note-10+, it sends all the above candidates, and also the srflx candidate with IPv4 public address i.e.

<candidate foundation='3' component='1' protocol='udp' priority='1677724415' generation='0' id='11' ip='119.56.100.180' port='5002' type='srflx' rel-addr='10.143.27.57' rel-port='5002' network='0'/>

However when network APN is set to IPv4 only; then host candidates and jingle:transports:ice-udp contents are captured as below. It is found that the transport candidate now contains the candidate type srflx, then the call setup is successfully.

2022-03-13 07:05:47.158 15183-15469/org.atalk.android I/aTalk: [245] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:1 1 udp 2130706431 10.134.146.187 5000 typ host
2022-03-13 07:05:47.179 15183-15469/org.atalk.android I/aTalk: [245] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:2 1 udp 2113932031 10.170.32.102 5000 typ host
<candidate foundation='1' component='1' protocol='udp' priority='2130706431' generation='0' id='9' ip='10.143.27.57' port='5002' type='host' network='0'/>
<candidate foundation='2' component='1' protocol='udp' priority='2130706431' generation='0' id='10' ip='10.170.32.102' port='5002' type='host' network='0'/>
<candidate foundation='3' component='1' protocol='udp' priority='1677724415' generation='0' id='11' ip='119.56.100.180' port='5002' type='srflx' rel-addr='10.143.27.57' rel-port='5002' network='0'/>
<candidate foundation='4' component='1' protocol='udp' priority='2815' generation='0' id='12' ip='42.60.77.13' port='50446' type='relay' network='0'/>
cmeng-git commented 2 years ago

After further investigation, below are my findings that give rise to the problems:

When the android mobile APN is set to IPv4/IPv6, it is found that all the user defined STUN servers FQDN host names are also resolved to IPv6 addresses. Therefore with the below 3 discovered Host candidates, except for the first IPv6 candidate, the user defined STUN servers are not reachable with the other two IPv4 candidates.

2022-03-14 15:13:32.322 6643-7172/org.atalk.android I/aTalk: [239] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:1 1 udp 2130706431 2401:7400:4004:3b9f:1:2:9ce8:6fc8 5000 typ host
2022-03-14 15:13:32.324 6643-7172/org.atalk.android I/aTalk: [239] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:2 1 udp 2130706431 10.135.40.28 5000 typ host
2022-03-14 15:13:32.327 6643-7172/org.atalk.android I/aTalk: [239] org.ice4j.ice.harvest.HostCandidateHarvester.log() Host candidate added: candidate:3 1 udp 2113932031 10.170.32.102 5000 typ host

However the STUN server returns a redundant srflx candidate on STUN request for the first IPv6 public address, hence it is discarded. Currently, the problem can be resolved when the user entered the STUN server with an actual IP address e.g. STUN harvester(srvr: 172.217.213.127:19302/udp).

No sure if there is any way to force android device to return both IPv4 and IPv6 addresses for a user defined FQDN STUN host name; when the device mobile APN = IPv4/IPv6.

Found that android chrome browser is able to show both IPv4 and IPv6 addresses for the device public IP.

cmeng-git commented 2 years ago

Found the solution with implementing the following function in IceUdpTransportManager.

    /**
     * Generate a list of TransportAddress from the given hostname, port and transport.
     * The given host name is resolved into both IPv4 and IPv6 InetAddresses.
     *
     * Note: android InetAddress.getByName(hostname) returns the first IP found, any may be an IPv6 InetAddress;
     * if mobile network setting for APN=IPV4/IPv6 or APN=IPv6. This causes problem in STUN candidate harvest:
     * @see https://github.com/jitsi/ice4j/issues/255
     *
     * @param hostname the address itself
     * @param port the port number
     * @param transport the transport to use with this address.
     */
    protected List<TransportAddress> getTransportAddress(String hostname, int port, Transport transport)
    {
        List<TransportAddress> transportAddress = new ArrayList<>();
        try {
            // return all associated InetAddress in both IPv4 and IPv6 address
            InetAddress[] inetAddresses = InetAddress.getAllByName(hostname);
            for (InetAddress inetAddress : inetAddresses) {
                transportAddress.add(new TransportAddress(inetAddress, port, transport));
            }
        } catch (UnknownHostException e) {
            Timber.e("UnknownHostException: %s", e.getMessage());
        }
        return transportAddress;
    }
NimbleDev commented 8 months ago

Found the solution with implementing the following function in IceUdpTransportManager.

    /**
     * Generate a list of TransportAddress from the given hostname, port and transport.
     * The given host name is resolved into both IPv4 and IPv6 InetAddresses.
     *
     * Note: android InetAddress.getByName(hostname) returns the first IP found, any may be an IPv6 InetAddress;
     * if mobile network setting for APN=IPV4/IPv6 or APN=IPv6. This causes problem in STUN candidate harvest:
     * @see https://github.com/jitsi/ice4j/issues/255
     *
     * @param hostname the address itself
     * @param port the port number
     * @param transport the transport to use with this address.
     */
    protected List<TransportAddress> getTransportAddress(String hostname, int port, Transport transport)
    {
        List<TransportAddress> transportAddress = new ArrayList<>();
        try {
            // return all associated InetAddress in both IPv4 and IPv6 address
            InetAddress[] inetAddresses = InetAddress.getAllByName(hostname);
            for (InetAddress inetAddress : inetAddresses) {
                transportAddress.add(new TransportAddress(inetAddress, port, transport));
            }
        } catch (UnknownHostException e) {
            Timber.e("UnknownHostException: %s", e.getMessage());
        }
        return transportAddress;
    }

I've encountered the same problem in my self hosted jitsi. But in current JVB version, getTransportAddress can not be found any more. Did you upgrade your jvb and found the new solution?

Looing forward to your reply eagerly and thx a lot in advance!