1 /*
2  * Copyright (C) 2023 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 android.net.MacAddress
20 import android.util.Log
21 import com.android.net.module.util.Ipv6Utils
22 import com.android.net.module.util.NetworkStackConstants.ETHER_HEADER_LEN
23 import com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_TLLA
24 import com.android.net.module.util.NetworkStackConstants.NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED
25 import com.android.net.module.util.Struct
26 import com.android.net.module.util.structs.Icmpv6Header
27 import com.android.net.module.util.structs.Ipv6Header
28 import com.android.net.module.util.structs.LlaOption
29 import com.android.net.module.util.structs.NsHeader
30 import com.android.testutils.PacketReflector.IPV6_HEADER_LENGTH
31 import java.lang.IllegalArgumentException
32 import java.net.Inet6Address
33 import java.nio.ByteBuffer
34 
35 private const val NS_TYPE = 135.toShort()
36 
37 /**
38  * A class that can be used to reply to Neighbor Solicitation packets on a [TapPacketReader].
39  */
40 class NSResponder(
41     reader: TapPacketReader,
42     table: Map<Inet6Address, MacAddress>,
43     name: String = NSResponder::class.java.simpleName
44 ) : PacketResponder(reader, Icmpv6Filter(), name) {
45     companion object {
46         private val TAG = NSResponder::class.simpleName
47     }
48 
49     // Copy the map if not already immutable (toMap) to make sure it is not modified
50     private val table = table.toMap()
51 
replyToPacketnull52     override fun replyToPacket(packet: ByteArray, reader: TapPacketReader) {
53         if (packet.size < IPV6_HEADER_LENGTH) {
54             return
55         }
56         val buf = ByteBuffer.wrap(packet, ETHER_HEADER_LEN, packet.size - ETHER_HEADER_LEN)
57         val ipv6Header = parseOrLog(Ipv6Header::class.java, buf) ?: return
58         val icmpHeader = parseOrLog(Icmpv6Header::class.java, buf) ?: return
59         if (icmpHeader.type != NS_TYPE) {
60             return
61         }
62         val ns = parseOrLog(NsHeader::class.java, buf) ?: return
63         val replyMacAddr = table[ns.target] ?: return
64         val slla = parseOrLog(LlaOption::class.java, buf) ?: return
65         val requesterMac = slla.linkLayerAddress
66 
67         val tlla = LlaOption.build(ICMPV6_ND_OPTION_TLLA.toByte(), replyMacAddr)
68         reader.sendResponse(Ipv6Utils.buildNaPacket(
69             replyMacAddr /* srcMac */,
70             requesterMac /* dstMac */,
71             ns.target /* srcIp */,
72             ipv6Header.srcIp /* dstIp */,
73             NEIGHBOR_ADVERTISEMENT_FLAG_SOLICITED,
74             ns.target,
75             tlla))
76     }
77 
parseOrLognull78     private fun <T> parseOrLog(clazz: Class<T>, buf: ByteBuffer): T? where T : Struct {
79         return try {
80             Struct.parse(clazz, buf)
81         } catch (e: IllegalArgumentException) {
82             Log.e(TAG, "Invalid ${clazz.simpleName} in ICMPv6 packet", e)
83             null
84         }
85     }
86 }
87