summaryrefslogtreecommitdiffstats
path: root/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddresses.java
blob: a49c4e1fbf6aab108b8a42f05d74bb3f395ab78c (plain) (blame)
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
package com.yahoo.vespa.hosted.node.admin.task.util.network;

import com.google.common.net.InetAddresses;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * IP addresses - IP utilities to retrieve and manipulate addresses for docker host and docker containers in a
 * multi-home environment.
 *
 * The assumption is that DNS is the source of truth for which address are assigned to the host and which
 * that belongs to the containers. Only one address should be assigned to each.
 *
 * The behavior with respect to site-local addresses are distinct for IPv4 and IPv6. For IPv4 we choose
 * the site-local address (assume the public is a NAT address not assigned to the host interface (the typical aws setup)).
 *
 * For IPv6 we disregard any site-local addresses (these are normally not in DNS anyway).
 *
 * This class also provides some utilities for prefix translation.
 *
 * @author smorgrav
 */
public interface IPAddresses {

    InetAddress[] getAddresses(String hostname);

    default List<String> getAddresses(String hostname, IPVersion ipVersion) {
        return Stream.of(getAddresses(hostname))
                .filter(inetAddress -> isOfType(inetAddress, ipVersion))
                .map(InetAddresses::toAddrString)
                .collect(Collectors.toList());
    }

    /**
     * Get the IPv6 address for the host if any.
     *
     * @throws RuntimeException if multiple addresses are found
     * @return An optional string representation of the IP address (RFC 5952 compact format)
     */
    default Optional<Inet6Address> getIPv6Address(String hostname) {
        List<InetAddress> ipv6addresses = Stream.of(getAddresses(hostname))
                .filter(inetAddress -> inetAddress instanceof Inet6Address)
                .filter(inetAddress -> !inetAddress.isLinkLocalAddress())
                .filter(inetAddress -> !inetAddress.isSiteLocalAddress())
                .collect(Collectors.toList());

        if (ipv6addresses.isEmpty()) {
            return Optional.empty();
        }

        if (ipv6addresses.size() > 1) {
            String addresses =
                    ipv6addresses.stream().map(InetAddresses::toAddrString).collect(Collectors.joining(","));
            throw new RuntimeException(
                    String.format(
                            "Multiple IPv6 addresses found: %s. Perhaps a missing DNS entry or multiple AAAA records in DNS?",
                            addresses));
        }

        return Optional.of((Inet6Address)ipv6addresses.get(0));
    }

    /**
     * Get the IPv4 address for the host if any.
     *
     * @throws RuntimeException if multiple site-local addresses are found
     * @return An optional string representation of the IP address
     */
    default Optional<Inet4Address> getIPv4Address(String hostname) {
        InetAddress[] allAddresses = getAddresses(hostname);

        List<InetAddress> ipv4Addresses = Stream.of(allAddresses)
                .filter(inetAddress -> inetAddress instanceof Inet4Address)
                .collect(Collectors.toList());

        if (ipv4Addresses.size() == 1) return Optional.of((Inet4Address)ipv4Addresses.get(0));

        if (ipv4Addresses.isEmpty()) {
            Optional.empty();
        }

        List<InetAddress> siteLocalIPv4Addresses = Stream.of(allAddresses)
                .filter(inetAddress -> inetAddress instanceof Inet4Address)
                .filter(InetAddress::isSiteLocalAddress)
                .collect(Collectors.toList());

        if (siteLocalIPv4Addresses.size() == 1) return Optional.of((Inet4Address)siteLocalIPv4Addresses.get(0));

        String addresses =
                ipv4Addresses.stream().map(InetAddresses::toAddrString).collect(Collectors.joining(","));
        throw new RuntimeException(
                String.format(
                        "Multiple IPv4 addresses found: %s. Perhaps a missing DNS entry or multiple A records in DNS?",
                        addresses));
    }

    static boolean isOfType(InetAddress address, IPVersion ipVersion) {
        if (ipVersion.equals(IPVersion.IPv4) && address instanceof Inet4Address) return true;
        if (ipVersion.equals(IPVersion.IPv6) && address instanceof Inet6Address) return true;
        return false;
    }

    /**
     * For NPTed networks we want to find the private address from a public.
     *
     * @param address    The original address to translate
     * @param prefix     The prefix address
     * @param subnetSizeInBytes in bits - e.g a /64 subnet equals 8 bytes
     * @return The translated address
     */
    static InetAddress prefixTranslate(InetAddress address, InetAddress prefix, int subnetSizeInBytes) {
        return prefixTranslate(address.getAddress(), prefix.getAddress(), subnetSizeInBytes);
    }

    static InetAddress prefixTranslate(byte[] address, byte[] prefix, int nofBytes) {
        System.arraycopy(prefix, 0, address, 0, nofBytes);
        try {
            return InetAddress.getByAddress(address);
        } catch (UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }
}