summaryrefslogtreecommitdiffhomepage
path: root/tunnel/src/main/java/com/wireguard/util/Resolver.java
blob: 654e01f5f2789e93207a639b70f39b289b940c41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/*
 * Copyright © 2023 WireGuard LLC. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package com.wireguard.util;

import java.io.IOException;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

import android.net.IpPrefix;
import android.net.LinkProperties;
import android.net.Network;
import android.net.TrafficStats;
import android.util.Log;

import androidx.annotation.Nullable;

@NonNullForAll
public class Resolver {
    private static final String TAG = "WireGuard/Resolver";
    private static final int STATS_TAG = 3; // FIXME
    @Nullable private final Network network;
    @Nullable private final LinkProperties linkProps;
    @Nullable private IpPrefix nat64Prefix;

    public Resolver(Network network, LinkProperties linkProps) {
        this.network = network;
        this.linkProps = linkProps;
        if (linkProps != null) {
            this.nat64Prefix = linkProps.getNat64Prefix();
        }
    }

    public static boolean isULA(Inet6Address addr) {
        byte[] raw = addr.getAddress();
        return ((raw[0] & 0xfe) == 0xfc);
    }

    boolean isWithinNAT64Prefix(Inet6Address address) {
        if (nat64Prefix == null)
            return false;

        int prefixLength = nat64Prefix.getPrefixLength();
        byte[] rawAddr = address.getAddress();
        byte[] rawPrefix = nat64Prefix.getRawAddress();

        for (int i=0; i < prefixLength/8; i++) {
            if (rawAddr[i] != rawPrefix[i])
                return false;
        }

        return true;
    }

    boolean isPreferredIPv6(Inet6Address local, Inet6Address remote) {
        if (linkProps == null) {
            // Prefer IPv4 if there are not link properties that can
            // be tested.
            return false;
        }

        // * Prefer IPv4 if local or remote address is ULA
        // * Prefer IPv4 if remote IPv6 is within NAT64 prefix.
        // * Otherwise prefer IPv6
        boolean isLocalULA = isULA(local);
        boolean isRemoteULA = isULA(remote);

        if (isLocalULA || isRemoteULA) {
            return false;
        }

        if (isWithinNAT64Prefix(remote)) {
            return false;
        }

        return true;
    }

    public InetAddress resolve(String host) throws UnknownHostException {
        TrafficStats.setThreadStatsTag(STATS_TAG);
        final InetAddress[] candidates = network != null ? network.getAllByName(host) : InetAddress.getAllByName(host);
        InetAddress address = candidates[0];
        for (final InetAddress candidate : candidates) {
            DatagramSocket sock;

            try {
                sock = new DatagramSocket();
                TrafficStats.tagDatagramSocket(sock);
                if (network != null) {
                    network.bindSocket(sock);
                }
            } catch (SocketException e) {
                // Return first candidate as fallback
                Log.w(TAG, "DatagramSocket failed, fallback to: \"" + address);
                return address;
            } catch (IOException e) {
                // Return first candidate as fallback
                Log.w(TAG, "BindSocket failed, fallback to: \"" + address);
                return address;
            }

            sock.connect(candidate, 51820);

            if (sock.getLocalAddress().isAnyLocalAddress()) {
                // Connect didn't find a local address.
                Log.w(TAG, "No local address");
                continue;
            }

            Log.w(TAG, "Local address: " + sock.getLocalAddress());

            if (candidate instanceof Inet4Address) {
                // Accept IPv4 as preferred address.
                address = candidate;
                break;
            }

            Inet6Address local = (Inet6Address)sock.getLocalAddress();
            InetSocketAddress remoteSockAddr = (InetSocketAddress)sock.getRemoteSocketAddress();
            Inet6Address remote = (Inet6Address)remoteSockAddr.getAddress();
            sock.close();

            if (isPreferredIPv6(local, remote)) {
                address = candidate;
                break;
            }
        }
        Log.w(TAG, "Resolved \"" + host + "\" to: " + address);
        return address;
    }
}