1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net;
18 
19 import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
20 import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.SystemApi;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.system.OsConstants;
28 
29 import com.android.net.module.util.IpUtils;
30 
31 import java.net.Inet4Address;
32 import java.net.Inet6Address;
33 import java.net.InetAddress;
34 import java.net.UnknownHostException;
35 import java.nio.ByteBuffer;
36 import java.nio.ByteOrder;
37 import java.util.Objects;
38 
39 /** @hide */
40 @SystemApi
41 public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
42     private static final int IPV4_HEADER_LENGTH = 20;
43     private static final int IPV6_HEADER_LENGTH = 40;
44     private static final int UDP_HEADER_LENGTH = 8;
45 
46     // This should only be constructed via static factory methods, such as
47     // nattKeepalivePacket
NattKeepalivePacketData(@onNull InetAddress srcAddress, int srcPort, @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data)48     public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
49             @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws
50             InvalidPacketException {
51         super(srcAddress, srcPort, dstAddress, dstPort, data);
52     }
53 
54     /**
55      * Factory method to create Nat-T keepalive packet structure.
56      * @hide
57      */
nattKeepalivePacket( InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)58     public static NattKeepalivePacketData nattKeepalivePacket(
59             InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
60             throws InvalidPacketException {
61         if (dstPort != NattSocketKeepalive.NATT_PORT) {
62             throw new InvalidPacketException(ERROR_INVALID_PORT);
63         }
64 
65         // Convert IPv4 mapped v6 address to v4 if any.
66         final InetAddress srcAddr, dstAddr;
67         try {
68             srcAddr = InetAddress.getByAddress(srcAddress.getAddress());
69             dstAddr = InetAddress.getByAddress(dstAddress.getAddress());
70         } catch (UnknownHostException e) {
71             throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
72         }
73 
74         if (srcAddr instanceof Inet4Address && dstAddr instanceof Inet4Address) {
75             return nattKeepalivePacketv4(
76                     (Inet4Address) srcAddr, srcPort, (Inet4Address) dstAddr, dstPort);
77         } else if (srcAddr instanceof Inet6Address && dstAddr instanceof Inet6Address) {
78             return nattKeepalivePacketv6(
79                     (Inet6Address) srcAddr, srcPort, (Inet6Address) dstAddr, dstPort);
80         } else {
81             // Destination address and source address should be the same IP family.
82             throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
83         }
84     }
85 
nattKeepalivePacketv4( Inet4Address srcAddress, int srcPort, Inet4Address dstAddress, int dstPort)86     private static NattKeepalivePacketData nattKeepalivePacketv4(
87             Inet4Address srcAddress, int srcPort, Inet4Address dstAddress, int dstPort)
88             throws InvalidPacketException {
89         int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
90         final ByteBuffer buf = ByteBuffer.allocate(length);
91         buf.order(ByteOrder.BIG_ENDIAN);
92         buf.putShort((short) 0x4500);                       // IP version and TOS
93         buf.putShort((short) length);
94         buf.putShort((short) 0);                            // ID
95         buf.putShort((short) 0x4000);                       // Flags(DF), offset
96         // Technically speaking, this should be reading/using the v4 sysctl
97         // /proc/sys/net/ipv4/ip_default_ttl. Use hard-coded 64 for simplicity.
98         buf.put((byte) 64);                                 // TTL
99         buf.put((byte) OsConstants.IPPROTO_UDP);
100         final int ipChecksumOffset = buf.position();
101         buf.putShort((short) 0);                            // IP checksum
102         buf.put(srcAddress.getAddress());
103         buf.put(dstAddress.getAddress());
104         buf.putShort((short) srcPort);
105         buf.putShort((short) dstPort);
106         buf.putShort((short) (UDP_HEADER_LENGTH + 1));      // UDP length
107         final int udpChecksumOffset = buf.position();
108         buf.putShort((short) 0);                            // UDP checksum
109         buf.put((byte) 0xff);                               // NAT-T keepalive
110         buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
111         buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
112 
113         return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
114     }
115 
nattKeepalivePacketv6( Inet6Address srcAddress, int srcPort, Inet6Address dstAddress, int dstPort)116     private static NattKeepalivePacketData nattKeepalivePacketv6(
117             Inet6Address srcAddress, int srcPort, Inet6Address dstAddress, int dstPort)
118             throws InvalidPacketException {
119         final ByteBuffer buf = ByteBuffer.allocate(IPV6_HEADER_LENGTH + UDP_HEADER_LENGTH + 1);
120         buf.order(ByteOrder.BIG_ENDIAN);
121         buf.putInt(0x60000000);                         // IP version, traffic class and flow label
122         buf.putShort((short) (UDP_HEADER_LENGTH + 1));  // Payload length
123         buf.put((byte) OsConstants.IPPROTO_UDP);        // Next header
124         // For native ipv6, this hop limit value should use the per interface v6 hoplimit sysctl.
125         // For 464xlat, this value should use the v4 ttl sysctl.
126         // Either way, for simplicity, just hard code 64.
127         buf.put((byte) 64);                             // Hop limit
128         buf.put(srcAddress.getAddress());
129         buf.put(dstAddress.getAddress());
130         // UDP
131         buf.putShort((short) srcPort);
132         buf.putShort((short) dstPort);
133         buf.putShort((short) (UDP_HEADER_LENGTH + 1));  // UDP length = Payload length
134         final int udpChecksumOffset = buf.position();
135         buf.putShort((short) 0);                        // UDP checksum
136         buf.put((byte) 0xff);                           // NAT-T keepalive. 1 byte of data
137         buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV6_HEADER_LENGTH));
138         return new NattKeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
139     }
140     /** Parcelable Implementation */
describeContents()141     public int describeContents() {
142         return 0;
143     }
144 
145     /** Write to parcel */
writeToParcel(@onNull Parcel out, int flags)146     public void writeToParcel(@NonNull Parcel out, int flags) {
147         out.writeString(getSrcAddress().getHostAddress());
148         out.writeString(getDstAddress().getHostAddress());
149         out.writeInt(getSrcPort());
150         out.writeInt(getDstPort());
151     }
152 
153     /** Parcelable Creator */
154     public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR =
155             new Parcelable.Creator<NattKeepalivePacketData>() {
156                 public NattKeepalivePacketData createFromParcel(Parcel in) {
157                     final InetAddress srcAddress =
158                             InetAddresses.parseNumericAddress(in.readString());
159                     final InetAddress dstAddress =
160                             InetAddresses.parseNumericAddress(in.readString());
161                     final int srcPort = in.readInt();
162                     final int dstPort = in.readInt();
163                     try {
164                         return NattKeepalivePacketData.nattKeepalivePacket(srcAddress, srcPort,
165                                     dstAddress, dstPort);
166                     } catch (InvalidPacketException e) {
167                         throw new IllegalArgumentException(
168                                 "Invalid NAT-T keepalive data: " + e.getError());
169                     }
170                 }
171 
172                 public NattKeepalivePacketData[] newArray(int size) {
173                     return new NattKeepalivePacketData[size];
174                 }
175             };
176 
177     @Override
equals(@ullable final Object o)178     public boolean equals(@Nullable final Object o) {
179         if (!(o instanceof NattKeepalivePacketData)) return false;
180         final NattKeepalivePacketData other = (NattKeepalivePacketData) o;
181         final InetAddress srcAddress = getSrcAddress();
182         final InetAddress dstAddress = getDstAddress();
183         return srcAddress.equals(other.getSrcAddress())
184             && dstAddress.equals(other.getDstAddress())
185             && getSrcPort() == other.getSrcPort()
186             && getDstPort() == other.getDstPort();
187     }
188 
189     @Override
hashCode()190     public int hashCode() {
191         return Objects.hash(getSrcAddress(), getDstAddress(), getSrcPort(), getDstPort());
192     }
193 }
194