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