Snap for 11274957 from 4443636fd203e989cd475ddd658632689dad935f to mainline-scheduling-release Change-Id: I2e6d1a2d8ec19141f31320bab21fb8038b705b55
diff --git a/Android.bp b/Android.bp index 4461c26..a7f57ae 100644 --- a/Android.bp +++ b/Android.bp
@@ -58,6 +58,7 @@ name: "NetworkStackNextEnableDefaults", enabled: true, } + // This is a placeholder comment to avoid merge conflicts // as the above target may have different "enabled" values // depending on the branch @@ -72,7 +73,7 @@ "framework-connectivity-t", "framework-statsd", "framework-wifi", - ] + ], } // Common defaults for NetworkStack integration tests, root tests and coverage tests @@ -85,7 +86,7 @@ java_defaults { name: "NetworkStackReleaseApiLevel", - defaults:["NetworkStackReleaseTargetSdk"], + defaults: ["NetworkStackReleaseTargetSdk"], sdk_version: module_34_version, libs: [ "framework-configinfrastructure", @@ -93,7 +94,7 @@ "framework-connectivity-t", "framework-statsd", "framework-wifi", - ] + ], } // Libraries for the API shims @@ -103,12 +104,12 @@ "androidx.annotation_annotation", "networkstack-aidl-latest", ], - static_libs : [ - "modules-utils-build_system" + static_libs: [ + "modules-utils-build_system", ], apex_available: [ "com.android.tethering", - "//apex_available:platform", // For InProcessNetworkStack + "//apex_available:platform", // For InProcessNetworkStack ], min_sdk_version: "30", } @@ -124,6 +125,9 @@ srcs: ["apishim/common/**/*.java"], sdk_version: "system_current", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Each level of the shims (29, 30, ...) is its own java_library compiled against the corresponding @@ -137,6 +141,9 @@ ], sdk_version: "system_29", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -153,6 +160,7 @@ visibility: ["//visibility:private"], lint: { strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", }, } @@ -176,6 +184,9 @@ ], sdk_version: "module_31", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -196,6 +207,9 @@ ], sdk_version: "module_33", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -217,6 +231,9 @@ ], sdk_version: module_34_version, visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Shims for APIs being added to the current development version of Android. These APIs are not @@ -228,7 +245,10 @@ // are part of the stable shims and scanned when generating jarjar rules. java_library { name: "NetworkStackApi35Shims", - defaults: ["NetworkStackShimsDefaults", "ConnectivityNextEnableDefaults"], + defaults: [ + "NetworkStackShimsDefaults", + "ConnectivityNextEnableDefaults", + ], srcs: [ "apishim/35/**/*.java", ], @@ -243,10 +263,13 @@ "framework-connectivity", "framework-connectivity-t.stubs.module_lib", "framework-tethering", - "android.net.ipsec.ike.stubs.module_lib" + "android.net.ipsec.ike.stubs.module_lib", ], sdk_version: "module_current", visibility: ["//visibility:private"], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // API current uses the API current shims directly. @@ -274,6 +297,9 @@ "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/tests:__subpackages__", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // API stable uses jarjar to rename the latest stable apishim package from @@ -281,7 +307,10 @@ // the networkstack code. java_library { name: "NetworkStackApiStableShims", - defaults: ["NetworkStackShimsDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackShimsDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: [ "NetworkStackShimsCommon", "NetworkStackApi29Shims", @@ -297,6 +326,9 @@ "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/tests:__subpackages__", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Common defaults for android libraries containing network stack code, used to compile variants of @@ -336,7 +368,7 @@ ], srcs: [ "src/**/*.java", - ":statslog-networkstack-java-gen-current" + ":statslog-networkstack-java-gen-current", ], static_libs: [ "NetworkStackApiCurrentShims", @@ -348,12 +380,18 @@ "//packages/modules/NetworkStack/tests/unit", "//packages/modules/NetworkStack/tests/integration", ], - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } android_library { name: "NetworkStackApiStableLib", - defaults: ["NetworkStackReleaseApiLevel", "NetworkStackAndroidLibraryDefaults"], + defaults: [ + "NetworkStackReleaseApiLevel", + "NetworkStackAndroidLibraryDefaults", + ], srcs: [ "src/**/*.java", ":statslog-networkstack-java-gen-stable", @@ -370,7 +408,10 @@ "//packages/modules/NetworkStack/tests/unit", "//packages/modules/NetworkStack/tests/integration", ], - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -396,6 +437,9 @@ "//packages/modules/Connectivity/Tethering/tests/integration", "//packages/modules/Connectivity/tests/cts/net", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_genrule { @@ -455,12 +499,18 @@ certificate: "platform", manifest: "AndroidManifest_InProcess.xml", // InProcessNetworkStack is a replacement for NetworkStack - overrides: ["NetworkStack", "NetworkStackNext"], + overrides: [ + "NetworkStack", + "NetworkStackNext", + ], // The InProcessNetworkStack goes together with the PlatformCaptivePortalLogin, which replaces // the default CaptivePortalLogin. required: [ "PlatformCaptivePortalLogin", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Pre-merge the AndroidManifest for NetworkStackNext, so that its manifest can be merged on top @@ -472,7 +522,10 @@ "ConnectivityNextEnableDefaults", ], static_libs: ["NetworkStackApiCurrentLib"], - manifest: "AndroidManifest.xml" + manifest: "AndroidManifest.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // NetworkStack build targeting the current API release, for testing on in-development SDK @@ -490,13 +543,19 @@ "privapp_whitelist_com.android.networkstack", ], updatable: true, - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } // Updatable network stack for finalized API android_app { name: "NetworkStack", - defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: ["NetworkStackApiStableLib"], certificate: "networkstack", manifest: "AndroidManifest.xml", @@ -504,13 +563,16 @@ "privapp_whitelist_com.android.networkstack", ], updatable: true, - lint: { strict_updatability_linting: true }, + lint: { + strict_updatability_linting: true, + baseline_filename: "lint-baseline.xml", + }, } cc_library_shared { name: "libnetworkstackutilsjni", srcs: [ - "jni/network_stack_utils_jni.cpp" + "jni/network_stack_utils_jni.cpp", ], header_libs: [ "bpf_headers", @@ -548,8 +610,8 @@ name: "statslog-networkstack-java-gen-current", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + - " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + - " --minApiLevel 30", + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30", out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], } @@ -557,12 +619,11 @@ name: "statslog-networkstack-java-gen-stable", tools: ["stats-log-api-gen"], cmd: "$(location stats-log-api-gen) --java $(out) --module network_stack" + - " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + - " --minApiLevel 30 --compileApiLevel 30", + " --javaPackage com.android.networkstack.metrics --javaClass NetworkStackStatsLog" + + " --minApiLevel 30 --compileApiLevel 30", out: ["com/android/networkstack/metrics/NetworkStackStatsLog.java"], } - version_code_networkstack_next = "300000000" version_code_networkstack_test = "999999999" @@ -570,21 +631,27 @@ name: "NetworkStackTestAndroidManifest", srcs: ["AndroidManifest.xml"], out: ["TestAndroidManifest.xml"], - cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" - + version_code_networkstack_test - + "\"/' $(in) > $(out)", + cmd: "sed -E 's/versionCode=\"[0-9]+\"/versionCode=\"" + + version_code_networkstack_test + + "\"/' $(in) > $(out)", visibility: ["//visibility:private"], } android_app { name: "TestNetworkStack", - defaults: ["NetworkStackAppDefaults", "NetworkStackReleaseApiLevel"], + defaults: [ + "NetworkStackAppDefaults", + "NetworkStackReleaseApiLevel", + ], static_libs: ["NetworkStackApiStableLib"], certificate: "networkstack", manifest: ":NetworkStackTestAndroidManifest", required: [ "privapp_whitelist_com.android.networkstack", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // When adding or modifying protos, the jarjar rules and possibly proguard rules need @@ -601,4 +668,7 @@ "networkstackprotos", ], defaults: ["NetworkStackReleaseApiLevel"], + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/common/networkstackclient/Android.bp b/common/networkstackclient/Android.bp index 07fd52e..060f0da 100644 --- a/common/networkstackclient/Android.bp +++ b/common/networkstackclient/Android.bp
@@ -221,6 +221,9 @@ "com.android.tethering", "com.android.wifi", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } java_library { @@ -265,4 +268,7 @@ "com.android.tethering", "com.android.wifi", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/src/android/net/apf/ApfFilter.java b/src/android/net/apf/ApfFilter.java index 2ef9c08..ee2990b 100644 --- a/src/android/net/apf/ApfFilter.java +++ b/src/android/net/apf/ApfFilter.java
@@ -1148,7 +1148,7 @@ // Generate code to match the packet bytes. if (section.type == PacketSection.Type.MATCH) { gen.addLoadImmediate(Register.R0, section.start); - gen.addJumpIfBytesNotEqual(Register.R0, + gen.addJumpIfBytesAtR0NotEqual( Arrays.copyOfRange(mPacket.array(), section.start, section.start + section.length), nextFilterLabel); @@ -1283,7 +1283,7 @@ final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // A NAT-T keepalive packet contains 1 byte payload with the value 0xff // Check payload length is 1 @@ -1298,11 +1298,11 @@ // Check that the ports match gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addAdd(ETH_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel); // Payload offset = R0 + UDP header length gen.addAdd(UDP_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); gen.addJump(mCountAndDropLabel); @@ -1398,7 +1398,7 @@ final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // Skip to the next filter if it's not zero-sized : // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 @@ -1420,7 +1420,7 @@ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); gen.addJump(mCountAndDropLabel); @@ -1522,7 +1522,7 @@ // Drop if not ARP IPv4. gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_NON_IPV4); - gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndDropLabel); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndDropLabel); // Drop if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); @@ -1538,7 +1538,7 @@ // Pass if non-broadcast reply. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); // Either a request, or a broadcast reply. gen.defineLabel(checkTargetIPv4); @@ -1552,7 +1552,7 @@ // and broadcast replies with a different target IPv4 address. gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); - gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel); + gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel); } maybeSetupCounter(gen, Counter.PASSED_ARP); @@ -1600,7 +1600,7 @@ gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET); // NOTE: Relies on R1 containing IPv4 header offset. gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter); + gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter); maybeSetupCounter(gen, Counter.PASSED_DHCP); gen.addJump(mCountAndPassLabel); @@ -1639,7 +1639,7 @@ // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); } @@ -1763,8 +1763,7 @@ // TODO: Drop only if they don't contain the address of on-link neighbours. final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix, - skipUnsolicitedMulticastNALabel); + gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); @@ -1821,8 +1820,7 @@ // Check it's L2 mDNS multicast address. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, - skipMdnsv4Filter); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter); // Checks it's IPv4. gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); @@ -1845,8 +1843,7 @@ // Checks it's L2 mDNS multicast address. // Relies on R0 containing the ethernet destination mac address offset. - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, - skipMdnsFilter); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter); // Checks it's IPv6. gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); @@ -1877,7 +1874,7 @@ for (int i = 0; i < mMdnsAllowList.size(); ++i) { final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i; final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i)); - gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck); + gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck); // QNAME matched gen.addJump(mDnsAcceptPacket); // QNAME not matched @@ -2023,7 +2020,7 @@ // Drop non-IP non-ARP broadcasts, pass the rest gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST); gen.addJump(mCountAndDropLabel);
diff --git a/src/android/net/apf/ApfGenerator.java b/src/android/net/apf/ApfGenerator.java index d038e96..1c08228 100644 --- a/src/android/net/apf/ApfGenerator.java +++ b/src/android/net/apf/ApfGenerator.java
@@ -16,6 +16,9 @@ package android.net.apf; +import static android.net.apf.ApfGenerator.Register.R0; +import static android.net.apf.ApfGenerator.Register.R1; + import androidx.annotation.NonNull; import com.android.internal.annotations.VisibleForTesting; @@ -65,7 +68,12 @@ OR(11), // Or, e.g. "or R0,5" SH(12), // Left shift, e.g, "sh R0, 5" or "sh R0, -5" (shifts right) LI(13), // Load immediate, e.g. "li R0,5" (immediate encoded as signed value) - JMP(14), // Jump, e.g. "jmp label" + // Jump, e.g. "jmp label" + // In APFv6, we use JMP(R=1) to encode the DATA instruction. DATA is executed as a jump. + // It tells how many bytes of the program regions are used to store the data and followed + // by the actual data bytes. + // "e.g. data 5, abcde" + JMP(14), JEQ(15), // Compare equal and branch, e.g. "jeq R0,5,label" JNE(16), // Compare not equal and branch, e.g. "jne R0,5,label" JGT(17), // Compare greater than and branch, e.g. "jgt R0,5,label" @@ -75,12 +83,17 @@ EXT(21), // Followed by immediate indicating ExtendedOpcodes. LDDW(22), // Load 4 bytes from data memory address (register + immediate): "lddw R0, [5]R1" STDW(23), // Store 4 bytes to data memory address (register + immediate): "stdw R0, [5]R1" - WRITE(24), // Write 1, 2 or 4 bytes imm to the output buffer, e.g. "WRITE 5" - // Copy the data from input packet or APF data region to output buffer. Register bit is - // used to specify the source of data copy: R=0 means copy from packet, R=1 means copy - // from APF data region. The source offset is encoded in the first imm and the copy length - // is encoded in the second imm. "e.g. MEMCOPY(R=0), 5, 5" - MEMCOPY(25); + // Write 1, 2 or 4 bytes immediate to the output buffer and auto-increment the pointer to + // write. e.g. "write 5" + WRITE(24), + // Copy bytes from input packet/APF program/data region to output buffer and + // auto-increment the output buffer pointer. + // Register bit is used to specify the source of data copy. + // R=0 means copy from packet. + // R=1 means copy from APF program/data region. + // The copy length is stored in (u8)imm2. + // e.g. "pktcopy 5, 5" "datacopy 5, 5" + PKTDATACOPY(25); final int value; @@ -97,21 +110,30 @@ NEG(33), // Negate, e.g. "neg R0" SWAP(34), // Swap, e.g. "swap R0,R1" MOVE(35), // Move, e.g. "move R0,R1" - ALLOC(36), // Allocate buffer, "e.g. ALLOC R0" + // Allocate writable output buffer. + // R=0, use register R0 to store the length. R=1, encode the length in the u16 int imm2. + // "e.g. allocate R0" + // "e.g. allocate 123" + ALLOCATE(36), // Transmit and deallocate the buffer (transmission can be delayed until the program // terminates). R=0 means discard the buffer, R=1 means transmit the buffer. // "e.g. trans" // "e.g. discard" TRANSMIT(37), DISCARD(37), - EWRITE1(38), // Write 1 byte from register to the output buffer, e.g. "EWRITE1 R0" - EWRITE2(39), // Write 2 bytes from register to the output buffer, e.g. "EWRITE2 R0" - EWRITE4(40), // Write 4 bytes from register to the output buffer, e.g. "EWRITE4 R0" - // Copy the data from input packet to output buffer. The source offset is encoded as [Rx - // + second imm]. The copy length is encoded in the third imm. "e.g. EPKTCOPY [R0 + 5], 5" + // Write 1, 2 or 4 byte value from register to the output buffer and auto-increment the + // output buffer pointer. + // e.g. "ewrite1 r0" + EWRITE1(38), + EWRITE2(39), + EWRITE4(40), + // Copy bytes from input packet/APF program/data region to output buffer and + // auto-increment the output buffer pointer. + // The copy src offset is stored in R0. + // when R=0, the copy length is stored in (u8)imm2. + // when R=1, the copy length is stored in R1. + // e.g. "pktcopy r0, 5", "pktcopy r0, r1", "datacopy r0, 5", "datacopy r0, r1" EPKTCOPY(41), - // Copy the data from APF data region to output buffer. The source offset is encoded as [Rx - // + second imm]. The copy length is encoded in the third imm. "e.g. EDATACOPY [R0 + 5], 5" EDATACOPY(42); final int value; @@ -144,43 +166,11 @@ private static class IntImmediate { public final IntImmediateType mImmediateType; - // TODO: remove mSigned and mImmSize fields in follow up CL - public final boolean mSigned; - public final int mImmSize; public final int mValue; - IntImmediate(int value, boolean signed) { - this(value, signed, calculateImmSize(value, signed)); - } - - // TODO: Remove this constructor in follow up CLs. - IntImmediate(int value, boolean signed, int size) { - mValue = value; - mSigned = signed; - mImmSize = size; - if (signed) { - mImmediateType = IntImmediateType.INDETERMINATE_SIZE_SIGNED; - } else { - mImmediateType = IntImmediateType.INDETERMINATE_SIZE_UNSIGNED; - } - } - IntImmediate(int value, IntImmediateType type) { mImmediateType = type; mValue = value; - // The following logic for initialize mSigned and mImmSize could be removed after we - // remove those two variable - switch (type) { - case INDETERMINATE_SIZE_SIGNED: - case SIGNED_8: - case SIGNED_BE16: - case SIGNED_BE32: - mSigned = true; - break; - default: - mSigned = false; - } - mImmSize = calculateImmSize(value, mSigned); } private int calculateIndeterminateSize() { @@ -226,51 +216,63 @@ getEncodingSize(immFieldSize)); } - public static IntImmediate newSignedIndeterminate(int imm) { + public static IntImmediate newSigned(int imm) { return new IntImmediate(imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED); } - public static IntImmediate newUnsignedIndeterminate(long imm) { + public static IntImmediate newUnsigned(long imm) { // upperBound is 2^32 - 1 - checkRange("Unsigned Indeterminate IMM", imm, 0 /* lowerBound */, + checkRange("Unsigned IMM", imm, 0 /* lowerBound */, 4294967295L /* upperBound */); return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED); } - public static IntImmediate newSigned8(byte imm) { - checkRange("Signed8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE); + public static IntImmediate newTwosComplementUnsigned(long imm) { + checkRange("Unsigned TwosComplement IMM", imm, Integer.MIN_VALUE, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_UNSIGNED); + } + + public static IntImmediate newTwosComplementSigned(long imm) { + checkRange("Signed TwosComplement IMM", imm, Integer.MIN_VALUE, + 4294967295L /* upperBound */); + return new IntImmediate((int) imm, IntImmediateType.INDETERMINATE_SIZE_SIGNED); + } + + public static IntImmediate newS8(byte imm) { + checkRange("S8 IMM", imm, Byte.MIN_VALUE, Byte.MAX_VALUE); return new IntImmediate(imm, IntImmediateType.SIGNED_8); } - public static IntImmediate newUnsigned8(int imm) { - checkRange("Unsigned8 IMM", imm, 0, 255); + public static IntImmediate newU8(int imm) { + checkRange("U8 IMM", imm, 0, 255); return new IntImmediate(imm, IntImmediateType.UNSIGNED_8); } - public static IntImmediate newSignedBe16(short imm) { + public static IntImmediate newS16(short imm) { return new IntImmediate(imm, IntImmediateType.SIGNED_BE16); } - public static IntImmediate newUnsignedBe16(int imm) { - checkRange("UnsignedBe16 IMM", imm, 0, 65535); + public static IntImmediate newU16(int imm) { + checkRange("U16 IMM", imm, 0, 65535); return new IntImmediate(imm, IntImmediateType.UNSIGNED_BE16); } - public static IntImmediate newSignedBe32(int imm) { + public static IntImmediate newS32(int imm) { return new IntImmediate(imm, IntImmediateType.SIGNED_BE32); } - public static IntImmediate newUnsignedBe32(long imm) { + public static IntImmediate newU32(long imm) { // upperBound is 2^32 - 1 - checkRange("UnsignedBe32 IMM", imm, 0 /* lowerBound */, + checkRange("U32 IMM", imm, 0 /* lowerBound */, 4294967295L /* upperBound */); return new IntImmediate((int) imm, IntImmediateType.UNSIGNED_BE32); } @Override public String toString() { - return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mSigned=" + mSigned - + ", mImmSize=" + mImmSize + ", mValue=" + mValue + '}'; + return "IntImmediate{" + "mImmediateType=" + mImmediateType + ", mValue=" + mValue + + '}'; } } @@ -280,6 +282,7 @@ public final List<IntImmediate> mIntImms = new ArrayList<>(); // When mOpcode is a jump: private int mTargetLabelSize; + private int mLenFieldOverride = -1; private String mTargetLabel; // When mOpcode == Opcodes.LABEL: private String mLabel; @@ -292,27 +295,81 @@ mRegister = (byte) register.value; } + Instruction(ExtendedOpcodes extendedOpcodes, Register register) { + this(Opcodes.EXT, register); + addUnsigned(extendedOpcodes.value); + } + + Instruction(ExtendedOpcodes extendedOpcodes, int slot, Register register) + throws IllegalInstructionException { + this(Opcodes.EXT, register); + if (slot < 0 || slot >= MEMORY_SLOTS) { + throw new IllegalInstructionException("illegal memory slot number: " + slot); + } + addUnsigned(extendedOpcodes.value + slot); + } + Instruction(Opcodes opcode) { - this(opcode, Register.R0); + this(opcode, R0); } - void addUnsignedImm(int imm) { - addImm(new IntImmediate(imm, false)); + Instruction(ExtendedOpcodes extendedOpcodes) { + this(extendedOpcodes, R0); } - void addUnsignedImm(int imm, byte size) { - addImm(new IntImmediate(imm, false, size)); + Instruction addSigned(int imm) { + mIntImms.add(IntImmediate.newSigned(imm)); + return this; } - void addSignedImm(int imm) { - addImm(new IntImmediate(imm, true)); + Instruction addUnsigned(int imm) { + mIntImms.add(IntImmediate.newUnsigned(imm)); + return this; } - void addImm(IntImmediate imm) { - mIntImms.add(imm); + + Instruction addTwosCompSigned(int imm) { + mIntImms.add(IntImmediate.newTwosComplementSigned(imm)); + return this; } - void setLabel(String label) throws IllegalInstructionException { + + Instruction addTwosCompUnsigned(int imm) { + mIntImms.add(IntImmediate.newTwosComplementUnsigned(imm)); + return this; + } + + Instruction addS8(byte imm) { + mIntImms.add(IntImmediate.newS8(imm)); + return this; + } + + Instruction addU8(int imm) { + mIntImms.add(IntImmediate.newU8(imm)); + return this; + } + + Instruction addS16(short imm) { + mIntImms.add(IntImmediate.newS16(imm)); + return this; + } + + Instruction addU16(int imm) { + mIntImms.add(IntImmediate.newU16(imm)); + return this; + } + + Instruction addS32(int imm) { + mIntImms.add(IntImmediate.newS32(imm)); + return this; + } + + Instruction addU32(long imm) { + mIntImms.add(IntImmediate.newU32(imm)); + return this; + } + + Instruction setLabel(String label) throws IllegalInstructionException { if (mLabels.containsKey(label)) { throw new IllegalInstructionException("duplicate label " + label); } @@ -321,18 +378,23 @@ } mLabel = label; mLabels.put(label, this); + return this; } - void setTargetLabel(String label) { + Instruction setTargetLabel(String label) { mTargetLabel = label; mTargetLabelSize = 4; // May shrink later on in generate(). + return this; } - void setBytesImm(byte[] bytes) { - if (mOpcode != Opcodes.JNEBS.value) { - throw new IllegalStateException("adding compare bytes to non-JNEBS instruction"); - } + Instruction overrideLenField(int size) { + mLenFieldOverride = size; + return this; + } + + Instruction setBytesImm(byte[] bytes) { mBytesImm = bytes; + return this; } /** @@ -377,6 +439,21 @@ * Assemble value for instruction size field. */ private int generateImmSizeField() { + // If we already know the size the length field, just use it + switch (mLenFieldOverride) { + case -1: + break; + case 1: + return 1; + case 2: + return 2; + case 4: + return 3; + default: + throw new IllegalStateException( + "mLenFieldOverride has invalid value: " + mLenFieldOverride); + } + // Otherwise, calculate int immSize = calculateRequiredIndeterminateSize(); // Encode size field to fit in 2 bits: 0->0, 1->1, 2->2, 3->4. return immSize == 4 ? 3 : immSize; @@ -522,6 +599,7 @@ // This version number syncs up with APF_VERSION in hardware/google/apf/apf_interpreter.h public static final int MIN_APF_VERSION = 2; public static final int MIN_APF_VERSION_IN_DEV = 5; + public static final int APF_VERSION_4 = 4; private final ArrayList<Instruction> mInstructions = new ArrayList<Instruction>(); @@ -555,11 +633,12 @@ } } - private void addInstruction(Instruction instruction) { + private ApfGenerator append(Instruction instruction) { if (mGenerated) { throw new IllegalStateException("Program already generated"); } mInstructions.add(instruction); + return this; } /** @@ -578,53 +657,38 @@ * In this case "next_filter" may not have any generated code associated with it. */ public ApfGenerator defineLabel(String name) throws IllegalInstructionException { - Instruction instruction = new Instruction(Opcodes.LABEL); - instruction.setLabel(name); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.LABEL).setLabel(name)); } /** * Add an unconditional jump instruction to the end of the program. */ public ApfGenerator addJump(String target) { - Instruction instruction = new Instruction(Opcodes.JMP); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.JMP).setTargetLabel(target)); } /** * Add an instruction to the end of the program to load the byte at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad8(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDB, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad8(Register r, int ofs) { + return append(new Instruction(Opcodes.LDB, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to load 16-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad16(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDH, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad16(Register r, int ofs) { + return append(new Instruction(Opcodes.LDH, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to load 32-bits at offset {@code offset} * bytes from the beginning of the packet into {@code register}. */ - public ApfGenerator addLoad32(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDW, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad32(Register r, int ofs) { + return append(new Instruction(Opcodes.LDW, r).addUnsigned(ofs)); } /** @@ -632,11 +696,8 @@ * {@code register}. The offset of the loaded byte from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad8Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDBX, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad8Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDBX, r).addUnsigned(ofs)); } /** @@ -644,11 +705,8 @@ * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad16Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDHX, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad16Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDHX, r).addUnsigned(ofs)); } /** @@ -656,109 +714,81 @@ * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is * the sum of {@code offset} and the value in register R1. */ - public ApfGenerator addLoad32Indexed(Register register, int offset) { - Instruction instruction = new Instruction(Opcodes.LDWX, register); - instruction.addUnsignedImm(offset); - addInstruction(instruction); - return this; + public ApfGenerator addLoad32Indexed(Register r, int ofs) { + return append(new Instruction(Opcodes.LDWX, r).addUnsigned(ofs)); } /** * Add an instruction to the end of the program to add {@code value} to register R0. */ - public ApfGenerator addAdd(int value) { - Instruction instruction = new Instruction(Opcodes.ADD); - instruction.addUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addAdd(int val) { + return append(new Instruction(Opcodes.ADD).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to multiply register R0 by {@code value}. */ - public ApfGenerator addMul(int value) { - Instruction instruction = new Instruction(Opcodes.MUL); - instruction.addUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addMul(int val) { + return append(new Instruction(Opcodes.MUL).addUnsigned(val)); } /** * Add an instruction to the end of the program to divide register R0 by {@code value}. */ - public ApfGenerator addDiv(int value) { - Instruction instruction = new Instruction(Opcodes.DIV); - instruction.addUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addDiv(int val) { + return append(new Instruction(Opcodes.DIV).addUnsigned(val)); } /** * Add an instruction to the end of the program to logically and register R0 with {@code value}. */ - public ApfGenerator addAnd(int value) { - Instruction instruction = new Instruction(Opcodes.AND); - instruction.addUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addAnd(int val) { + return append(new Instruction(Opcodes.AND).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to logically or register R0 with {@code value}. */ - public ApfGenerator addOr(int value) { - Instruction instruction = new Instruction(Opcodes.OR); - instruction.addUnsignedImm(value); - addInstruction(instruction); - return this; + public ApfGenerator addOr(int val) { + return append(new Instruction(Opcodes.OR).addTwosCompUnsigned(val)); } /** * Add an instruction to the end of the program to shift left register R0 by {@code value} bits. */ - public ApfGenerator addLeftShift(int value) { - Instruction instruction = new Instruction(Opcodes.SH); - instruction.addSignedImm(value); - addInstruction(instruction); - return this; + // TODO: consider whether should change the argument type to byte + public ApfGenerator addLeftShift(int val) { + return append(new Instruction(Opcodes.SH).addSigned(val)); } /** * Add an instruction to the end of the program to shift right register R0 by {@code value} * bits. */ - public ApfGenerator addRightShift(int value) { - Instruction instruction = new Instruction(Opcodes.SH); - instruction.addSignedImm(-value); - addInstruction(instruction); - return this; + // TODO: consider whether should change the argument type to byte + public ApfGenerator addRightShift(int val) { + return append(new Instruction(Opcodes.SH).addSigned(-val)); } /** * Add an instruction to the end of the program to add register R1 to register R0. */ public ApfGenerator addAddR1() { - Instruction instruction = new Instruction(Opcodes.ADD, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.ADD, R1)); } /** * Add an instruction to the end of the program to multiply register R0 by register R1. */ public ApfGenerator addMulR1() { - Instruction instruction = new Instruction(Opcodes.MUL, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.MUL, R1)); } /** * Add an instruction to the end of the program to divide register R0 by register R1. */ public ApfGenerator addDivR1() { - Instruction instruction = new Instruction(Opcodes.DIV, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.DIV, R1)); } /** @@ -766,9 +796,7 @@ * and store the result back into register R0. */ public ApfGenerator addAndR1() { - Instruction instruction = new Instruction(Opcodes.AND, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.AND, R1)); } /** @@ -776,9 +804,7 @@ * and store the result back into register R0. */ public ApfGenerator addOrR1() { - Instruction instruction = new Instruction(Opcodes.OR, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.OR, R1)); } /** @@ -786,111 +812,77 @@ * register R1. */ public ApfGenerator addLeftShiftR1() { - Instruction instruction = new Instruction(Opcodes.SH, Register.R1); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.SH, R1)); } /** * Add an instruction to the end of the program to move {@code value} into {@code register}. */ public ApfGenerator addLoadImmediate(Register register, int value) { - Instruction instruction = new Instruction(Opcodes.LI, register); - instruction.addSignedImm(value); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.LI, register).addSigned(value)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals {@code value}. */ - public ApfGenerator addJumpIfR0Equals(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JEQ); - instruction.addUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0Equals(int val, String tgt) { + return append(new Instruction(Opcodes.JEQ).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal {@code value}. */ - public ApfGenerator addJumpIfR0NotEquals(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JNE); - instruction.addUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0NotEquals(int val, String tgt) { + return append(new Instruction(Opcodes.JNE).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than {@code value}. */ - public ApfGenerator addJumpIfR0GreaterThan(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JGT); - instruction.addUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0GreaterThan(int val, String tgt) { + return append(new Instruction(Opcodes.JGT).addUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is less than {@code value}. */ - public ApfGenerator addJumpIfR0LessThan(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JLT); - instruction.addUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0LessThan(int val, String tgt) { + return append(new Instruction(Opcodes.JLT).addUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in {@code value}. */ - public ApfGenerator addJumpIfR0AnyBitsSet(int value, String target) { - Instruction instruction = new Instruction(Opcodes.JSET); - instruction.addUnsignedImm(value); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0AnyBitsSet(int val, String tgt) { + return append(new Instruction(Opcodes.JSET).addTwosCompUnsigned(val).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value equals register R1's value. */ - public ApfGenerator addJumpIfR0EqualsR1(String target) { - Instruction instruction = new Instruction(Opcodes.JEQ, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0EqualsR1(String tgt) { + return append(new Instruction(Opcodes.JEQ, R1).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value does not equal register R1's value. */ - public ApfGenerator addJumpIfR0NotEqualsR1(String target) { - Instruction instruction = new Instruction(Opcodes.JNE, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0NotEqualsR1(String tgt) { + return append(new Instruction(Opcodes.JNE, R1).setTargetLabel(tgt)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value is greater than register R1's value. */ - public ApfGenerator addJumpIfR0GreaterThanR1(String target) { - Instruction instruction = new Instruction(Opcodes.JGT, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0GreaterThanR1(String tgt) { + return append(new Instruction(Opcodes.JGT, R1).setTargetLabel(tgt)); } /** @@ -898,133 +890,104 @@ * value is less than register R1's value. */ public ApfGenerator addJumpIfR0LessThanR1(String target) { - Instruction instruction = new Instruction(Opcodes.JLT, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + return append(new Instruction(Opcodes.JLT, R1).setTargetLabel(target)); } /** * Add an instruction to the end of the program to jump to {@code target} if register R0's * value has any bits set that are also set in R1's value. */ - public ApfGenerator addJumpIfR0AnyBitsSetR1(String target) { - Instruction instruction = new Instruction(Opcodes.JSET, Register.R1); - instruction.setTargetLabel(target); - addInstruction(instruction); - return this; + public ApfGenerator addJumpIfR0AnyBitsSetR1(String tgt) { + return append(new Instruction(Opcodes.JSET, R1).setTargetLabel(tgt)); } /** - * Add an instruction to the end of the program to jump to {@code target} if the bytes of the - * packet at an offset specified by {@code register} don't match {@code bytes}, {@code register} - * must be R0. + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code register} don't match {@code bytes} + * R=0 means check for not equal */ - public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target) + public ApfGenerator addJumpIfBytesAtR0NotEqual(byte[] bytes, String tgt) { + return append(new Instruction(Opcodes.JNEBS).addUnsigned( + bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); + } + + /** + * Add an instruction to the end of the program to jump to {@code tgt} if the bytes of the + * packet at an offset specified by {@code register} match {@code bytes} + * R=1 means check for equal. + */ + public ApfGenerator addJumpIfBytesAtR0Equal(byte[] bytes, String tgt) throws IllegalInstructionException { - if (register == Register.R1) { - throw new IllegalInstructionException("JNEBS fails with R1"); - } - Instruction instruction = new Instruction(Opcodes.JNEBS, register); - instruction.addUnsignedImm(bytes.length); - instruction.setTargetLabel(target); - instruction.setBytesImm(bytes); - addInstruction(instruction); - return this; + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.JNEBS, R1).addUnsigned( + bytes.length).setTargetLabel(tgt).setBytesImm(bytes)); } /** * Add an instruction to the end of the program to load memory slot {@code slot} into * {@code register}. */ - public ApfGenerator addLoadFromMemory(Register register, int slot) + public ApfGenerator addLoadFromMemory(Register r, int slot) throws IllegalInstructionException { - if (slot < 0 || slot > (MEMORY_SLOTS - 1)) { - throw new IllegalInstructionException("illegal memory slot number: " + slot); - } - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.LDM.value + slot); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.LDM, slot, r)); } /** * Add an instruction to the end of the program to store {@code register} into memory slot * {@code slot}. */ - public ApfGenerator addStoreToMemory(Register register, int slot) + public ApfGenerator addStoreToMemory(Register r, int slot) throws IllegalInstructionException { - if (slot < 0 || slot > (MEMORY_SLOTS - 1)) { - throw new IllegalInstructionException("illegal memory slot number: " + slot); - } - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.STM.value + slot); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.STM, slot, r)); } /** * Add an instruction to the end of the program to logically not {@code register}. */ - public ApfGenerator addNot(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.NOT.value); - addInstruction(instruction); - return this; + public ApfGenerator addNot(Register r) { + return append(new Instruction(ExtendedOpcodes.NOT, r)); } /** * Add an instruction to the end of the program to negate {@code register}. */ - public ApfGenerator addNeg(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.NEG.value); - addInstruction(instruction); - return this; + public ApfGenerator addNeg(Register r) { + return append(new Instruction(ExtendedOpcodes.NEG, r)); } /** * Add an instruction to swap the values in register R0 and register R1. */ public ApfGenerator addSwap() { - Instruction instruction = new Instruction(Opcodes.EXT); - instruction.addUnsignedImm(ExtendedOpcodes.SWAP.value); - addInstruction(instruction); - return this; + return append(new Instruction(ExtendedOpcodes.SWAP)); } /** * Add an instruction to the end of the program to move the value into * {@code register} from the other register. */ - public ApfGenerator addMove(Register register) { - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.MOVE.value); - addInstruction(instruction); - return this; + public ApfGenerator addMove(Register r) { + return append(new Instruction(ExtendedOpcodes.MOVE, r)); } /** * Add an instruction to the end of the program to let the program immediately return PASS. */ - public ApfGenerator addPass() throws IllegalInstructionException { - Instruction instruction = new Instruction(Opcodes.PASS, Register.R0); - addInstruction(instruction); - return this; + public ApfGenerator addPass() { + // PASS requires using R0 because it shares opcode with DROP + return append(new Instruction(Opcodes.PASS)); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return PASS. */ - public ApfGenerator addCountAndPass(int counterNumber) throws IllegalInstructionException { + public ApfGenerator addCountAndPass(int cnt) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); - checkRange("CounterNumber", counterNumber /* value */, 1 /* lowerBound */, + checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); - Instruction instruction = new Instruction(Opcodes.PASS, Register.R0); - instruction.addUnsignedImm(counterNumber); - addInstruction(instruction); - return this; + // PASS requires using R0 because it shares opcode with DROP + return append(new Instruction(Opcodes.PASS).addUnsigned(cnt)); } /** @@ -1032,36 +995,52 @@ */ public ApfGenerator addDrop() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); - Instruction instruction = new Instruction(Opcodes.DROP, Register.R1); - addInstruction(instruction); - return this; + // DROP requires using R1 because it shares opcode with PASS + return append(new Instruction(Opcodes.DROP, R1)); } /** * Add an instruction to the end of the program to increment the counter value and * immediately return DROP. */ - public ApfGenerator addCountAndDrop(int counterNumber) throws IllegalInstructionException { + public ApfGenerator addCountAndDrop(int cnt) throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); - checkRange("CounterNumber", counterNumber /* value */, 1 /* lowerBound */, + checkRange("CounterNumber", cnt /* value */, 1 /* lowerBound */, 1000 /* upperBound */); - Instruction instruction = new Instruction(Opcodes.DROP, Register.R1); - instruction.addUnsignedImm(counterNumber); - addInstruction(instruction); - return this; + // DROP requires using R1 because it shares opcode with PASS + return append(new Instruction(Opcodes.DROP, R1).addUnsigned(cnt)); + } + + /** + * Add an instruction to the end of the program to call the apf_allocate_buffer() function. + * Buffer length to be allocated is stored in register 0. + */ + public ApfGenerator addAllocateR0() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.ALLOCATE)); } /** * Add an instruction to the end of the program to call the apf_allocate_buffer() function. * - * @param register the register value contains the buffer size. + * @param size the buffer length to be allocated. */ - public ApfGenerator addAlloc(Register register) throws IllegalInstructionException { - requireApfVersion(5); - Instruction instruction = new Instruction(Opcodes.EXT, register); - instruction.addUnsignedImm(ExtendedOpcodes.ALLOC.value); - addInstruction(instruction); - return this; + public ApfGenerator addAllocate(int size) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + // R1 means the extra be16 immediate is present + return append(new Instruction(ExtendedOpcodes.ALLOCATE, R1).addU16(size)); + } + + /** + * Add an instruction to the beginning of the program to reserve the data region. + * @param data the actual data byte + */ + public ApfGenerator addData(byte[] data) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + if (!mInstructions.isEmpty()) { + throw new IllegalInstructionException("data instruction has to come first"); + } + return append(new Instruction(Opcodes.JMP, R1).addUnsigned(data.length).setBytesImm(data)); } /** @@ -1069,10 +1048,8 @@ */ public ApfGenerator addTransmit() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); - Instruction instruction = new Instruction(Opcodes.EXT, Register.R0); - instruction.addUnsignedImm(ExtendedOpcodes.TRANSMIT.value); - addInstruction(instruction); - return this; + // TRANSMIT requires using R0 because it shares opcode with DISCARD + return append(new Instruction(ExtendedOpcodes.TRANSMIT)); } /** @@ -1080,176 +1057,141 @@ */ public ApfGenerator addDiscard() throws IllegalInstructionException { requireApfVersion(MIN_APF_VERSION_IN_DEV); - Instruction instruction = new Instruction(Opcodes.EXT, Register.R1); - instruction.addUnsignedImm(ExtendedOpcodes.DISCARD.value); - addInstruction(instruction); - return this; + // DISCARD requires using R1 because it shares opcode with TRANSMIT + return append(new Instruction(ExtendedOpcodes.DISCARD, R1)); } - // TODO: add back when support WRITE opcode -// /** -// * Add an instruction to the end of the program to write 1, 2 or 4 bytes value to output -// buffer. -// * -// * @param value the value to write -// * @param size the size of the value -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when size is not 1, 2 or 4 -// */ -// public ApfGenerator addWrite(int value, byte size) throws IllegalInstructionException { -// requireApfVersion(5); -// if (!(size == 1 || size == 2 || size == 4)) { -// throw new IllegalInstructionException("length field must be 1, 2 or 4"); -// } -// if (size < calculateImmSize(value, false)) { -// throw new IllegalInstructionException( -// String.format("the value %d is unfit into size: %d", value, size)); -// } -// Instruction instruction = new Instruction(Opcodes.WRITE); -// instruction.addUnsignedImm(value, size); -// addInstruction(instruction); -// return this; -// } - - // TODO: add back when support EWRITE opcode -// /** -// * Add an instruction to the end of the program to write 1, 2 or 4 bytes value from register -// * to output buffer. -// * -// * @param register the register contains the value to be written -// * @param size the size of the value -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when size is not 1, 2 or 4 -// */ -// public ApfGenerator addWrite(Register register, byte size) -// throws IllegalInstructionException { -// requireApfVersion(5); -// if (!(size == 1 || size == 2 || size == 4)) { -// throw new IllegalInstructionException( -// "length field must be 1, 2 or 4"); -// } -// Instruction instruction = new Instruction(Opcodes.EXT, register); -// if (size == 1) { -// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE1.value); -// } else if (size == 2) { -// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE2.value); -// } else { -// instruction.addUnsignedImm(ExtendedOpcodes.EWRITE4.value); -// } -// addInstruction(instruction); -// return this; -// } - - // TODO: add back when support PKTCOPY/DATACOPY opcode -// /** -// * Add an instruction to the end of the program to copy data from APF data region to output -// * buffer. -// * -// * @param srcOffset the offset inside the APF data region for where to start copy -// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at -// * one time. -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when imm size is incorrectly set. -// */ -// public ApfGenerator addDataCopy(int srcOffset, int length) -// throws IllegalInstructionException { -// return addMemCopy(srcOffset, length, Register.R1); -// } -// -// /** -// * Add an instruction to the end of the program to copy data from input packet to output -// buffer. -// * -// * @param srcOffset the offset inside the input packet for where to start copy -// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at -// * one time. -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when imm size is incorrectly set. -// */ -// public ApfGenerator addPacketCopy(int srcOffset, int length) -// throws IllegalInstructionException { -// return addMemCopy(srcOffset, length, Register.R0); -// } -// -// private ApfGenerator addMemCopy(int srcOffset, int length, Register register) -// throws IllegalInstructionException { -// requireApfVersion(5); -// checkCopyLength(length); -// checkCopyOffset(srcOffset); -// Instruction instruction = new Instruction(Opcodes.MEMCOPY, register); -// // if the offset == 0, it should still be encoded with 1 byte size. -// if (srcOffset == 0) { -// instruction.addUnsignedImm(srcOffset, (byte) 1 /* size */); -// } else { -// instruction.addUnsignedImm(srcOffset); -// } -// instruction.addUnsignedImm(length, (byte) 1 /* size */); -// addInstruction(instruction); -// return this; -// } -// TODO: add back when support EPKTCOPY/EDATACOPY opcode -// /** -// * Add an instruction to the end of the program to copy data from APF data region to output -// * buffer. -// * -// * @param register the register that stored the base offset value. -// * @param relativeOffset the offset inside the APF data region for where to start copy -// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at -// * one time. -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when imm size is incorrectly set. -// */ -// public ApfGenerator addDataCopy(Register register, int relativeOffset, int length) -// throws IllegalInstructionException { -// return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EDATACOPY.value); -// } -// -// /** -// * Add an instruction to the end of the program to copy data from input packet to output -// buffer. -// * -// * @param register the register that stored the base offset value. -// * @param relativeOffset the offset inside the input packet for where to start copy -// * @param length the length of bytes needed to be copied, only <= 255 bytes can be copied at -// * one time. -// * @return the ApfGenerator object -// * @throws IllegalInstructionException throws when imm size is incorrectly set. -// */ -// public ApfGenerator addPacketCopy(Register register, int relativeOffset, int length) -// throws IllegalInstructionException { -// return addMemcopy(register, relativeOffset, length, ExtendedOpcodes.EPKTCOPY.value); -// } -// -// private ApfGenerator addMemcopy(Register register, int relativeOffset, int length, int opcode) -// throws IllegalInstructionException { -// requireApfVersion(5); -// checkCopyLength(length); -// checkCopyOffset(relativeOffset); -// Instruction instruction = new Instruction(Opcodes.EXT, register); -// instruction.addUnsignedImm(opcode); -// // if the offset == 0, it should still be encoded with 1 byte size. -// if (relativeOffset == 0) { -// instruction.addUnsignedImm(relativeOffset, (byte) 1 /* size */); -// } else { -// instruction.addUnsignedImm(relativeOffset); -// } -// instruction.addUnsignedImm(length, (byte) 1 /* size */); -// addInstruction(instruction); -// return this; -// } - - private void checkCopyLength(int length) { - if (length < 0 || length > 255) { - throw new IllegalArgumentException( - "copy length must between 0 to 255, length: " + length); - } + /** + * Add an instruction to the end of the program to write 1 byte value to output buffer. + */ + public ApfGenerator addWriteU8(int val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(1).addU8(val)); } - private void checkCopyOffset(int offset) { - if (offset < 0) { - throw new IllegalArgumentException( - "offset must be non less than zero, offset: " + offset); - } + /** + * Add an instruction to the end of the program to write 2 bytes value to output buffer. + */ + public ApfGenerator addWriteU16(int val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(2).addU16(val)); + } + + /** + * Add an instruction to the end of the program to write 4 bytes value to output buffer. + */ + public ApfGenerator addWriteU32(long val) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.WRITE).overrideLenField(4).addU32(val)); + } + + /** + * Add an instruction to the end of the program to write 1 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU8(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE1, reg)); + } + + /** + * Add an instruction to the end of the program to write 2 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU16(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE2, reg)); + } + + /** + * Add an instruction to the end of the program to write 4 byte value from register to output + * buffer. + */ + public ApfGenerator addWriteU32(Register reg) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EWRITE4, reg)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * + * @param src the offset inside the APF program/data region for where to start copy. + * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at + * one time. + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopy(int src, int len) + throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R1).addUnsigned(src).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * + * @param src the offset inside the input packet for where to start copy. + * @param len the length of bytes needed to be copied, only <= 255 bytes can be copied at + * one time. + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopy(int src, int len) + throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(Opcodes.PKTDATACOPY, R0).addUnsigned(src).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * + * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopyFromR0(int len) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EDATACOPY).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * + * @param len the number of bytes to be copied, only <= 255 bytes can be copied at once. + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopyFromR0(int len) throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EPKTCOPY).addU8(len)); + } + + /** + * Add an instruction to the end of the program to copy data from APF program/data region to + * output buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * Copy length is stored in R1. + * + * @return the ApfGenerator object + */ + public ApfGenerator addDataCopyFromR0LenR1() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EDATACOPY, R1)); + } + + /** + * Add an instruction to the end of the program to copy data from input packet to output + * buffer and auto-increment the output buffer pointer. + * Source offset is stored in R0. + * Copy length is stored in R1. + * + * @return the ApfGenerator object + */ + public ApfGenerator addPacketCopyFromR0LenR1() throws IllegalInstructionException { + requireApfVersion(MIN_APF_VERSION_IN_DEV); + return append(new Instruction(ExtendedOpcodes.EPKTCOPY, R1)); } private static void checkRange(@NonNull String variableName, long value, long lowerBound, @@ -1266,30 +1208,24 @@ * Add an instruction to the end of the program to load 32 bits from the data memory into * {@code register}. The source address is computed by adding the signed immediate * @{code offset} to the other register. - * Requires APF v3 or greater. + * Requires APF v4 or greater. */ - public ApfGenerator addLoadData(Register destinationRegister, int offset) + public ApfGenerator addLoadData(Register dst, int ofs) throws IllegalInstructionException { - requireApfVersion(3); - Instruction instruction = new Instruction(Opcodes.LDDW, destinationRegister); - instruction.addSignedImm(offset); - addInstruction(instruction); - return this; + requireApfVersion(APF_VERSION_4); + return append(new Instruction(Opcodes.LDDW, dst).addSigned(ofs)); } /** * Add an instruction to the end of the program to store 32 bits from {@code register} into the * data memory. The destination address is computed by adding the signed immediate * @{code offset} to the other register. - * Requires APF v3 or greater. + * Requires APF v4 or greater. */ - public ApfGenerator addStoreData(Register sourceRegister, int offset) + public ApfGenerator addStoreData(Register src, int ofs) throws IllegalInstructionException { - requireApfVersion(3); - Instruction instruction = new Instruction(Opcodes.STDW, sourceRegister); - instruction.addSignedImm(offset); - addInstruction(instruction); - return this; + requireApfVersion(APF_VERSION_4); + return append(new Instruction(Opcodes.STDW, src).addSigned(ofs)); } /**
diff --git a/src/android/net/apf/DnsUtils.java b/src/android/net/apf/DnsUtils.java index 0300d34..5bd2515 100644 --- a/src/android/net/apf/DnsUtils.java +++ b/src/android/net/apf/DnsUtils.java
@@ -301,7 +301,7 @@ gen.addJumpIfR0NotEquals(label.length(), noMatchLabel); gen.addLoadFromMemory(R0, SLOT_CURRENT_PARSE_OFFSET); gen.addAdd(1); - gen.addJumpIfBytesNotEqual(R0, label.getBytes(), noMatchLabel); + gen.addJumpIfBytesAtR0NotEqual(label.getBytes(), noMatchLabel); // Prep offset of next label. gen.addAdd(label.length());
diff --git a/src/android/net/apf/LegacyApfFilter.java b/src/android/net/apf/LegacyApfFilter.java index cc09856..6b93d89 100644 --- a/src/android/net/apf/LegacyApfFilter.java +++ b/src/android/net/apf/LegacyApfFilter.java
@@ -982,7 +982,7 @@ // Generate code to match the packet bytes. if (section.type == PacketSection.Type.MATCH) { gen.addLoadImmediate(Register.R0, section.start); - gen.addJumpIfBytesNotEqual(Register.R0, + gen.addJumpIfBytesAtR0NotEqual( Arrays.copyOfRange(mPacket.array(), section.start, section.start + section.length), nextFilterLabel); @@ -1065,7 +1065,7 @@ final String nextFilterLabel = "natt_keepalive_filter" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // A NAT-T keepalive packet contains 1 byte payload with the value 0xff // Check payload length is 1 @@ -1080,11 +1080,11 @@ // Check that the ports match gen.addLoadFromMemory(Register.R0, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addAdd(ETH_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPortFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortFingerprint, nextFilterLabel); // Payload offset = R0 + UDP header length gen.addAdd(UDP_HEADER_LEN); - gen.addJumpIfBytesNotEqual(Register.R0, mPayload, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPayload, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_NATT_KEEPALIVE); gen.addJump(mCountAndDropLabel); @@ -1180,7 +1180,7 @@ final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked(); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mSrcDstAddr, nextFilterLabel); // Skip to the next filter if it's not zero-sized : // TCP_HEADER_SIZE + IPV4_HEADER_SIZE - ipv4_total_length == 0 @@ -1202,7 +1202,7 @@ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT); gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN); gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mPortSeqAckFingerprint, nextFilterLabel); + gen.addJumpIfBytesAtR0NotEqual(mPortSeqAckFingerprint, nextFilterLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK); gen.addJump(mCountAndDropLabel); @@ -1316,7 +1316,7 @@ // Pass if not ARP IPv4. gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET); maybeSetupCounter(gen, Counter.PASSED_ARP_NON_IPV4); - gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ARP_IPV4_HEADER, mCountAndPassLabel); // Pass if unknown ARP opcode. gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET); @@ -1332,7 +1332,7 @@ // Pass if unicast reply. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); // Either a unicast request, a unicast reply, or a broadcast reply. gen.defineLabel(checkTargetIPv4); @@ -1346,7 +1346,7 @@ // and broadcast replies with a different target IPv4 address. gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET); maybeSetupCounter(gen, Counter.DROPPED_ARP_OTHER_HOST); - gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel); + gen.addJumpIfBytesAtR0NotEqual(mIPv4Address, mCountAndDropLabel); } maybeSetupCounter(gen, Counter.PASSED_ARP); @@ -1394,7 +1394,7 @@ gen.addLoadImmediate(Register.R0, DHCP_CLIENT_MAC_OFFSET); // NOTE: Relies on R1 containing IPv4 header offset. gen.addAddR1(); - gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter); + gen.addJumpIfBytesAtR0NotEqual(mHardwareAddress, skipDhcpv4Filter); maybeSetupCounter(gen, Counter.PASSED_DHCP); gen.addJump(mCountAndPassLabel); @@ -1428,7 +1428,7 @@ // TODO: can we invert this condition to fall through to the common pass case below? maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST); gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST); gen.addJump(mCountAndDropLabel); } else { @@ -1555,8 +1555,7 @@ // TODO: Drop only if they don't contain the address of on-link neighbours. final byte[] unsolicitedNaDropPrefix = Arrays.copyOf(IPV6_ALL_NODES_ADDRESS, 15); gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, unsolicitedNaDropPrefix, - skipUnsolicitedMulticastNALabel); + gen.addJumpIfBytesAtR0NotEqual(unsolicitedNaDropPrefix, skipUnsolicitedMulticastNALabel); maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA); gen.addJump(mCountAndDropLabel); @@ -1613,7 +1612,7 @@ // Check it's L2 mDNS multicast address. gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V4_MAC_ADDRESS, skipMdnsv4Filter); // Checks it's IPv4. @@ -1631,8 +1630,7 @@ // Checks it's L2 mDNS multicast address. // Relies on R0 containing the ethernet destination mac address offset. - gen.addJumpIfBytesNotEqual(Register.R0, ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, - skipMdnsFilter); + gen.addJumpIfBytesAtR0NotEqual(ETH_MULTICAST_MDNS_V6_MAC_ADDRESS, skipMdnsFilter); // Checks it's IPv6. gen.addLoad16(Register.R0, ETH_ETHERTYPE_OFFSET); @@ -1663,7 +1661,7 @@ for (int i = 0; i < mMdnsAllowList.size(); ++i) { final String mDnsNextAllowedQnameCheck = "mdns_next_allowed_qname_check" + i; final byte[] encodedQname = encodeQname(mMdnsAllowList.get(i)); - gen.addJumpIfBytesNotEqual(Register.R0, encodedQname, mDnsNextAllowedQnameCheck); + gen.addJumpIfBytesAtR0NotEqual(encodedQname, mDnsNextAllowedQnameCheck); // QNAME matched gen.addJump(mDnsAcceptPacket); // QNAME not matched @@ -1777,7 +1775,7 @@ // Drop non-IP non-ARP broadcasts, pass the rest gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET); maybeSetupCounter(gen, Counter.PASSED_NON_IP_UNICAST); - gen.addJumpIfBytesNotEqual(Register.R0, ETHER_BROADCAST, mCountAndPassLabel); + gen.addJumpIfBytesAtR0NotEqual(ETHER_BROADCAST, mCountAndPassLabel); maybeSetupCounter(gen, Counter.DROPPED_ETH_BROADCAST); gen.addJump(mCountAndDropLabel);
diff --git a/src/android/net/dhcp6/Dhcp6Client.java b/src/android/net/dhcp6/Dhcp6Client.java index 291a97a..8d53048 100644 --- a/src/android/net/dhcp6/Dhcp6Client.java +++ b/src/android/net/dhcp6/Dhcp6Client.java
@@ -20,9 +20,7 @@ import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; import static android.system.OsConstants.AF_INET6; -import static android.system.OsConstants.IFA_F_NODAD; import static android.system.OsConstants.IPPROTO_UDP; -import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static android.system.OsConstants.SOCK_DGRAM; import static android.system.OsConstants.SOCK_NONBLOCK; @@ -31,14 +29,8 @@ import static com.android.net.module.util.NetworkStackConstants.DHCP6_SERVER_PORT; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ANY; import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; -import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; -import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; -import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64; -import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64; import android.content.Context; -import android.net.IpPrefix; -import android.net.LinkAddress; import android.net.ip.IpClient; import android.net.util.SocketUtils; import android.os.Handler; @@ -58,12 +50,10 @@ import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.InterfaceParams; import com.android.net.module.util.PacketReader; -import com.android.net.module.util.netlink.NetlinkUtils; import com.android.net.module.util.structs.IaPrefixOption; import java.io.FileDescriptor; import java.io.IOException; -import java.net.Inet6Address; import java.net.SocketException; import java.nio.ByteBuffer; import java.util.Collections; @@ -95,8 +85,6 @@ // Message.arg1 arguments to CMD_DHCP6_RESULT notification public static final int DHCP6_PD_SUCCESS = 1; public static final int DHCP6_PD_PREFIX_EXPIRED = 2; - public static final int DHCP6_PD_PREFIX_CHANGED = 3; - public static final int DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED = 4; // Notification from DHCPv6 state machine before quitting public static final int CMD_ON_QUIT = PUBLIC_BASE + 4; @@ -436,8 +424,8 @@ Log.d(TAG, "Scheduling IA_PD expiry in " + expirationTimeout + "s"); } - private void notifyPrefixDelegation(int result, @Nullable final PrefixDelegation pd) { - mController.sendMessage(CMD_DHCP6_RESULT, result, 0, pd); + private void notifyPrefixDelegation(int result, @Nullable final List<IaPrefixOption> ipos) { + mController.sendMessage(CMD_DHCP6_RESULT, result, 0, ipos); } private void clearDhcp6State() { @@ -557,10 +545,15 @@ return sendSolicitPacket(transId, elapsedTimeMs, pd.build()); } - // TODO: support multiple prefixes. @Override protected void receivePacket(Dhcp6Packet packet) { final PrefixDelegation pd = packet.mPrefixDelegation; + // Ignore any Advertise or Reply for Solicit(with Rapid Commit) with NoPrefixAvail + // status code, retransmit Solicit to see if any valid response from other Servers. + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Solicit without available prefix, ignoring"); + return; + } if (packet instanceof Dhcp6AdvertisePacket) { Log.d(TAG, "Get prefix delegation option from Advertise: " + pd); mAdvertise = pd; @@ -601,6 +594,11 @@ protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; final PrefixDelegation pd = packet.mPrefixDelegation; + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Request without available prefix, restart Solicit"); + transitionTo(mSolicitState); + return; + } Log.d(TAG, "Get prefix delegation option from Reply: " + pd); mReply = pd; mSolMaxRtMs = packet.getSolMaxRtMs().orElse(mSolMaxRtMs); @@ -621,7 +619,7 @@ public boolean processMessage(Message message) { switch (message.what) { case CMD_DHCP6_PD_EXPIRE: - notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, null); + notifyPrefixDelegation(DHCP6_PD_PREFIX_EXPIRED, mReply.getValidIaPrefixes()); transitionTo(mSolicitState); return HANDLED; default: @@ -639,33 +637,6 @@ } } - // Create an IPv6 address from the interface mac address with IFA_F_MANAGETEMPADDR - // flag, kernel will create another privacy IPv6 address on behalf of user space. - // We don't need to remember IPv6 addresses that need to extend the lifetime every - // time it enters BoundState. - private boolean addInterfaceAddress(@NonNull final Inet6Address address, - @NonNull final IaPrefixOption ipo) { - final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD; - final long now = SystemClock.elapsedRealtime(); - final long deprecationTime = now + ipo.preferred; - final long expirationTime = now + ipo.valid; - final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, - RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); - if (!la.isGlobalPreferred()) { - Log.e(TAG, la + " is not a global preferred IPv6 address"); - return false; - } - if (!NetlinkUtils.sendRtmNewAddressRequest(mIface.index, address, - (short) RFC7421_PREFIX_LENGTH, - flags, (byte) RT_SCOPE_UNIVERSE /* scope */, - ipo.preferred, ipo.valid)) { - Log.e(TAG, "Failed to set IPv6 address " + address.getHostAddress() - + "%" + mIface.index); - return false; - } - return true; - } - /** * Client has already obtained the lease(e.g. IA_PD option) from server and stays in Bound * state until T1 expires, and then transition to Renew state to extend the lease duration. @@ -675,26 +646,9 @@ public void enter() { super.enter(); scheduleLeaseTimers(); - - // TODO: roll back to SOLICIT state after a delay if something wrong happens - // instead of returning directly. - for (IaPrefixOption ipo : mReply.getValidIaPrefixes()) { - // TODO: The prefix with preferred/valid lifetime of 0 is valid, but client - // should stop using the prefix immediately. Actually kernel doesn't accept - // the address with valid lifetime of 0 and returns EINVAL when it sees that. - // We should send RTM_DELADDR netlink message to kernel to delete these addresses - // from the interface if any. - // Configure IPv6 addresses based on the delegated prefix(es) on the interface. - // We've checked that delegated prefix is valid upon receiving the response from - // DHCPv6 server, and the server may assign a prefix with length less than 64. So - // for SLAAC use case we always set the prefix length to 64 even if the delegated - // prefix length is less than 64. - final IpPrefix prefix = ipo.getIpPrefix(); - final Inet6Address address = createInet6AddressFromEui64(prefix, - macAddressToEui64(mIface.macAddr)); - if (!addInterfaceAddress(address, ipo)) continue; - } - notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply); + // Pass valid delegated prefix(es) to IpClient for IPv6 address configuration and + // active prefix(es) maintenance. + notifyPrefixDelegation(DHCP6_PD_SUCCESS, mReply.getValidIaPrefixes()); } @Override @@ -723,8 +677,8 @@ * That forces previous delegated prefixes to expire in a natural way, and client should * also stop trying to extend the lifetime for them. That being said, the global IPv6 address * lifetime won't be updated in BoundState if corresponding prefix doesn't appear in Reply - * message, resulting in these global IPv6 addresses eventually and IpClient obtains these - * updates via netlink message and remove the delegated prefix(es) from LinkProperties. + * message, resulting in these global IPv6 addresses expire eventually and IpClient obtains + * these updates via netlink message and remove the delegated prefix(es) from LinkProperties. * - If some binding IA_PDs were absent in Reply message, client should still stay at RenewState * or RebindState and retransmit Renew/Rebind messages to see if it can get all later. So far * we only support one IA_PD option per interface, if the received Reply message doesn't take @@ -744,9 +698,15 @@ @Override protected void receivePacket(Dhcp6Packet packet) { if (!(packet instanceof Dhcp6ReplyPacket)) return; + final PrefixDelegation pd = packet.mPrefixDelegation; + // Stay at Renew/Rebind state if the Reply message takes NoPrefixAvail status code, + // retransmit Renew/Rebind message to server, to retry obtaining the prefixes. + if (pd.statusCode == Dhcp6Packet.STATUS_NO_PREFIX_AVAIL) { + Log.w(TAG, "Server responded to Renew/Rebind without available prefix, ignoring"); + return; + } // TODO: send a Request message to the server that responded if any of the IA_PDs in // Reply message contain NoBinding status code. - final PrefixDelegation pd = packet.mPrefixDelegation; Log.d(TAG, "Get prefix delegation option from Reply as response to Renew/Rebind " + pd); if (pd.ipos.isEmpty()) return; mReply = pd;
diff --git a/src/android/net/dhcp6/Dhcp6Packet.java b/src/android/net/dhcp6/Dhcp6Packet.java index 4ef3195..53dd274 100644 --- a/src/android/net/dhcp6/Dhcp6Packet.java +++ b/src/android/net/dhcp6/Dhcp6Packet.java
@@ -32,7 +32,6 @@ import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -95,16 +94,16 @@ * DHCPv6 Optional Type: Status Code. */ public static final byte DHCP6_STATUS_CODE = 13; + private static final byte MIN_STATUS_CODE_OPT_LEN = 6; protected short mStatusCode; - protected String mStatusMsg; public static final short STATUS_SUCCESS = 0; public static final short STATUS_UNSPEC_FAIL = 1; - public static final short STATUS_NO_ADDR_AVAI = 2; + public static final short STATUS_NO_ADDRS_AVAIL = 2; public static final short STATUS_NO_BINDING = 3; - public static final short STATUS_PREFIX_NOT_ONLINK = 4; + public static final short STATUS_NOT_ONLINK = 4; public static final short STATUS_USE_MULTICAST = 5; - public static final short STATUS_NO_PREFIX_AVAI = 6; + public static final short STATUS_NO_PREFIX_AVAIL = 6; /** * DHCPv6 zero-length Optional Type: Rapid Commit. Per RFC4039, both DHCPDISCOVER and DHCPACK @@ -209,14 +208,22 @@ public final int t2; @NonNull public final List<IaPrefixOption> ipos; + public final short statusCode; + @VisibleForTesting public PrefixDelegation(int iaid, int t1, int t2, - @NonNull final List<IaPrefixOption> ipos) { + @NonNull final List<IaPrefixOption> ipos, short statusCode) { Objects.requireNonNull(ipos); this.iaid = iaid; this.t1 = t1; this.t2 = t2; this.ipos = ipos; + this.statusCode = statusCode; + } + + public PrefixDelegation(int iaid, int t1, int t2, + @NonNull final List<IaPrefixOption> ipos) { + this(iaid, t1, t2, ipos, STATUS_SUCCESS /* statusCode */); } /** @@ -251,6 +258,7 @@ final int t1 = buffer.getInt(); final int t2 = buffer.getInt(); final List<IaPrefixOption> ipos = new ArrayList<IaPrefixOption>(); + short statusCode = STATUS_SUCCESS; while (buffer.remaining() > 0) { final int original = buffer.position(); final short optionType = buffer.getShort(); @@ -262,12 +270,18 @@ Log.d(TAG, "IA Prefix Option: " + ipo); ipos.add(ipo); break; - // TODO: support DHCP6_STATUS_CODE option + case DHCP6_STATUS_CODE: + statusCode = buffer.getShort(); + // Skip the status message if any. + if (optionLen > 2) { + skipOption(buffer, optionLen - 2); + } + break; default: skipOption(buffer, optionLen); } } - return new PrefixDelegation(iaid, t1, t2, ipos); + return new PrefixDelegation(iaid, t1, t2, ipos, statusCode); } catch (BufferUnderflowException e) { throw new ParseException(e.getMessage()); } @@ -282,16 +296,26 @@ /** * Build an IA_PD option from given specific parameters, including IA_PREFIX options. + * + * Per RFC8415 section 21.13 if the Status Code option does not appear in a message in + * which the option could appear, the status of the message is assumed to be Success. So + * only put the Status Code option in IA_PD when the status code is not Success. */ public ByteBuffer build(@NonNull final List<IaPrefixOption> input) { final ByteBuffer iapd = ByteBuffer.allocate(IaPdOption.LENGTH - + Struct.getSize(IaPrefixOption.class) * input.size()); + + Struct.getSize(IaPrefixOption.class) * input.size() + + (statusCode != STATUS_SUCCESS ? MIN_STATUS_CODE_OPT_LEN : 0)); iapd.putInt(iaid); iapd.putInt(t1); iapd.putInt(t2); for (IaPrefixOption ipo : input) { ipo.writeToByteBuffer(iapd); } + if (statusCode != STATUS_SUCCESS) { + iapd.putShort(DHCP6_STATUS_CODE); + iapd.putShort((short) 2); + iapd.putShort(statusCode); + } iapd.flip(); return iapd; } @@ -314,8 +338,8 @@ @Override public String toString() { - return "Prefix Delegation: iaid " + iaid + ", t1 " + t1 + ", t2 " + t2 - + ", IA prefix options: " + ipos; + return String.format("Prefix Delegation, iaid: %s, t1: %s, t2: %s, status code: %s," + + " IA prefix options: %s", iaid, t1, t2, statusCodeToString(statusCode), ipos); } /** @@ -366,6 +390,27 @@ } } + private static String statusCodeToString(short statusCode) { + switch (statusCode) { + case STATUS_SUCCESS: + return "Success"; + case STATUS_UNSPEC_FAIL: + return "UnspecFail"; + case STATUS_NO_ADDRS_AVAIL: + return "NoAddrsAvail"; + case STATUS_NO_BINDING: + return "NoBinding"; + case STATUS_NOT_ONLINK: + return "NotOnLink"; + case STATUS_USE_MULTICAST: + return "UseMulticast"; + case STATUS_NO_PREFIX_AVAIL: + return "NoPrefixAvail"; + default: + return "Unknown"; + } + } + private static void skipOption(@NonNull final ByteBuffer packet, int optionLen) throws BufferUnderflowException { for (int i = 0; i < optionLen; i++) { @@ -374,35 +419,6 @@ } /** - * Reads a string of specified length from the buffer. - * - * TODO: move to a common place which can be shared with DhcpClient. - */ - private static String readAsciiString(@NonNull final ByteBuffer buf, int byteCount, - boolean isNullOk) { - final byte[] bytes = new byte[byteCount]; - buf.get(bytes); - return readAsciiString(bytes, isNullOk); - } - - private static String readAsciiString(@NonNull final byte[] payload, boolean isNullOk) { - final byte[] bytes = payload; - int length = bytes.length; - if (!isNullOk) { - // Stop at the first null byte. This is because some DHCP options (e.g., the domain - // name) are passed to netd via FrameworkListener, which refuses arguments containing - // null bytes. We don't do this by default because vendorInfo is an opaque string which - // could in theory contain null bytes. - for (length = 0; length < bytes.length; length++) { - if (bytes[length] == 0) { - break; - } - } - } - return new String(bytes, 0, length, StandardCharsets.US_ASCII); - } - - /** * Creates a concrete Dhcp6Packet from the supplied ByteBuffer. * * The buffer only starts with a UDP encapsulation (i.e. DHCPv6 message). A subset of the @@ -426,7 +442,6 @@ byte[] serverDuid = null; byte[] clientDuid = null; short statusCode = STATUS_SUCCESS; - String statusMsg = null; boolean rapidCommit = false; int solMaxRt = 0; PrefixDelegation pd = null; @@ -487,7 +502,12 @@ case DHCP6_STATUS_CODE: expectedLen = optionLen; statusCode = packet.getShort(); - statusMsg = readAsciiString(packet, expectedLen - 2, false /* isNullOk */); + // Skip the status message (if any), which is a UTF-8 encoded text string + // suitable for display to the end user, but is not useful for Dhcp6Client + // to decide how to properly handle the status code. + if (optionLen - 2 > 0) { + skipOption(packet, optionLen - 2); + } break; case DHCP6_SOL_MAX_RT: expectedLen = 4; @@ -545,7 +565,6 @@ throw new ParseException("Missing IA_PD option"); } newPacket.mStatusCode = statusCode; - newPacket.mStatusMsg = statusMsg; newPacket.mRapidCommit = rapidCommit; newPacket.mSolMaxRt = (solMaxRt >= 60 && solMaxRt <= 86400)
diff --git a/src/android/net/ip/IpClient.java b/src/android/net/ip/IpClient.java index 4f8eead..d7ef4df 100644 --- a/src/android/net/ip/IpClient.java +++ b/src/android/net/ip/IpClient.java
@@ -19,7 +19,6 @@ import static android.net.RouteInfo.RTN_UNICAST; import static android.net.RouteInfo.RTN_UNREACHABLE; import static android.net.dhcp.DhcpResultsParcelableUtil.toStableParcelable; -import static android.net.dhcp6.Dhcp6Packet.PrefixDelegation; import static android.net.ip.IIpClient.PROV_IPV4_DISABLED; import static android.net.ip.IIpClient.PROV_IPV6_DISABLED; import static android.net.ip.IIpClient.PROV_IPV6_LINKLOCAL; @@ -32,22 +31,28 @@ import static android.system.OsConstants.AF_PACKET; import static android.system.OsConstants.ETH_P_ARP; import static android.system.OsConstants.ETH_P_IPV6; +import static android.system.OsConstants.IFA_F_NODAD; +import static android.system.OsConstants.RT_SCOPE_UNIVERSE; import static android.system.OsConstants.SOCK_NONBLOCK; import static android.system.OsConstants.SOCK_RAW; +import static com.android.net.module.util.LinkPropertiesUtils.CompareResult; import static com.android.net.module.util.NetworkStackConstants.ARP_REPLY; import static com.android.net.module.util.NetworkStackConstants.ETHER_BROADCAST; import static com.android.net.module.util.NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST; import static com.android.net.module.util.NetworkStackConstants.RFC7421_PREFIX_LENGTH; import static com.android.net.module.util.NetworkStackConstants.VENDOR_SPECIFIC_IE_ID; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_MANAGETEMPADDR; +import static com.android.networkstack.apishim.ConstantsShim.IFA_F_NOPREFIXROUTE; import static com.android.networkstack.util.NetworkStackUtils.APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE; import static com.android.networkstack.util.NetworkStackUtils.APF_NEW_RA_FILTER_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_FORCE_DISABLE; +import static com.android.networkstack.util.NetworkStackUtils.APF_POLLING_COUNTERS_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_DHCPV6_PREFIX_DELEGATION_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GARP_NA_ROAMING_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_GRATUITOUS_NA_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION; +import static com.android.networkstack.util.NetworkStackUtils.createInet6AddressFromEui64; +import static com.android.networkstack.util.NetworkStackUtils.macAddressToEui64; import static com.android.server.util.PermissionUtil.enforceNetworkStackCallingPermission; import android.annotation.SuppressLint; @@ -123,8 +128,10 @@ import com.android.internal.util.WakeupMessage; import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.ConnectivityUtils; import com.android.net.module.util.DeviceConfigUtils; import com.android.net.module.util.InterfaceParams; +import com.android.net.module.util.LinkPropertiesUtils; import com.android.net.module.util.SharedLog; import com.android.net.module.util.SocketUtils; import com.android.net.module.util.arp.ArpPacket; @@ -154,7 +161,6 @@ import java.net.SocketAddress; import java.net.SocketException; import java.net.URL; -import java.net.UnknownHostException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -541,6 +547,8 @@ // IpClient shares a handler with Dhcp6Client: commands must not overlap public static final int DHCP6CLIENT_CMD_BASE = 2000; + private static final int DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS = + IFA_F_MANAGETEMPADDR | IFA_F_NOPREFIXROUTE | IFA_F_NODAD; // Settings and default values. private static final int MAX_LOG_RECORDS = 500; @@ -677,6 +685,8 @@ private final Set<Inet6Address> mGratuitousNaTargetAddresses = new HashSet<>(); // Set of IPv6 addresses from which multicast NS packets have been sent. private final Set<Inet6Address> mMulticastNsSourceAddresses = new HashSet<>(); + // Set of delegated prefixes. + private final Set<IpPrefix> mDelegatedPrefixes = new HashSet<>(); @Nullable private final DevicePolicyManager mDevicePolicyManager; @@ -694,7 +704,7 @@ private final boolean mUseNewApfFilter; private final boolean mEnableIpClientIgnoreLowRaLifetime; private final boolean mApfShouldHandleLightDoze; - private final boolean mApfShouldPollingCounters; + private final boolean mEnableApfPollingCounters; private InterfaceParams mInterfaceParams; @@ -721,7 +731,6 @@ private Integer mDadTransmits = null; private int mMaxDtimMultiplier = DTIM_MULTIPLIER_RESET; private ApfCapabilities mCurrentApfCapabilities; - private PrefixDelegation mPrefixDelegation; private WakeupMessage mIpv6AutoconfTimeoutAlarm = null; /** @@ -928,13 +937,13 @@ CONFIG_APF_COUNTER_POLLING_INTERVAL_SECS, DEFAULT_APF_COUNTER_POLLING_INTERVAL_SECS) * DateUtils.SECOND_IN_MILLIS; mUseNewApfFilter = mDependencies.isFeatureEnabled(context, APF_NEW_RA_FILTER_VERSION); + mEnableApfPollingCounters = mDependencies.isFeatureEnabled(context, + APF_POLLING_COUNTERS_VERSION); mEnableIpClientIgnoreLowRaLifetime = mDependencies.isFeatureEnabled(context, IPCLIENT_IGNORE_LOW_RA_LIFETIME_VERSION); // Light doze mode status checking API is only available at T or later releases. mApfShouldHandleLightDoze = SdkLevel.isAtLeastT() && mDependencies.isFeatureNotChickenedOut( mContext, APF_HANDLE_LIGHT_DOZE_FORCE_DISABLE); - mApfShouldPollingCounters = mDependencies.isFeatureNotChickenedOut( - mContext, APF_POLLING_COUNTERS_FORCE_DISABLE); IpClientLinkObserver.Configuration config = new IpClientLinkObserver.Configuration( mMinRdnssLifetimeSec); @@ -1135,10 +1144,6 @@ return mDependencies.isFeatureEnabled(mContext, IPCLIENT_GARP_NA_ROAMING_VERSION); } - private boolean isMulticastNsEnabled() { - return mDependencies.isFeatureNotChickenedOut(mContext, IPCLIENT_MULTICAST_NS_VERSION); - } - @VisibleForTesting static MacAddress getInitialBssid(final Layer2Information layer2Info, final ScanResultInfo scanResultInfo, boolean isAtLeastS) { @@ -1460,7 +1465,6 @@ mDhcpResults = null; mTcpBufferSizes = ""; mHttpProxy = null; - mPrefixDelegation = null; mLinkProperties = new LinkProperties(); mLinkProperties.setInterfaceName(mInterfaceName); @@ -1741,6 +1745,31 @@ addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); mShim.setNat64Prefix(newLp, mShim.getNat64Prefix(netlinkLinkProperties)); + // Check if any link address update from netlink. + final CompareResult<LinkAddress> results = + LinkPropertiesUtils.compareAddresses(mLinkProperties, newLp); + // In the case that there are multiple netlink update events about a global IPv6 address + // derived from the delegated prefix, a flag-only change event(e.g. due to the duplicate + // address detection) will cause an identical IP address to be put into both Added and + // Removed list based on the CompareResult implementation. To prevent a prefix from being + // mistakenly removed from the delegate prefix list, it is better to always check the + // removed list before checking the added list(e.g. anyway we can add the removed prefix + // back again). + for (LinkAddress la : results.removed) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.remove(prefix); + } + // TODO: remove onIpv6AddressRemoved callback. + } + + for (LinkAddress la : results.added) { + if (mDhcp6PrefixDelegationEnabled && isIpv6StableDelegatedAddress(la)) { + final IpPrefix prefix = new IpPrefix(la.getAddress(), RFC7421_PREFIX_LENGTH); + mDelegatedPrefixes.add(prefix); + } + } + // [3] Add in data from DHCPv4, if available. // // mDhcpResults is never shared with any other owner so we don't have @@ -1777,26 +1806,22 @@ // TODO: also look at the IPv6 RA (netlink) for captive portal URL } - // [4] Add in data from DHCPv6 Prefix Delegation, if available. - if (mPrefixDelegation != null) { - for (IaPrefixOption ipo : mPrefixDelegation.ipos) { - try { - final IpPrefix destination = - new IpPrefix(Inet6Address.getByAddress(ipo.prefix), ipo.prefixLen); - // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to this route - // based on the delegated prefix. To prevent the traffic loop between host and - // upstream delegated router. Because we specify the IFA_F_NOPREFIXROUTE when - // adding the IPv6 address, the kernel does not create a delegated prefix route, - // as a result, the user space won't receive any RTM_NEWROUTE message about the - // delegated prefix, we still need to install an unreachable route for the - // delegated prefix manually in LinkProperties to notify the caller this update. - // TODO: support RTN_BLACKHOLE in netd and use that on newer Android versions. - final RouteInfo route = new RouteInfo(destination, null /* gateway */, - mInterfaceName, RTN_UNREACHABLE); - newLp.addRoute(route); - } catch (UnknownHostException e) { - Log.wtf(mTag, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix)); - } + // [4] Add route with delegated prefix according to the global address update. + if (mDhcp6PrefixDelegationEnabled) { + for (IpPrefix destination : mDelegatedPrefixes) { + // Direct-connected route to delegated prefix. Add RTN_UNREACHABLE to + // this route based on the delegated prefix. To prevent the traffic loop + // between host and upstream delegated router. Because we specify the + // IFA_F_NOPREFIXROUTE when adding the IPv6 address, the kernel does not + // create a delegated prefix route, as a result, the user space won't + // receive any RTM_NEWROUTE message about the delegated prefix, we still + // need to install an unreachable route for the delegated prefix manually + // in LinkProperties to notify the caller this update. + // TODO: support RTN_BLACKHOLE in netd and use that on newer Android + // versions. + final RouteInfo route = new RouteInfo(destination, + null /* gateway */, mInterfaceName, RTN_UNREACHABLE); + newLp.addRoute(route); } } @@ -1994,6 +2019,24 @@ } } + private static boolean hasFlag(@NonNull final LinkAddress la, final int flags) { + return (la.getFlags() & flags) == flags; + + } + + // Check whether a global IPv6 stable address is derived from DHCPv6 prefix delegation. + // Address derived from delegated prefix should be: + // - unicast global routable address + // - with prefix length of 64 + // - has IFA_F_MANAGETEMPADDR, IFA_F_NOPREFIXROUTE and IFA_F_NODAD flags + private static boolean isIpv6StableDelegatedAddress(@NonNull final LinkAddress la) { + return la.isIpv6() + && !ConnectivityUtils.isIPv6ULA(la.getAddress()) + && (la.getPrefixLength() == RFC7421_PREFIX_LENGTH) + && (la.getScope() == (byte) RT_SCOPE_UNIVERSE) + && hasFlag(la, DHCPV6_PREFIX_DELEGATION_ADDRESS_FLAGS); + } + // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); @@ -2035,9 +2078,7 @@ // // TODO: stop sending this multicast NS after deployment of RFC9131 in the field, leverage // the gratuitous NA to update the first-hop router's neighbor cache entry. - if (isMulticastNsEnabled()) { - maybeSendMulticastNSes(newLp); - } + maybeSendMulticastNSes(newLp); // Either success IPv4 or IPv6 provisioning triggers new LinkProperties update, // wait for the provisioning completion and record the latency. @@ -2441,6 +2482,7 @@ mHasDisabledAcceptRaDefrtrOnProvLoss = false; mGratuitousNaTargetAddresses.clear(); mMulticastNsSourceAddresses.clear(); + mDelegatedPrefixes.clear(); resetLinkProperties(); if (mStartTimeMillis > 0) { @@ -2903,7 +2945,7 @@ if (mApfFilter == null) { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } - if (mApfShouldPollingCounters) { + if (mEnableApfPollingCounters) { sendMessageDelayed(CMD_UPDATE_APF_DATA_SNAPSHOT, mApfCounterPollingIntervalMs); } @@ -2998,30 +3040,71 @@ } } - private void clearIpv6PrefixDelegationAddresses() { - if (mPrefixDelegation == null) { - Log.wtf(mTag, "PrefixDelegation shouldn't be null when DHCPv6 PD fails."); - return; - } - final IaPrefixOption ipo = mPrefixDelegation.ipos.get(0); - final IpPrefix prefix; - try { - prefix = new IpPrefix(Inet6Address.getByAddress(ipo.prefix), RFC7421_PREFIX_LENGTH); - } catch (UnknownHostException e) { - Log.wtf(TAG, "Invalid delegated prefix " + HexDump.toHexString(ipo.prefix)); - return; - } - - // Delete the global IPv6 address based on delegated prefix from interface. + private void deleteIpv6PrefixDelegationAddresses(final IpPrefix prefix) { for (LinkAddress la : mLinkProperties.getLinkAddresses()) { final InetAddress address = la.getAddress(); if (prefix.contains(address)) { - NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, - (Inet6Address) address, (short) la.getPrefixLength()); + if (!NetlinkUtils.sendRtmDelAddressRequest(mInterfaceParams.index, + (Inet6Address) address, (short) la.getPrefixLength())) { + Log.e(TAG, "Failed to delete IPv6 address " + address.getHostAddress()); + } } } } + private void addInterfaceAddress(@NonNull final Inet6Address address, + @NonNull final IaPrefixOption ipo) { + final int flags = IFA_F_NOPREFIXROUTE | IFA_F_MANAGETEMPADDR | IFA_F_NODAD; + final long now = SystemClock.elapsedRealtime(); + final long deprecationTime = now + ipo.preferred; + final long expirationTime = now + ipo.valid; + final LinkAddress la = new LinkAddress(address, RFC7421_PREFIX_LENGTH, flags, + RT_SCOPE_UNIVERSE /* scope */, deprecationTime, expirationTime); + if (!la.isGlobalPreferred()) { + Log.w(TAG, la + " is not a global IPv6 address"); + return; + } + if (!NetlinkUtils.sendRtmNewAddressRequest(mInterfaceParams.index, address, + (short) RFC7421_PREFIX_LENGTH, + flags, (byte) RT_SCOPE_UNIVERSE /* scope */, + ipo.preferred, ipo.valid)) { + Log.e(TAG, "Failed to set IPv6 address on " + address.getHostAddress() + + "%" + mInterfaceParams.index); + } + } + + private void updateDelegatedAddresses(@NonNull final List<IaPrefixOption> valid) { + if (valid.isEmpty()) return; + for (IaPrefixOption ipo : valid) { + final IpPrefix prefix = ipo.getIpPrefix(); + // The prefix with preferred/valid lifetime of 0 is considered as a valid prefix, + // it can be passed to IpClient from Dhcp6Client, however, client should stop using + // the global addresses derived from this prefix immediately. + if (ipo.withZeroLifetimes()) { + Log.d(TAG, "Delete IPv6 address derived from prefix " + prefix + + " with 0 preferred/valid lifetime"); + deleteIpv6PrefixDelegationAddresses(prefix); + } + // Otherwise, configure IPv6 addresses derived from the delegated prefix(es) on + // the interface. We've checked that delegated prefix is valid upon receiving the + // response from DHCPv6 server, and the server may assign a prefix with length less + // than 64. So for SLAAC use case we always set the prefix length to 64 even if the + // delegated prefix length is less than 64. + final Inet6Address address = createInet6AddressFromEui64(prefix, + macAddressToEui64(mInterfaceParams.macAddr)); + addInterfaceAddress(address, ipo); + } + } + + private void removeExpiredDelegatedAddresses(@NonNull final List<IaPrefixOption> expired) { + if (expired.isEmpty()) return; + for (IaPrefixOption ipo : expired) { + final IpPrefix prefix = ipo.getIpPrefix(); + Log.d(TAG, "Delete IPv6 address derived from expired prefix " + prefix); + deleteIpv6PrefixDelegationAddresses(prefix); + } + } + @Override public boolean processMessage(Message msg) { switch (msg.what) { @@ -3216,15 +3299,14 @@ case Dhcp6Client.CMD_DHCP6_RESULT: switch(msg.arg1) { case Dhcp6Client.DHCP6_PD_SUCCESS: - mPrefixDelegation = (PrefixDelegation) msg.obj; + final List<IaPrefixOption> toBeUpdated = (List<IaPrefixOption>) msg.obj; + updateDelegatedAddresses(toBeUpdated); handleLinkPropertiesUpdate(SEND_CALLBACKS); break; case Dhcp6Client.DHCP6_PD_PREFIX_EXPIRED: - case Dhcp6Client.DHCP6_PD_PREFIX_CHANGED: - case Dhcp6Client.DHCP6_PD_PREFIX_MSG_EXCHANGE_TERMINATED: - clearIpv6PrefixDelegationAddresses(); - mPrefixDelegation = null; + final List<IaPrefixOption> toBeRemoved = (List<IaPrefixOption>) msg.obj; + removeExpiredDelegatedAddresses(toBeRemoved); handleLinkPropertiesUpdate(SEND_CALLBACKS); break;
diff --git a/src/android/net/ip/IpReachabilityMonitor.java b/src/android/net/ip/IpReachabilityMonitor.java index 40a4bb6..e252a68 100644 --- a/src/android/net/ip/IpReachabilityMonitor.java +++ b/src/android/net/ip/IpReachabilityMonitor.java
@@ -23,7 +23,6 @@ import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DEFAULT_ROUTER_VERSION; import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION; -import static com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION; import android.content.Context; import android.net.ConnectivityManager; @@ -236,7 +235,6 @@ private int mInterSolicitIntervalMs; @NonNull private final Callback mCallback; - private final boolean mMulticastResolicitEnabled; private final boolean mIgnoreIncompleteIpv6DnsServerEnabled; private final boolean mIgnoreIncompleteIpv6DefaultRouterEnabled; @@ -260,8 +258,6 @@ mUsingMultinetworkPolicyTracker = usingMultinetworkPolicyTracker; mCm = context.getSystemService(ConnectivityManager.class); mDependencies = dependencies; - mMulticastResolicitEnabled = dependencies.isFeatureNotChickenedOut(context, - IP_REACHABILITY_MCAST_RESOLICIT_VERSION); mIgnoreIncompleteIpv6DnsServerEnabled = dependencies.isFeatureNotChickenedOut(context, IP_REACHABILITY_IGNORE_INCOMPLETE_IPV6_DNS_SERVER_VERSION); mIgnoreIncompleteIpv6DefaultRouterEnabled = dependencies.isFeatureEnabled(context, @@ -274,10 +270,8 @@ // In case the overylaid parameters specify an invalid configuration, set the parameters // to the hardcoded defaults first, then set them to the values used in the steady state. try { - int numResolicits = mMulticastResolicitEnabled - ? NUD_MCAST_RESOLICIT_NUM - : INVALID_NUD_MCAST_RESOLICIT_NUM; - setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, numResolicits); + setNeighborParameters(MIN_NUD_SOLICIT_NUM, MIN_NUD_SOLICIT_INTERVAL_MS, + NUD_MCAST_RESOLICIT_NUM); } catch (Exception e) { Log.e(TAG, "Failed to adjust neighbor parameters with hardcoded defaults"); } @@ -414,8 +408,7 @@ private void handleNeighborReachable(@Nullable final NeighborEvent prev, @NonNull final NeighborEvent event) { - if (mMulticastResolicitEnabled - && hasDefaultRouterNeighborMacAddressChanged(prev, event)) { + if (hasDefaultRouterNeighborMacAddressChanged(prev, event)) { // This implies device has confirmed the neighbor's reachability from // other states(e.g., NUD_PROBE or NUD_STALE), checking if the mac // address hasn't changed is required. If Mac address does change, then @@ -581,8 +574,7 @@ private long getProbeWakeLockDuration() { final long gracePeriodMs = 500; - final int numSolicits = - mNumSolicits + (mMulticastResolicitEnabled ? NUD_MCAST_RESOLICIT_NUM : 0); + final int numSolicits = mNumSolicits + NUD_MCAST_RESOLICIT_NUM; return (long) (numSolicits * mInterSolicitIntervalMs) + gracePeriodMs; }
diff --git a/src/com/android/networkstack/util/NetworkStackUtils.java b/src/com/android/networkstack/util/NetworkStackUtils.java index 85da400..829d0c6 100755 --- a/src/com/android/networkstack/util/NetworkStackUtils.java +++ b/src/com/android/networkstack/util/NetworkStackUtils.java
@@ -197,13 +197,6 @@ public static final String IPCLIENT_GRATUITOUS_NA_VERSION = "ipclient_gratuitous_na_version"; /** - * Experiment flag to send multicast NS from the global IPv6 GUA to the solicited-node - * multicast address based on the default router's IPv6 link-local address, which helps - * flush the first-hop routers' neighbor cache entry for the global IPv6 GUA. - */ - public static final String IPCLIENT_MULTICAST_NS_VERSION = "ipclient_multicast_ns_version"; - - /** * Experiment flag to enable sending Gratuitous APR and Gratuitous Neighbor Advertisement for * all assigned IPv4 and IPv6 GUAs after completing L2 roaming. */ @@ -219,13 +212,6 @@ "ipclient_accept_ipv6_link_local_dns_version"; /** - * Experiment flag to enable "mcast_resolicit" neighbor parameter in IpReachabilityMonitor, - * set it to 3 by default. - */ - public static final String IP_REACHABILITY_MCAST_RESOLICIT_VERSION = - "ip_reachability_mcast_resolicit_version"; - - /** * Experiment flag to attempt to ignore the on-link IPv6 DNS server which fails to respond to * address resolution. */ @@ -250,6 +236,10 @@ */ public static final String APF_NEW_RA_FILTER_VERSION = "apf_new_ra_filter_version"; /** + * Experiment flag to enable the feature of polling counters in Apf. + */ + public static final String APF_POLLING_COUNTERS_VERSION = "apf_polling_counters_version"; + /** * Experiment flag to enable the feature of ignoring any individual RA section with lifetime * below accept_ra_min_lft sysctl. */ @@ -272,12 +262,6 @@ "apf_handle_light_doze_force_disable"; /** - * Kill switch flag to disable the feature of polling counters in Apf. - */ - public static final String APF_POLLING_COUNTERS_FORCE_DISABLE = - "apf_polling_counters_force_disable"; - - /** * Kill switch flag to disable the feature of skipping Tcp socket info polling when light * doze mode is enabled. */
diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index 8300c73..ec058d7 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp
@@ -68,6 +68,9 @@ static_libs: [ "NetworkStackApiStableLib", ], + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack integration tests. @@ -84,6 +87,9 @@ jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack next integration tests. @@ -107,6 +113,9 @@ jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Network stack integration root tests. @@ -124,12 +133,18 @@ "NetworkStackApiStableLib", ], platform_apis: true, - test_suites: ["general-tests", "mts-networking"], + test_suites: [ + "general-tests", + "mts-networking", + ], compile_multilib: "both", manifest: "AndroidManifest_root.xml", jarjar_rules: ":NetworkStackJarJarRules", host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate_Integration.xml", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Special version of the network stack tests that includes all tests necessary for code coverage @@ -138,7 +153,10 @@ name: "NetworkStackCoverageTests", certificate: "networkstack", platform_apis: true, - test_suites: ["device-tests", "mts-networking"], + test_suites: [ + "device-tests", + "mts-networking", + ], test_config: "AndroidTest_Coverage.xml", defaults: [ "NetworkStackReleaseTargetSdk", @@ -154,4 +172,7 @@ compile_multilib: "both", manifest: "AndroidManifest_coverage.xml", jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, }
diff --git a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java index c733f9c..2f1f7d1 100644 --- a/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java +++ b/tests/integration/common/android/net/ip/IpClientIntegrationTestCommon.java
@@ -312,6 +312,7 @@ protected static final long TEST_TIMEOUT_MS = 2_000L; private static final long TEST_WAIT_ENOBUFS_TIMEOUT_MS = 30_000L; + private static final long TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS = 15_000L; @Rule public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(); @@ -779,6 +780,8 @@ enableRealAlarm("DhcpClient." + mIfaceName + ".KICK"); // Enable alarm for IPv6 autoconf via SLAAC in IpClient. enableRealAlarm("IpClient." + mIfaceName + ".EVENT_IPV6_AUTOCONF_TIMEOUT"); + // Enable packet retransmit alarm in Dhcp6Client. + enableRealAlarm("Dhcp6Client." + mIfaceName + ".KICK"); } mIIpClient = makeIIpClient(mIfaceName, mCb); @@ -4137,12 +4140,6 @@ return ns; } - // Override this function with disabled experiment flag by default, in order not to - // affect those tests which are just related to basic IpReachabilityMonitor infra. - private void prepareIpReachabilityMonitorTest() throws Exception { - prepareIpReachabilityMonitorTest(false /* isMulticastResolicitEnabled */); - } - private void assertNotifyNeighborLost(Inet6Address targetIp, NudEventType eventType) throws Exception { // For root test suite, rely on the IIpClient aidl interface version constant defined in @@ -4175,8 +4172,7 @@ verify(mCb, never()).onReachabilityLost(any()); } - private void prepareIpReachabilityMonitorTest(boolean isMulticastResolicitEnabled) - throws Exception { + private void prepareIpReachabilityMonitorTest() throws Exception { final ScanResultInfo info = makeScanResultInfo(TEST_DEFAULT_SSID, TEST_DEFAULT_BSSID); ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withLayer2Information(new Layer2Information(TEST_L2KEY, TEST_CLUSTER, @@ -4185,8 +4181,6 @@ .withDisplayName(TEST_DEFAULT_SSID) .withoutIPv4() .build(); - setFeatureEnabled(NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION, - isMulticastResolicitEnabled); startIpClientProvisioning(config); verify(mCb, timeout(TEST_TIMEOUT_MS)).setFallbackMulticastFilter(true); doIpv6OnlyProvisioning(); @@ -4200,11 +4194,15 @@ final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); - assertEquals(expectedNudSolicitNum, nsList.size()); - for (NeighborSolicitation ns : nsList) { + int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; + assertEquals(expectedSize, nsList.size()); + for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); } + for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { + assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); + } } @Test @@ -4239,43 +4237,10 @@ assertNeverNotifyNeighborLost(); } - private void runIpReachabilityMonitorMcastResolicitProbeFailedTest() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); - - final List<NeighborSolicitation> nsList = waitForMultipleNeighborSolicitations(); - final int expectedNudSolicitNum = readNudSolicitNumPostRoamingFromResource(); - int expectedSize = expectedNudSolicitNum + NUD_MCAST_RESOLICIT_NUM; - assertEquals(expectedSize, nsList.size()); - for (NeighborSolicitation ns : nsList.subList(0, expectedNudSolicitNum)) { - assertUnicastNeighborSolicitation(ns, ROUTER_MAC /* dstMac */, - ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); - } - for (NeighborSolicitation ns : nsList.subList(expectedNudSolicitNum, nsList.size())) { - assertMulticastNeighborSolicitation(ns, ROUTER_LINK_LOCAL /* targetIp */); - } - } - - @Test - public void testIpReachabilityMonitor_mcastResolicitProbeFailed() throws Exception { - runIpReachabilityMonitorMcastResolicitProbeFailedTest(); - assertNotifyNeighborLost(ROUTER_LINK_LOCAL /* targetIp */, - NudEventType.NUD_POST_ROAMING_FAILED_CRITICAL); - } - - @Test @SignatureRequiredTest(reason = "requires mock callback object") - public void testIpReachabilityMonitor_mcastResolicitProbeFailed_legacyCallback() - throws Exception { - when(mCb.getInterfaceVersion()).thenReturn(12 /* assign an older interface aidl version */); - - runIpReachabilityMonitorMcastResolicitProbeFailedTest(); - verify(mCb, timeout(TEST_TIMEOUT_MS)).onReachabilityLost(any()); - verify(mCb, never()).onReachabilityFailure(any()); - } - @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithSameLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); + prepareIpReachabilityMonitorTest(); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4292,7 +4257,7 @@ @Test public void testIpReachabilityMonitor_mcastResolicitProbeReachableWithDiffLinkLayerAddress() throws Exception { - prepareIpReachabilityMonitorTest(true /* isMulticastResolicitEnabled */); + prepareIpReachabilityMonitorTest(); final NeighborSolicitation ns = waitForUnicastNeighborSolicitation(ROUTER_MAC /* dstMac */, ROUTER_LINK_LOCAL /* dstIp */, ROUTER_LINK_LOCAL /* targetIp */); @@ -4664,9 +4629,6 @@ .withoutIPv4() .build(); - setFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION, - true /* isUnsolicitedNsEnabled */); - assertTrue(isFeatureEnabled(NetworkStackUtils.IPCLIENT_MULTICAST_NS_VERSION)); startIpClientProvisioning(config); doIpv6OnlyProvisioning(); @@ -5216,9 +5178,7 @@ x -> x.isIpv6Provisioned() && hasIpv6AddressPrefixedWith(x, prefix) && hasIpv6AddressPrefixedWith(x, prefix1) - // TODO: comment this line to make the test passed, remove the comment later - // once IpClient supports multi-prefixes. - // && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE) // IPv6 link-local, four global delegated IPv6 addresses && x.getLinkAddresses().size() == 5 @@ -5280,14 +5240,15 @@ mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, (Inet6Address) mClientIpAddress, false /* rapidCommit */)); verify(mCb, never()).onProvisioningFailure(any()); + // IPv6 addresses derived from prefix with 0 preferred/valid lifetime should be deleted. verify(mCb, timeout(TEST_TIMEOUT_MS)).onLinkPropertiesChange(argThat( x -> x.isIpv6Provisioned() - && hasIpv6AddressPrefixedWith(x, prefix) + && !hasIpv6AddressPrefixedWith(x, prefix) && hasIpv6AddressPrefixedWith(x, prefix1) - && hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) + && !hasRouteTo(x, "2001:db8:1::/64", RTN_UNREACHABLE) && hasRouteTo(x, "2001:db8:2::/64", RTN_UNREACHABLE) - // IPv6 link-local, four global delegated IPv6 addresses - && x.getLinkAddresses().size() == 5 + // IPv6 link-local, two global delegated IPv6 addresses with prefix1 + && x.getLinkAddresses().size() == 3 )); handler.post(() -> renewAlarm.onAlarm()); @@ -5296,7 +5257,8 @@ packet = getNextDhcp6Packet(); assertTrue(packet instanceof Dhcp6RenewPacket); final List<IaPrefixOption> renewIpos = packet.getPrefixDelegation().ipos; - assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64 + assertEquals(1, renewIpos.size()); // don't renew prefix 2001:db8:1::/64 with 0 + // preferred/valid lifetime assertEquals(prefix1, renewIpos.get(0).getIpPrefix()); } @@ -5317,7 +5279,7 @@ clearInvocations(mCb); - // Reply with the requested prefix with preferred/valid lifetime of 0. + // Reply with the requested prefix with the same t1/t2/lifetime. final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); final IaPrefixOption ipo = buildIaPrefixOption(prefix, 3600 /* preferred */, 3600 /* valid */); @@ -5339,6 +5301,220 @@ } @Test + public void testDhcp6Pd_multipleIaPrefixOptions() throws Exception { + final InOrder inOrder = inOrder(mCb); + final IpPrefix prefix1 = new IpPrefix("2001:db8:1::/64"); + final IpPrefix prefix2 = new IpPrefix("2400:db8:100::/64"); + final IpPrefix prefix3 = new IpPrefix("fd7c:9df8:7f39:dc89::/64"); + final IaPrefixOption ipo1 = buildIaPrefixOption(prefix1, 4500 /* preferred */, + 7200 /* valid */); + final IaPrefixOption ipo2 = buildIaPrefixOption(prefix2, 5600 /* preferred */, + 6000 /* valid */); + final IaPrefixOption ipo3 = buildIaPrefixOption(prefix3, 7200 /* preferred */, + 14400 /* valid */); + prepareDhcp6PdTest(); + handleDhcp6Packets(Arrays.asList(ipo1, ipo2, ipo3), 3600 /* t1 */, 4500 /* t2 */, + true /* shouldReplyRapidCommit */); + + final ArgumentCaptor<LinkProperties> captor = ArgumentCaptor.forClass(LinkProperties.class); + verifyWithTimeout(inOrder, mCb).onProvisioningSuccess(captor.capture()); + LinkProperties lp = captor.getValue(); + + // Sometimes privacy address or route may appear later along with onLinkPropertiesChange + // callback, in this case we wait a bit longer to see all of these properties appeared and + // then verify if they are what we are looking for. + if (lp.getLinkAddresses().size() < 5 || lp.getRoutes().size() < 4) { + final CompletableFuture<LinkProperties> lpFuture = new CompletableFuture<>(); + verifyWithTimeout(inOrder, mCb).onLinkPropertiesChange(argThat(x -> { + if (!x.isIpv6Provisioned()) return false; + if (x.getLinkAddresses().size() != 5) return false; + if (x.getRoutes().size() != 4) return false; + lpFuture.complete(x); + return true; + })); + lp = lpFuture.get(TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS); + } + assertNotNull(lp); + assertTrue(hasIpv6AddressPrefixedWith(lp, prefix1)); + assertTrue(hasIpv6AddressPrefixedWith(lp, prefix2)); + assertFalse(hasIpv6AddressPrefixedWith(lp, prefix3)); + assertTrue(hasRouteTo(lp, prefix1.toString(), RTN_UNREACHABLE)); + assertTrue(hasRouteTo(lp, prefix2.toString(), RTN_UNREACHABLE)); + assertFalse(hasRouteTo(lp, prefix3.toString(), RTN_UNREACHABLE)); + } + + private void runDhcp6PacketWithNoPrefixAvailStatusCodeTest(boolean shouldReplyWithAdvertise) + throws Exception { + prepareDhcp6PdTest(); + Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + + final PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */, + new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + final ByteBuffer iapd = pd.build(); + if (shouldReplyWithAdvertise) { + mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress)); + } else { + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, true /* rapidCommit */)); + } + + // Check if client will ignore Advertise or Reply for Rapid Commit Solicit and + // retransmit Solicit. + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + } + + @Test + public void testDhcp6AdvertiseWithNoPrefixAvailStatusCode() throws Exception { + // Advertise + runDhcp6PacketWithNoPrefixAvailStatusCodeTest(true /* shouldReplyWithAdvertise */); + } + + @Test + public void testDhcp6ReplyForRapidCommitSolicitWithNoPrefixAvailStatusCode() throws Exception { + // Reply + runDhcp6PacketWithNoPrefixAvailStatusCodeTest(false /* shouldReplyWithAdvertise */); + } + + @Test + public void testDhcp6ReplyForRequestWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdTest(); + Dhcp6Packet packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Advertise(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress)); + + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6RequestPacket); + + // Reply for Request with NoPrefixAvail status code. Not sure if this is reasonable in + // practice, but Server can do everything it wants. + pd = new PrefixDelegation(packet.getIaId(), 0 /* t1 */, 0 /* t2 */, + new ArrayList<IaPrefixOption>() /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + // Check if client will ignore Reply for Request with NoPrefixAvail status code, and + // rollback to SolicitState. + packet = getNextDhcp6Packet(PACKET_TIMEOUT_MS); + assertTrue(packet instanceof Dhcp6SolicitPacket); + } + + @Test + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + public void testDhcp6ReplyForRenewWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply with normal IA_PD. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + // Trigger another Renew message. + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + // Reply for Renew with NoPrefixAvail status code, check if client will retransmit the + // Renew message. + pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, 4500 /* t2 */, + new ArrayList<IaPrefixOption>(0) /* ipos */, Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS); + assertTrue(packet instanceof Dhcp6RenewPacket); + } + + @Test + @SignatureRequiredTest(reason = "Need to mock the DHCP6 renew/rebind alarms") + public void testDhcp6ReplyForRebindWithNoPrefixAvailStatusCode() throws Exception { + prepareDhcp6PdRenewTest(); + + final InOrder inOrder = inOrder(mAlarm); + final Handler handler = mDependencies.mDhcp6Client.getHandler(); + final OnAlarmListener renewAlarm = expectAlarmSet(inOrder, "RENEW", 3600, handler); + final OnAlarmListener rebindAlarm = expectAlarmSet(inOrder, "REBIND", 4500, handler); + + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + Dhcp6Packet packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + handler.post(() -> rebindAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RebindPacket); + + // Reply with normal IA_PD. + final IpPrefix prefix = new IpPrefix("2001:db8:1::/64"); + final IaPrefixOption ipo = buildIaPrefixOption(prefix, 4500 /* preferred */, + 7200 /* valid */); + PrefixDelegation pd = new PrefixDelegation(packet.getIaId(), 1000 /* t1 */, + 2000 /* t2 */, Arrays.asList(ipo)); + ByteBuffer iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + // Trigger another Rebind message. + handler.post(() -> renewAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RenewPacket); + + handler.post(() -> rebindAlarm.onAlarm()); + HandlerUtils.waitForIdle(handler, TEST_TIMEOUT_MS); + + packet = getNextDhcp6Packet(); + assertTrue(packet instanceof Dhcp6RebindPacket); + + // Reply for Rebind with NoPrefixAvail status code, check if client will retransmit the + // Rebind message. + pd = new PrefixDelegation(packet.getIaId(), 3600 /* t1 */, + 4500 /* t2 */, new ArrayList<IaPrefixOption>(0) /* ipos */, + Dhcp6Packet.STATUS_NO_PREFIX_AVAIL); + iapd = pd.build(); + mPacketReader.sendResponse(buildDhcp6Reply(packet, iapd.array(), mClientMac, + (Inet6Address) mClientIpAddress, false /* rapidCommit */)); + + packet = getNextDhcp6Packet(TEST_WAIT_RENEW_REBIND_RETRANSMIT_MS); + assertTrue(packet instanceof Dhcp6RebindPacket); + } + + @Test @SignatureRequiredTest(reason = "InterfaceParams.getByName requires CAP_NET_ADMIN") public void testSendRtmDelAddressMethod() throws Exception { ProvisioningConfiguration config = new ProvisioningConfiguration.Builder()
diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index 464e4a1..ea29714 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp
@@ -21,7 +21,10 @@ java_defaults { name: "NetworkStackTestsDefaults", platform_apis: true, - srcs: ["src/**/*.java", "src/**/*.kt"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], resource_dirs: ["res"], static_libs: [ "androidx.test.ext.junit", @@ -39,7 +42,7 @@ ], defaults: [ "framework-connectivity-test-defaults", - "libnetworkstackutilsjni_deps" + "libnetworkstackutilsjni_deps", ], jni_libs: [ // For mockito extended @@ -66,6 +69,9 @@ static_libs: ["NetworkStackApiCurrentLib"], compile_multilib: "both", // Workaround for b/147785146 for mainline-presubmit jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Library containing the unit tests. This is used by the coverage test target to pull in the @@ -76,18 +82,24 @@ min_sdk_version: "30", defaults: ["NetworkStackTestsDefaults"], static_libs: ["NetworkStackApiStableLib"], - lint: { test: true }, + lint: { + test: true, + baseline_filename: "lint-baseline.xml", + }, visibility: [ "//packages/modules/NetworkStack/tests/integration", "//packages/modules/Connectivity/tests:__subpackages__", "//packages/modules/Connectivity/Tethering/tests:__subpackages__", - ] + ], } android_test { name: "NetworkStackTests", min_sdk_version: "30", - test_suites: ["general-tests", "mts"], + test_suites: [ + "general-tests", + "mts", + ], defaults: [ "NetworkStackTestsDefaults", "connectivity-mainline-presubmit-java-defaults", @@ -95,6 +107,9 @@ static_libs: ["NetworkStackApiStableLib"], compile_multilib: "both", jarjar_rules: ":NetworkStackJarJarRules", + lint: { + baseline_filename: "lint-baseline.xml", + }, } // Additional dependencies of libnetworkstackutilsjni that are not provided by the system when
diff --git a/tests/unit/src/android/net/apf/ApfTest.java b/tests/unit/src/android/net/apf/ApfTest.java index 0b61e04..4e1187b 100644 --- a/tests/unit/src/android/net/apf/ApfTest.java +++ b/tests/unit/src/android/net/apf/ApfTest.java
@@ -16,6 +16,7 @@ package android.net.apf; +import static android.net.apf.ApfGenerator.APF_VERSION_4; import static android.net.apf.ApfGenerator.Register.R0; import static android.net.apf.ApfGenerator.Register.R1; import static android.net.apf.ApfJniUtils.compareBpfApf; @@ -631,7 +632,7 @@ // Test jump if bytes not equal. gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); program = gen.generate(); assertEquals(6, program.length); assertEquals((13 << 3) | (1 << 1) | 0, program[0]); @@ -643,20 +644,20 @@ assertDrop(program, new byte[MIN_PKT_SIZE], 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); byte[] packet123 = {0,123,0,0,0,0,0,0,0,0,0,0,0,0,0}; assertPass(gen, packet123, 0); gen = new ApfGenerator(MIN_APF_VERSION); - gen.addJumpIfBytesNotEqual(R0, new byte[]{123}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{123}, gen.DROP_LABEL); assertDrop(gen, packet123, 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,30,4,5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 30, 4, 5}, gen.DROP_LABEL); byte[] packet12345 = {0,1,2,3,4,5,0,0,0,0,0,0,0,0,0}; assertDrop(gen, packet12345, 0); gen = new ApfGenerator(MIN_APF_VERSION); gen.addLoadImmediate(R0, 1); - gen.addJumpIfBytesNotEqual(R0, new byte[]{1,2,3,4,5}, gen.DROP_LABEL); + gen.addJumpIfBytesAtR0NotEqual(new byte[]{1, 2, 3, 4, 5}, gen.DROP_LABEL); assertPass(gen, packet12345, 0); } @@ -747,23 +748,23 @@ ApfGenerator gen; // Load data with no offset: lddw R0, [0 + r1] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadData(R0, 0); assertProgramEquals(new byte[]{LDDW_OP | SIZE0}, gen.generate()); // Store data with 8bit negative offset: lddw r0, [-42 + r1] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addStoreData(R0, -42); assertProgramEquals(new byte[]{STDW_OP | SIZE8, -42}, gen.generate()); // Store data to R1 with 16bit negative offset: stdw r1, [-0x1122 + r0] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addStoreData(R1, -0x1122); assertProgramEquals(new byte[]{STDW_OP | SIZE16 | R1_REG, (byte)0xEE, (byte)0xDE}, gen.generate()); // Load data to R1 with 32bit negative offset: lddw r1, [0xDEADBEEF + r0] - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadData(R1, 0xDEADBEEF); assertProgramEquals( new byte[]{LDDW_OP | SIZE32 | R1_REG, @@ -781,12 +782,12 @@ byte[] expected_data = data.clone(); // No memory access instructions: should leave the data segment untouched. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Expect value 0x87654321 to be stored starting from address -11 from the end of the // data buffer, in big-endian order. - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 0x87654321); gen.addLoadImmediate(R1, -5); gen.addStoreData(R0, -6); // -5 + -6 = -11 (offset +5 with data_len=16) @@ -803,7 +804,7 @@ @Test public void testApfDataRead() throws IllegalInstructionException, Exception { // Program that DROPs if address 10 (-6) contains 0x87654321. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R1, 1000); gen.addLoadData(R0, -1006); // 1000 + -1006 = -6 (offset +10 with data_len=16) gen.addJumpIfR0Equals(0x87654321, gen.DROP_LABEL); @@ -832,7 +833,7 @@ */ @Test public void testApfDataReadModifyWrite() throws IllegalInstructionException, Exception { - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R1, -22); gen.addLoadData(R0, 0); // Load from address 32 -22 + 0 = 10 gen.addAdd(0x78453412); // 87654321 + 78453412 = FFAA7733 @@ -859,35 +860,35 @@ byte[] expected_data = data; // Program that DROPs unconditionally. This is our the baseline. - ApfGenerator gen = new ApfGenerator(3); + ApfGenerator gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 3); gen.addLoadData(R1, 7); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // Same program as before, but this time we're trying to load past the end of the data. - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, 15); // 20 + 15 > 32 gen.addJump(gen.DROP_LABEL); // Not reached. assertDataMemoryContents(PASS, gen.generate(), packet, data, expected_data); // Subtracting an immediate should work... - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -4); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...and underflowing simply wraps around to the end of the buffer... - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -30); gen.addJump(gen.DROP_LABEL); assertDataMemoryContents(DROP, gen.generate(), packet, data, expected_data); // ...but doesn't allow accesses before the start of the buffer - gen = new ApfGenerator(3); + gen = new ApfGenerator(APF_VERSION_4); gen.addLoadImmediate(R0, 20); gen.addLoadData(R1, -1000); gen.addJump(gen.DROP_LABEL); // Not reached.
diff --git a/tests/unit/src/android/net/apf/ApfV5Test.kt b/tests/unit/src/android/net/apf/ApfV5Test.kt index 1b74c3e..dc8b7b6 100644 --- a/tests/unit/src/android/net/apf/ApfV5Test.kt +++ b/tests/unit/src/android/net/apf/ApfV5Test.kt
@@ -16,6 +16,8 @@ package android.net.apf import android.net.apf.ApfGenerator.IllegalInstructionException +import android.net.apf.ApfGenerator.Register.R0 +import android.net.apf.ApfGenerator.Register.R1 import androidx.test.filters.SmallTest import androidx.test.runner.AndroidJUnit4 import java.lang.IllegalArgumentException @@ -39,13 +41,50 @@ assertFailsWith<IllegalInstructionException> { gen.addCountAndPass(1000) } assertFailsWith<IllegalInstructionException> { gen.addTransmit() } assertFailsWith<IllegalInstructionException> { gen.addDiscard() } + assertFailsWith<IllegalInstructionException> { gen.addAllocateR0() } + assertFailsWith<IllegalInstructionException> { gen.addAllocate(100) } + assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(100) } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopy(100, 100) } + assertFailsWith<IllegalInstructionException> { gen.addDataCopy(100, 100) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R0) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU8(R1) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU16(R1) } + assertFailsWith<IllegalInstructionException> { gen.addWriteU32(R1) } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0LenR1() } + assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0LenR1() } + assertFailsWith<IllegalInstructionException> { gen.addPacketCopyFromR0(10) } + assertFailsWith<IllegalInstructionException> { gen.addDataCopyFromR0(10) } + assertFailsWith<IllegalInstructionException> { + gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL) } } @Test - fun testApfInstructionArgumentCheck() { + fun testDataInstructionMustComeFirst() { var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) - assertFailsWith<IllegalArgumentException> { gen.addCountAndPass(0) } - assertFailsWith<IllegalArgumentException> { gen.addCountAndDrop(0) } + gen.addAllocateR0() + assertFailsWith<IllegalInstructionException> { gen.addData(ByteArray(3) { 0x01 }) } + } + + @Test + fun testApfInstructionEncodingSizeCheck() { + var gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + assertFailsWith<IllegalArgumentException> { gen.addAllocate(65536) } + assertFailsWith<IllegalArgumentException> { gen.addAllocate(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(-1, 1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, 256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopy(1, -1) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(256) } + assertFailsWith<IllegalArgumentException> { gen.addPacketCopyFromR0(-1) } + assertFailsWith<IllegalArgumentException> { gen.addDataCopyFromR0(-1) } } @Test @@ -81,10 +120,18 @@ 0x03, 0xe8.toByte()), program) gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) - gen.addAlloc(ApfGenerator.Register.R0) + gen.addAllocateR0() + gen.addAllocate(1500) program = gen.generate() - assertContentEquals(byteArrayOf(encodeInstruction(21, 1, 0), 36), program) - assertContentEquals(arrayOf(" 0: alloc r0"), ApfJniUtils.disassembleApf(program)) + // encoding ALLOC opcode: opcode=21(EXT opcode number), imm=36(TRANS opcode number). + // R=0 means length stored in R0. R=1 means the length stored in imm1. + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 21, immLength = 1, register = 0), 36, + encodeInstruction(opcode = 21, immLength = 1, register = 1), 36, 0x05, + 0xDC.toByte()), + program) + // TODO: add back disassembling test check after we update the apf_disassembler + // assertContentEquals(arrayOf(" 0: alloc"), ApfJniUtils.disassembleApf(program)) gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) gen.addTransmit() @@ -100,63 +147,113 @@ // TODO: add back disassembling test check after we update the apf_disassembler // assertContentEquals(arrayOf(" 0: trans"), ApfJniUtils.disassembleApf(program)) - // TODO: add back when support write opcode -// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) -// gen.addWrite(0x01, 1) -// gen.addWrite(0x0102, 2) -// gen.addWrite(0x01020304, 4) -// program = gen.generate() -// assertContentEquals(byteArrayOf( -// encodeInstruction(24, 1, 0), 0x01, -// encodeInstruction(24, 2, 0), 0x01, 0x02, -// encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04 -// ), program) -// assertContentEquals(arrayOf( -// " 0: write 0x01", -// " 2: write 0x0102", -// " 5: write 0x01020304"), ApfJniUtils.disassembleApf(program)) -// -// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) -// gen.addWrite(ApfGenerator.Register.R0, 1) -// gen.addWrite(ApfGenerator.Register.R0, 2) -// gen.addWrite(ApfGenerator.Register.R0, 4) -// program = gen.generate() -// assertContentEquals(byteArrayOf( -// encodeInstruction(21, 1, 0), 38, -// encodeInstruction(21, 1, 0), 39, -// encodeInstruction(21, 1, 0), 40 -// ), program) -// assertContentEquals(arrayOf( -// " 0: write r0, 1", -// " 2: write r0, 2", -// " 4: write r0, 4"), ApfJniUtils.disassembleApf(program)) + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + val largeByteArray = ByteArray(256) { 0x01 } + gen.addData(largeByteArray) + program = gen.generate() + // encoding DATA opcode: opcode=14(JMP), R=1 + assertContentEquals(byteArrayOf( + encodeInstruction(opcode = 14, immLength = 2, register = 1), 0x01, 0x00) + + largeByteArray, program) - // TODO: add back when we properly support copy opcode -// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) -// gen.addDataCopy(1, 5) -// gen.addPacketCopy(1000, 255) -// program = gen.generate() -// assertContentEquals(byteArrayOf( -// encodeInstruction(25, 1, 1), 1, 5, -// encodeInstruction(25, 2, 0), -// 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), -// ), program) + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addWriteU8(0x01) + gen.addWriteU16(0x0102) + gen.addWriteU32(0x01020304) + gen.addWriteU8(0x00) + gen.addWriteU8(0x80) + gen.addWriteU16(0x0000) + gen.addWriteU16(0x8000) + gen.addWriteU32(0x00000000) + gen.addWriteU32(0x80000000) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(24, 1, 0), 0x01, + encodeInstruction(24, 2, 0), 0x01, 0x02, + encodeInstruction(24, 4, 0), 0x01, 0x02, 0x03, 0x04, + encodeInstruction(24, 1, 0), 0x00, + encodeInstruction(24, 1, 0), 0x80.toByte(), + encodeInstruction(24, 2, 0), 0x00, 0x00, + encodeInstruction(24, 2, 0), 0x80.toByte(), 0x00, + encodeInstruction(24, 4, 0), 0x00, 0x00, 0x00, 0x00, + encodeInstruction(24, 4, 0), 0x80.toByte(), 0x00, 0x00, + 0x00), program) + assertContentEquals(arrayOf( + " 0: write 0x01", + " 2: write 0x0102", + " 5: write 0x01020304", + " 10: write 0x00", + " 12: write 0x80", + " 14: write 0x0000", + " 17: write 0x8000", + " 20: write 0x00000000", + " 25: write 0x80000000"), + ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addWriteU8(R0) + gen.addWriteU16(R0) + gen.addWriteU32(R0) + gen.addWriteU8(R1) + gen.addWriteU16(R1) + gen.addWriteU32(R1) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 0), 38, + encodeInstruction(21, 1, 0), 39, + encodeInstruction(21, 1, 0), 40, + encodeInstruction(21, 1, 1), 38, + encodeInstruction(21, 1, 1), 39, + encodeInstruction(21, 1, 1), 40 + ), program) + // TODO: add back disassembling test check after we update the apf_disassembler // assertContentEquals(arrayOf( -// " 0: dcopy 1, 5", +// " 0: ewrite1 r0", +// " 2: ewrite2 r0", +// " 4: ewrite4 r0", +// " 6: ewrite1 r1", +// " 8: ewrite2 r1", +// " 10: ewrite4 r1"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addDataCopy(0, 10) + gen.addDataCopy(1, 5) + gen.addPacketCopy(1000, 255) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(25, 0, 1), 10, + encodeInstruction(25, 1, 1), 1, 5, + encodeInstruction(25, 2, 0), + 0x03.toByte(), 0xe8.toByte(), 0xff.toByte(), + ), program) + // TODO: add back disassembling test check after we update the apf_disassembler +// assertContentEquals(arrayOf( +// " 0: dcopy 0, 5", // " 3: pcopy 1000, 255"), ApfJniUtils.disassembleApf(program)) -// -// gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) -// gen.addDataCopy(ApfGenerator.Register.R1, 0, 5) -// gen.addPacketCopy(ApfGenerator.Register.R0, 1000, 255) -// program = gen.generate() -// assertContentEquals(byteArrayOf( -// encodeInstruction(21, 1, 1), 42, 0, 5, -// encodeInstruction(21, 2, 0), -// 0, 41, 0x03.toByte(), 0xe8.toByte(), 0xff.toByte() -// ), program) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addPacketCopyFromR0LenR1() + gen.addPacketCopyFromR0(5) + gen.addDataCopyFromR0LenR1() + gen.addDataCopyFromR0(5) + program = gen.generate() + assertContentEquals(byteArrayOf( + encodeInstruction(21, 1, 1), 41, + encodeInstruction(21, 1, 0), 41, 5, + encodeInstruction(21, 1, 1), 42, + encodeInstruction(21, 1, 0), 42, 5, + ), program) + // TODO: add back the following test case when implementing EPKTCOPY, EDATACOPY opcodes. // assertContentEquals(arrayOf( // " 0: dcopy [r1+0], 5", // " 4: pcopy [r0+1000], 255"), ApfJniUtils.disassembleApf(program)) + + gen = ApfGenerator(ApfGenerator.MIN_APF_VERSION_IN_DEV) + gen.addJumpIfBytesAtR0Equal(byteArrayOf('a'.code.toByte()), ApfGenerator.DROP_LABEL) + program = gen.generate() + assertContentEquals( + byteArrayOf(encodeInstruction(opcode = 20, immLength = 1, register = 1), + 1, 1, 'a'.code.toByte()), program) } private fun encodeInstruction(opcode: Int, immLength: Int, register: Int): Byte {
diff --git a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt index b5e806f..32cf464 100644 --- a/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt +++ b/tests/unit/src/android/net/dhcp6/Dhcp6PacketTest.kt
@@ -220,4 +220,168 @@ assertEquals(423, packet.prefixDelegation.minimalPreferredLifetime) assertEquals(43200, packet.prefixDelegation.minimalValidLifetime) } + + @Test + fun testStatusCodeOptionWithStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "0019000c000000000000000000000000" + + // Status code option: status code=NoPrefixAvail + "000d00150006" + + // Status code option: status message="no prefix available" + "6e6f2070726566697820617661696c61626c65" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode) + } + + @Test + fun testStatusCodeOptionWithoutStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "0019000c000000000000000000000000" + + // Status code option: status code=NoPrefixAvail + "000d00020006" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mStatusCode) + } + + @Test + fun testStatusCodeOptionInIaPdWithStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, status code=NoPrefixAvail, + // status message="no prefix available") + "00190025000000000000000000000000000d00150006" + + "6e6f2070726566697820617661696c61626c65" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode) + } + + @Test + fun testStatusCodeOptionInIaPdWithoutStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, status code=NoPrefixAvail) + "00190012000000000000000000000000000d00020006" + val bytes = HexDump.hexStringToByteArray(replyHex) + val packet = Dhcp6Packet.decode(bytes, bytes.size) + assertTrue(packet is Dhcp6ReplyPacket) + assertEquals(0, packet.mPrefixDelegation.iaid) + assertEquals(0, packet.mPrefixDelegation.t1) + assertEquals(0, packet.mPrefixDelegation.t2) + assertEquals(Dhcp6Packet.STATUS_NO_PREFIX_AVAIL, packet.mPrefixDelegation.statusCode) + } + + @Test + fun testStatusCodeOptionWithTruncatedStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // Status code option: len=21, status code=NoPrefixAvail + "000d00150006" + + // Status code option: truncated status message="no prefix available" + "6e6f2070726566697820617661696c6162" + val bytes = HexDump.hexStringToByteArray(replyHex) + assertThrows(Dhcp6Packet.ParseException::class.java) { + Dhcp6Packet.decode(bytes, bytes.size) + } + } + + @Test + fun testStatusCodeOptionInIaPdWithTruncatedStatusMessage() { + val replyHex = + // Reply, Transaction ID + "07000A47" + + // server identifier option(option_len=10) + "0002000A0003000186C9B26AED4D" + + // client identifier option(option_len=12) + "0001000C0003001B02FBBAFFFEB7BC71" + + // SOL_MAX_RT (don't support this option yet) + "005200040000003c" + + // Rapid Commit + "000e0000" + + // DNS recursive server (don't support this opton yet) + "00170010fdfd9ed6795000000000000000000001" + + // IA_PD option (t1=t2=0, empty prefix) + "00190025000000000000000000000000" + + // Status code option: len=21, status code=NoPrefixAvail + "000d00150006" + + // truncated status message="no prefix available") + "6e6f2070726566697820617661696c6162" + val bytes = HexDump.hexStringToByteArray(replyHex) + assertThrows(Dhcp6Packet.ParseException::class.java) { + Dhcp6Packet.decode(bytes, bytes.size) + } + } }
diff --git a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt index 6471f3a..4d57df5 100644 --- a/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt +++ b/tests/unit/src/android/net/ip/IpReachabilityMonitorTest.kt
@@ -56,7 +56,6 @@ import com.android.net.module.util.netlink.StructNdMsg.NUD_REACHABLE import com.android.net.module.util.netlink.StructNdMsg.NUD_STALE import com.android.networkstack.metrics.IpReachabilityMonitorMetrics -import com.android.networkstack.util.NetworkStackUtils.IP_REACHABILITY_MCAST_RESOLICIT_VERSION import com.android.testutils.makeNewNeighMessage import com.android.testutils.waitForIdle import java.io.FileDescriptor @@ -259,8 +258,6 @@ }.`when`(dependencies).makeIpNeighborMonitor(any(), any(), any()) doReturn(mIpReachabilityMonitorMetrics) .`when`(dependencies).getIpReachabilityMonitorMetrics() - doReturn(true).`when`(dependencies).isFeatureNotChickenedOut(any(), - eq(IP_REACHABILITY_MCAST_RESOLICIT_VERSION)) val monitorFuture = CompletableFuture<IpReachabilityMonitor>() // IpReachabilityMonitor needs to be started from the handler thread