1 /*
2  * Copyright (C) 2022 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 com.android.testutils;
18 
19 import static android.system.OsConstants.IPPROTO_ICMPV6;
20 
21 import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
22 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_SLLA;
23 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
24 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
25 import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
26 import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
27 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
28 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
29 import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
30 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
31 import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
32 
33 import android.net.InetAddresses;
34 import android.net.IpPrefix;
35 import android.net.MacAddress;
36 import android.util.ArrayMap;
37 import android.util.Log;
38 import android.util.Pair;
39 
40 import com.android.net.module.util.Ipv6Utils;
41 import com.android.net.module.util.Struct;
42 import com.android.net.module.util.structs.EthernetHeader;
43 import com.android.net.module.util.structs.Icmpv6Header;
44 import com.android.net.module.util.structs.Ipv6Header;
45 import com.android.net.module.util.structs.LlaOption;
46 import com.android.net.module.util.structs.NsHeader;
47 import com.android.net.module.util.structs.PrefixInformationOption;
48 import com.android.net.module.util.structs.RdnssOption;
49 
50 import java.io.IOException;
51 import java.net.Inet6Address;
52 import java.nio.ByteBuffer;
53 import java.util.Map;
54 import java.util.Objects;
55 import java.util.Random;
56 
57 /**
58  * ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface.
59  * TODO: rename to NdResponder
60  */
61 public class RouterAdvertisementResponder extends PacketResponder {
62     private static final String TAG = "RouterAdvertisementResponder";
63     private static final Inet6Address DNS_SERVER =
64             (Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64");
65     private final TapPacketReader mPacketReader;
66     // Maps IPv6 address to MacAddress and isRouter boolean.
67     private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>();
68     private final IpPrefix mPrefix;
69 
RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix)70     public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) {
71         super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG);
72         mPacketReader = packetReader;
73         mPrefix = Objects.requireNonNull(prefix);
74     }
75 
RouterAdvertisementResponder(TapPacketReader packetReader)76     public RouterAdvertisementResponder(TapPacketReader packetReader) {
77         this(packetReader, makeRandomPrefix());
78     }
79 
makeRandomPrefix()80     private static IpPrefix makeRandomPrefix() {
81         final byte[] prefixBytes = new IpPrefix("2001:db8::/64").getAddress().getAddress();
82         final Random r = new Random();
83         for (int i = 4; i < 8; i++) {
84             prefixBytes[i] = (byte) r.nextInt();
85         }
86         return new IpPrefix(prefixBytes, 64);
87     }
88 
89     /** Returns true if the packet is a router solicitation or neighbor solicitation message. */
isRsOrNs(byte[] packet)90     private static boolean isRsOrNs(byte[] packet) {
91         final ByteBuffer buffer = ByteBuffer.wrap(packet);
92         final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer);
93         if (ethHeader.etherType != ETHER_TYPE_IPV6) {
94             return false;
95         }
96         final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer);
97         if (ipv6Header.nextHeader != IPPROTO_ICMPV6) {
98             return false;
99         }
100         final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer);
101         return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION
102             || icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION;
103     }
104 
105     /**
106      * Adds a new router to be advertised.
107      * @param mac the mac address of the router.
108      * @param ip the link-local address of the router.
109      */
addRouterEntry(MacAddress mac, Inet6Address ip)110     public void addRouterEntry(MacAddress mac, Inet6Address ip) {
111         mNeighborMap.put(ip, new Pair<>(mac, true));
112     }
113 
114     /**
115      * Adds a new neighbor to be advertised.
116      * @param mac the mac address of the neighbor.
117      * @param ip the link-local address of the neighbor.
118      */
addNeighborEntry(MacAddress mac, Inet6Address ip)119     public void addNeighborEntry(MacAddress mac, Inet6Address ip) {
120         mNeighborMap.put(ip, new Pair<>(mac, false));
121     }
122 
123     /**
124      * @return the prefix that is announced in the Router Advertisements sent by this object.
125      */
getPrefix()126     public IpPrefix getPrefix() {
127         return mPrefix;
128     }
129 
buildPrefixOption()130     private ByteBuffer buildPrefixOption() {
131         return PrefixInformationOption.build(
132                 mPrefix, (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS),
133                 3600 /* valid lifetime */, 3600 /* preferred lifetime */);
134     }
135 
buildRdnssOption()136     private ByteBuffer buildRdnssOption() {
137         return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER);
138     }
139 
buildSllaOption(MacAddress srcMac)140     private ByteBuffer buildSllaOption(MacAddress srcMac) {
141         return LlaOption.build((byte) ICMPV6_ND_OPTION_SLLA, srcMac);
142     }
143 
buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp)144     private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) {
145         return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST,
146                 (byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/,
147                 0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption(),
148                 buildSllaOption(srcMac));
149     }
150 
sendResponse(TapPacketReader reader, ByteBuffer buffer)151     private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) {
152         try {
153             reader.sendResponse(buffer);
154         } catch (IOException e) {
155             // Throwing an exception here will crash the test process. Let's stick to logging, as
156             // the test will fail either way.
157             Log.e(TAG, "Failed to send buffer", e);
158         }
159     }
160 
replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac)161     private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) {
162         for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) {
163             final boolean isRouter = it.getValue().second;
164             if (!isRouter) {
165                 continue;
166             }
167             final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey());
168             sendResponse(reader, raResponse);
169         }
170     }
171 
replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac, Inet6Address dstIp, Inet6Address targetIp)172     private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac,
173             Inet6Address dstIp, Inet6Address targetIp) {
174         final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp);
175         if (neighbor == null) {
176             return;
177         }
178 
179         final MacAddress srcMac = neighbor.first;
180         final boolean isRouter = neighbor.second;
181         int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
182         if (isRouter) {
183             flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
184         }
185 
186         final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
187         final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp,
188                 flags, targetIp, tlla);
189         sendResponse(reader, naResponse);
190     }
191 
192     @Override
replyToPacket(byte[] packet, TapPacketReader reader)193     protected void replyToPacket(byte[] packet, TapPacketReader reader) {
194         final ByteBuffer buf = ByteBuffer.wrap(packet);
195         // Messages are filtered by parent class, so it is safe to assume that packet is either an
196         // RS or NS.
197         final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
198         final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
199         final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
200 
201         if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) {
202             replyToRouterSolicitation(reader, ethHdr.srcMac);
203         } else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) {
204             final NsHeader nsHeader = Struct.parse(NsHeader.class, buf);
205             replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target);
206         }
207     }
208 }
209