1 /*
2  * Copyright (C) 2020 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.netlink;
18 
19 import static android.net.InetAddresses.parseNumericAddress;
20 import static android.system.OsConstants.AF_INET6;
21 import static android.system.OsConstants.NETLINK_ROUTE;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertTrue;
27 
28 import android.net.IpPrefix;
29 
30 import androidx.test.filters.SmallTest;
31 import androidx.test.runner.AndroidJUnit4;
32 
33 import libcore.util.HexEncoding;
34 
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.net.InetAddress;
39 import java.nio.ByteBuffer;
40 import java.nio.ByteOrder;
41 
42 @RunWith(AndroidJUnit4.class)
43 @SmallTest
44 public class NduseroptMessageTest {
45 
46     private static final byte ICMP_TYPE_RA = (byte) 134;
47 
48     private static final int IFINDEX1 = 15715755;
49     private static final int IFINDEX2 = 1431655765;
50 
51     // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding.
52     private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000";
53 
54     // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
55     private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000";
56 
57     // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding.
58     private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000";
59 
60     // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064
61     private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006";
62 
63     // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8
64     private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008";
65 
66     private static final InetAddress SADDR1 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX1);
67     private static final InetAddress SADDR2 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX2);
68 
69     private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR;
70     private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR;
71 
72     @Test
testParsing()73     public void testParsing() {
74         NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY));
75         assertMatches(AF_INET6, 0, IFINDEX1, ICMP_TYPE_RA, (byte) 0, SADDR1, msg);
76         assertNull(msg.option);
77 
78         msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
79         assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
80         assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
81     }
82 
83     @Test
testParseWithinNetlinkMessage()84     public void testParseWithinNetlinkMessage() throws Exception {
85         // A NduseroptMessage inside a netlink message. Ensure that it parses the same way both by
86         // parsing the netlink message via NetlinkMessage.parse() and by parsing the option itself
87         // with NduseroptMessage.parse().
88         final String hexBytes =
89                 "44000000440000000000000000000000"             // len=68, RTM_NEWNDUSEROPT
90                 + "0A0010001E0000008600000000000000"           // IPv6, opt_bytes=16, ifindex=30, RA
91                 + "260202580064FF9B0000000000000000"           // pref64, prefix=64:ff9b::/96, 600
92                 + "14000100FE800000000000000250B6FFFEB7C499";  // srcaddr=fe80::250:b6ff:feb7:c499
93 
94         ByteBuffer buf = toBuffer(hexBytes);
95         assertEquals(68, buf.limit());
96         buf.order(ByteOrder.nativeOrder());
97 
98         NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
99         assertNotNull(nlMsg);
100         assertTrue(nlMsg instanceof NduseroptMessage);
101 
102         NduseroptMessage msg = (NduseroptMessage) nlMsg;
103         InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
104         assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
105         assertPref64Option("64:ff9b::/96", msg.option);
106 
107         final String hexBytesWithoutHeader = hexBytes.substring(StructNlMsgHdr.STRUCT_SIZE * 2);
108         ByteBuffer bufWithoutHeader = toBuffer(hexBytesWithoutHeader);
109         assertEquals(52, bufWithoutHeader.limit());
110         msg = parseNduseroptMessage(bufWithoutHeader);
111         assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
112         assertPref64Option("64:ff9b::/96", msg.option);
113     }
114 
115     @Test
testParseUnknownOptionWithinNetlinkMessage()116     public void testParseUnknownOptionWithinNetlinkMessage() throws Exception {
117         final String hexBytes =
118                 "4C0000004400000000000000000000000"
119                 + "A0018001E0000008600000000000000"
120                 + "1903000000001770FD123456789000000000000000000001"  // RDNSS option
121                 + "14000100FE800000000000000250B6FFFEB7C499";
122 
123         ByteBuffer buf = toBuffer(hexBytes);
124         assertEquals(76, buf.limit());
125         buf.order(ByteOrder.nativeOrder());
126 
127         NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE);
128         assertNotNull(nlMsg);
129         assertTrue(nlMsg instanceof NduseroptMessage);
130 
131         NduseroptMessage msg = (NduseroptMessage) nlMsg;
132         InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30");
133         assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg);
134         assertEquals(NdOption.UNKNOWN, msg.option);
135     }
136 
137     @Test
testUnknownOption()138     public void testUnknownOption() {
139         ByteBuffer buf = toBuffer(MSG_PREF64);
140         // Replace the PREF64 option type (38) with an unknown option number.
141         final int optionStart = NduseroptMessage.STRUCT_SIZE;
142         assertEquals(38, buf.get(optionStart));
143         buf.put(optionStart, (byte) 42);
144 
145         NduseroptMessage msg = parseNduseroptMessage(buf);
146         assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
147         assertEquals(NdOption.UNKNOWN, msg.option);
148 
149         buf.flip();
150         assertEquals(42, buf.get(optionStart));
151         buf.put(optionStart, (byte) 38);
152 
153         msg = parseNduseroptMessage(buf);
154         assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
155         assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
156     }
157 
158     @Test
testZeroLengthOption()159     public void testZeroLengthOption() {
160         // Make sure an unknown option with a 0-byte length is ignored and parsing continues with
161         // the address, which comes after it.
162         final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR;
163         ByteBuffer buf = toBuffer(hexString);
164         assertEquals(52, buf.limit());
165         NduseroptMessage msg = parseNduseroptMessage(buf);
166         assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
167         assertNull(msg.option);
168     }
169 
170     @Test
testTooLongOption()171     public void testTooLongOption() {
172         // Make sure that if an option's length is too long, it's ignored and parsing continues with
173         // the address, which comes after it.
174         final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR;
175         ByteBuffer buf = toBuffer(hexString);
176         assertEquals(52, buf.limit());
177         NduseroptMessage msg = parseNduseroptMessage(buf);
178         assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg);
179         assertNull(msg.option);
180     }
181 
182     @Test
testOptionsTooLong()183     public void testOptionsTooLong() {
184         // Header claims 32 bytes of options. Buffer ends before options end.
185         String hexString = HDR_32BYTE + OPT_PREF64;
186         ByteBuffer buf = toBuffer(hexString);
187         assertEquals(32, buf.limit());
188         assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
189 
190         // Header claims 32 bytes of options. Buffer ends at end of options with no source address.
191         hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64;
192         buf = toBuffer(hexString);
193         assertEquals(48, buf.limit());
194         assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE));
195     }
196 
197     @Test
testTruncation()198     public void testTruncation() {
199         final int optLen = MSG_PREF64.length() / 2;  // 1 byte = 2 hex chars
200         for (int len = 0; len < optLen; len++) {
201             ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2));
202             NduseroptMessage msg = parseNduseroptMessage(buf);
203             if (len < optLen) {
204                 assertNull(msg);
205             } else {
206                 assertNotNull(msg);
207                 assertPref64Option("2001:db8:3:4:5:6::/96", msg.option);
208             }
209         }
210     }
211 
212     @Test
testToString()213     public void testToString() {
214         NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64));
215         assertNotNull(msg);
216         assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)",
217                 msg.toString());
218     }
219 
220     // Convenience method to parse a NduseroptMessage that's not part of a netlink message.
parseNduseroptMessage(ByteBuffer buf)221     private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) {
222         return NduseroptMessage.parse(null, buf);
223     }
224 
toBuffer(String hexString)225     private ByteBuffer toBuffer(String hexString) {
226         return ByteBuffer.wrap(HexEncoding.decode(hexString));
227     }
228 
assertMatches(int family, int optsLen, int ifindex, byte icmpType, byte icmpCode, InetAddress srcaddr, NduseroptMessage msg)229     private void assertMatches(int family, int optsLen, int ifindex, byte icmpType,
230             byte icmpCode, InetAddress srcaddr, NduseroptMessage msg) {
231         assertNotNull(msg);
232         assertEquals(family, msg.family);
233         assertEquals(ifindex, msg.ifindex);
234         assertEquals(optsLen, msg.opts_len);
235         assertEquals(icmpType, msg.icmp_type);
236         assertEquals(icmpCode, msg.icmp_code);
237         assertEquals(srcaddr, msg.srcaddr);
238     }
239 
assertPref64Option(String prefix, NdOption opt)240     private void assertPref64Option(String prefix, NdOption opt) {
241         assertNotNull(opt);
242         assertTrue(opt instanceof StructNdOptPref64);
243         StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt;
244         assertEquals(new IpPrefix(prefix), pref64Opt.prefix);
245     }
246 }
247