blob: 51df93517966724c5cfacde7844fa3f51be89423 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.testutils;
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
import static com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_AUTONOMOUS;
import static com.android.net.module.util.NetworkStackConstants.PIO_FLAG_ON_LINK;
import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.MacAddress;
import android.util.ArrayMap;
import android.util.Pair;
import com.android.net.module.util.Ipv6Utils;
import com.android.net.module.util.Struct;
import com.android.net.module.util.structs.EthernetHeader;
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.LlaOption;
import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.PrefixInformationOption;
import com.android.net.module.util.structs.RdnssOption;
import java.io.IOException;
import java.net.Inet6Address;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
/**
* ND (RA & NA) responder class useful for tests that require a provisioned IPv6 interface.
* TODO: rename to NdResponder
*/
public class RouterAdvertisementResponder extends PacketResponder {
private static final String TAG = "RouterAdvertisementResponder";
private static final Inet6Address DNS_SERVER =
(Inet6Address) InetAddresses.parseNumericAddress("2001:4860:4860::64");
private final TapPacketReader mPacketReader;
// Maps IPv6 address to MacAddress and isRouter boolean.
private final Map<Inet6Address, Pair<MacAddress, Boolean>> mNeighborMap = new ArrayMap<>();
private final IpPrefix mPrefix;
public RouterAdvertisementResponder(TapPacketReader packetReader, IpPrefix prefix) {
super(packetReader, RouterAdvertisementResponder::isRsOrNs, TAG);
mPacketReader = packetReader;
mPrefix = Objects.requireNonNull(prefix);
}
public RouterAdvertisementResponder(TapPacketReader packetReader) {
this(packetReader, makeRandomPrefix());
}
private static IpPrefix makeRandomPrefix() {
final byte[] prefixBytes = new IpPrefix("2001:db8::/64").getAddress().getAddress();
final Random r = new Random();
for (int i = 4; i < 8; i++) {
prefixBytes[i] = (byte) r.nextInt();
}
return new IpPrefix(prefixBytes, 64);
}
/** Returns true if the packet is a router solicitation or neighbor solicitation message. */
private static boolean isRsOrNs(byte[] packet) {
final ByteBuffer buffer = ByteBuffer.wrap(packet);
final EthernetHeader ethHeader = Struct.parse(EthernetHeader.class, buffer);
if (ethHeader.etherType != ETHER_TYPE_IPV6) {
return false;
}
final Ipv6Header ipv6Header = Struct.parse(Ipv6Header.class, buffer);
if (ipv6Header.nextHeader != IPPROTO_ICMPV6) {
return false;
}
final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buffer);
return icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION
|| icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION;
}
/**
* Adds a new router to be advertised.
* @param mac the mac address of the router.
* @param ip the link-local address of the router.
*/
public void addRouterEntry(MacAddress mac, Inet6Address ip) {
mNeighborMap.put(ip, new Pair<>(mac, true));
}
/**
* Adds a new neighbor to be advertised.
* @param mac the mac address of the neighbor.
* @param ip the link-local address of the neighbor.
*/
public void addNeighborEntry(MacAddress mac, Inet6Address ip) {
mNeighborMap.put(ip, new Pair<>(mac, false));
}
/**
* @return the prefix that is announced in the Router Advertisements sent by this object.
*/
public IpPrefix getPrefix() {
return mPrefix;
}
private ByteBuffer buildPrefixOption() {
return PrefixInformationOption.build(
mPrefix, (byte) (PIO_FLAG_ON_LINK | PIO_FLAG_AUTONOMOUS),
3600 /* valid lifetime */, 3600 /* preferred lifetime */);
}
private ByteBuffer buildRdnssOption() {
return RdnssOption.build(3600/*lifetime, must be at least 120*/, DNS_SERVER);
}
private ByteBuffer buildRaPacket(MacAddress srcMac, MacAddress dstMac, Inet6Address srcIp) {
return Ipv6Utils.buildRaPacket(srcMac, dstMac, srcIp, IPV6_ADDR_ALL_NODES_MULTICAST,
(byte) 0 /*M=0, O=0*/, 3600 /*lifetime*/, 0 /*reachableTime, unspecified*/,
0/*retransTimer, unspecified*/, buildPrefixOption(), buildRdnssOption());
}
private static void sendResponse(TapPacketReader reader, ByteBuffer buffer) {
try {
reader.sendResponse(buffer);
} catch (IOException e) {
throw new RuntimeException("Failed to send buffer.");
}
}
private void replyToRouterSolicitation(TapPacketReader reader, MacAddress dstMac) {
for (Map.Entry<Inet6Address, Pair<MacAddress, Boolean>> it : mNeighborMap.entrySet()) {
final boolean isRouter = it.getValue().second;
if (!isRouter) {
continue;
}
final ByteBuffer raResponse = buildRaPacket(it.getValue().first, dstMac, it.getKey());
sendResponse(reader, raResponse);
}
}
private void replyToNeighborSolicitation(TapPacketReader reader, MacAddress dstMac,
Inet6Address dstIp, Inet6Address targetIp) {
final Pair<MacAddress, Boolean> neighbor = mNeighborMap.get(targetIp);
if (neighbor == null) {
return;
}
final MacAddress srcMac = neighbor.first;
final boolean isRouter = neighbor.second;
int flags = NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED | NEIGHBOR_ADVERTISEMENT_FLAG_OVERRIDE;
if (isRouter) {
flags |= NEIGHBOR_ADVERTISEMENT_FLAG_ROUTER;
}
final ByteBuffer tlla = LlaOption.build((byte) ICMPV6_ND_OPTION_TLLA, srcMac);
final ByteBuffer naResponse = Ipv6Utils.buildNaPacket(srcMac, dstMac, targetIp, dstIp,
flags, targetIp, tlla);
sendResponse(reader, naResponse);
}
@Override
protected void replyToPacket(byte[] packet, TapPacketReader reader) {
final ByteBuffer buf = ByteBuffer.wrap(packet);
// Messages are filtered by parent class, so it is safe to assume that packet is either an
// RS or NS.
final EthernetHeader ethHdr = Struct.parse(EthernetHeader.class, buf);
final Ipv6Header ipv6Hdr = Struct.parse(Ipv6Header.class, buf);
final Icmpv6Header icmpv6Header = Struct.parse(Icmpv6Header.class, buf);
if (icmpv6Header.type == ICMPV6_ROUTER_SOLICITATION) {
replyToRouterSolicitation(reader, ethHdr.srcMac);
} else if (icmpv6Header.type == ICMPV6_NEIGHBOR_SOLICITATION) {
final NsHeader nsHeader = Struct.parse(NsHeader.class, buf);
replyToNeighborSolicitation(reader, ethHdr.srcMac, ipv6Hdr.srcIp, nsHeader.target);
}
}
}