From 57b3442c653fcc6211c70b02f97f4a9007fa0ab4 Mon Sep 17 00:00:00 2001 From: Xiao Ma Date: Thu, 1 Jul 2021 13:57:02 +0000 Subject: [PATCH] Migrate netlink-client to net-utils-device-common-netlink. Move netlink stuff to frameworks/libs/net/common/device, and build the source files as an individual libraray. NetworkStack module just depends on the net-utils-device-common-netlink. Besides, also fix the incorrect format detected by checkstyle_hook script such as missing java doc and make some public function as private, rename the variable and etc. Bug: 192535368 Test: atest NetworkStaticlibTests Change-Id: I00e7f30be1bc9ebc2e24d7cd53efc403d6ba3daa --- staticlibs/Android.bp | 36 +- .../com/android/net/module/util/HexDump.java | 2 +- .../com/android/net/module/util/Struct.java | 5 +- .../module/util/netlink/ConntrackMessage.java | 558 ++++++++++++++++++ .../module/util/netlink/InetDiagMessage.java | 231 ++++++++ .../net/module/util/netlink/NdOption.java | 83 +++ .../module/util/netlink/NduseroptMessage.java | 141 +++++ .../module/util/netlink/NetlinkConstants.java | 260 ++++++++ .../util/netlink/NetlinkErrorMessage.java | 64 ++ .../module/util/netlink/NetlinkMessage.java | 153 +++++ .../module/util/netlink/NetlinkSocket.java | 174 ++++++ .../netlink/RtNetlinkNeighborMessage.java | 237 ++++++++ .../util/netlink/StructInetDiagMsg.java | 62 ++ .../util/netlink/StructInetDiagReqV2.java | 100 ++++ .../util/netlink/StructInetDiagSockId.java | 90 +++ .../net/module/util/netlink/StructNdMsg.java | 198 +++++++ .../util/netlink/StructNdOptPref64.java | 169 ++++++ .../util/netlink/StructNdaCacheInfo.java | 133 +++++ .../module/util/netlink/StructNfGenMsg.java | 105 ++++ .../net/module/util/netlink/StructNlAttr.java | 315 ++++++++++ .../module/util/netlink/StructNlMsgErr.java | 77 +++ .../module/util/netlink/StructNlMsgHdr.java | 173 ++++++ .../util/structs/PrefixInformationOption.java | 3 +- .../util/netlink/ConntrackMessageTest.java | 433 ++++++++++++++ .../util/netlink/InetDiagSocketTest.java | 268 +++++++++ .../util/netlink/NduseroptMessageTest.java | 246 ++++++++ .../util/netlink/NetlinkConstantsTest.java | 132 +++++ .../util/netlink/NetlinkErrorMessageTest.java | 91 +++ .../util/netlink/NetlinkSocketTest.java | 242 ++++++++ .../netlink/RtNetlinkNeighborMessageTest.java | 207 +++++++ .../util/netlink/StructNdOptPref64Test.java | 200 +++++++ .../util/netlink/StructNlMsgHdrTest.java | 102 ++++ staticlibs/testutils/Android.bp | 1 + .../com/android/testutils/NetlinkTestUtils.kt | 102 ++++ 34 files changed, 5388 insertions(+), 5 deletions(-) create mode 100644 staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NdOption.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/NetlinkSocket.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java create mode 100644 staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkSocketTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java create mode 100644 staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java create mode 100644 staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt diff --git a/staticlibs/Android.bp b/staticlibs/Android.bp index 1371c8ccf3..4e7a9da7d0 100644 --- a/staticlibs/Android.bp +++ b/staticlibs/Android.bp @@ -34,7 +34,10 @@ package { java_library { name: "net-utils-device-common", srcs: [ - "device/com/android/net/module/util/**/*.java", + "device/com/android/net/module/util/DeviceConfigUtils.java", + "device/com/android/net/module/util/FdEventsReader.java", + "device/com/android/net/module/util/HexDump.java", + "device/com/android/net/module/util/PacketReader.java", // This library is used by system modules, for which the system health impact of Kotlin // has not yet been evaluated. Annotations may need jarjar'ing. // "src_devicecommon/**/*.kt", @@ -90,6 +93,37 @@ filegroup { ], } +java_library { + name: "net-utils-device-common-netlink", + // TODO: Ipv6Utils and Struct stuff could be separated out of th netlink library into + // an individual Struct library, and remove the net-utils-framework-common lib dependency. + // But there is no need doing this at the moment. + srcs: [ + "device/com/android/net/module/util/HexDump.java", + "device/com/android/net/module/util/Ipv6Utils.java", + "device/com/android/net/module/util/Struct.java", + "device/com/android/net/module/util/netlink/*.java", + "device/com/android/net/module/util/structs/*.java", + ], + sdk_version: "system_current", + min_sdk_version: "29", + visibility: [ + "//frameworks/libs/net/common/testutils:__subpackages__", + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ], + static_libs: [ + "net-utils-framework-common", + ], + libs: [ + "androidx.annotation_annotation", + ], + apex_available: [ + "com.android.tethering", + "//apex_available:platform", + ], +} + java_library { name: "net-utils-framework-common", srcs: [ diff --git a/staticlibs/device/com/android/net/module/util/HexDump.java b/staticlibs/device/com/android/net/module/util/HexDump.java index 6d36487b07..a27c0a3c95 100644 --- a/staticlibs/device/com/android/net/module/util/HexDump.java +++ b/staticlibs/device/com/android/net/module/util/HexDump.java @@ -16,7 +16,7 @@ package com.android.net.module.util; -import android.annotation.Nullable; +import androidx.annotation.Nullable; /** * Hex utility functions. diff --git a/staticlibs/device/com/android/net/module/util/Struct.java b/staticlibs/device/com/android/net/module/util/Struct.java index d95c303714..b43e2c458e 100644 --- a/staticlibs/device/com/android/net/module/util/Struct.java +++ b/staticlibs/device/com/android/net/module/util/Struct.java @@ -16,10 +16,11 @@ package com.android.net.module.util; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.net.MacAddress; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java new file mode 100644 index 0000000000..1763c04ba8 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/ConntrackMessage.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; + +import static com.android.net.module.util.netlink.StructNlAttr.findNextAttrOfType; +import static com.android.net.module.util.netlink.StructNlAttr.makeNestedType; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static java.nio.ByteOrder.BIG_ENDIAN; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * A NetlinkMessage subclass for netlink conntrack messages. + * + * see also: <linux_src>/include/uapi/linux/netfilter/nfnetlink_conntrack.h + * + * @hide + */ +public class ConntrackMessage extends NetlinkMessage { + public static final int STRUCT_SIZE = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + + // enum ctattr_type + public static final short CTA_TUPLE_ORIG = 1; + public static final short CTA_TUPLE_REPLY = 2; + public static final short CTA_STATUS = 3; + public static final short CTA_TIMEOUT = 7; + + // enum ctattr_tuple + public static final short CTA_TUPLE_IP = 1; + public static final short CTA_TUPLE_PROTO = 2; + + // enum ctattr_ip + public static final short CTA_IP_V4_SRC = 1; + public static final short CTA_IP_V4_DST = 2; + + // enum ctattr_l4proto + public static final short CTA_PROTO_NUM = 1; + public static final short CTA_PROTO_SRC_PORT = 2; + public static final short CTA_PROTO_DST_PORT = 3; + + // enum ip_conntrack_status + public static final int IPS_EXPECTED = 0x00000001; + public static final int IPS_SEEN_REPLY = 0x00000002; + public static final int IPS_ASSURED = 0x00000004; + public static final int IPS_CONFIRMED = 0x00000008; + public static final int IPS_SRC_NAT = 0x00000010; + public static final int IPS_DST_NAT = 0x00000020; + public static final int IPS_SEQ_ADJUST = 0x00000040; + public static final int IPS_SRC_NAT_DONE = 0x00000080; + public static final int IPS_DST_NAT_DONE = 0x00000100; + public static final int IPS_DYING = 0x00000200; + public static final int IPS_FIXED_TIMEOUT = 0x00000400; + public static final int IPS_TEMPLATE = 0x00000800; + public static final int IPS_UNTRACKED = 0x00001000; + public static final int IPS_HELPER = 0x00002000; + public static final int IPS_OFFLOAD = 0x00004000; + public static final int IPS_HW_OFFLOAD = 0x00008000; + + // ip_conntrack_status mask + // Interesting on the NAT conntrack session which has already seen two direction traffic. + // TODO: Probably IPS_{SRC, DST}_NAT_DONE are also interesting. + public static final int ESTABLISHED_MASK = IPS_CONFIRMED | IPS_ASSURED | IPS_SEEN_REPLY + | IPS_SRC_NAT; + // Interesting on the established NAT conntrack session which is dying. + public static final int DYING_MASK = ESTABLISHED_MASK | IPS_DYING; + + /** + * A tuple for the conntrack connection information. + * + * see also CTA_TUPLE_ORIG and CTA_TUPLE_REPLY. + */ + public static class Tuple { + public final Inet4Address srcIp; + public final Inet4Address dstIp; + + // Both port and protocol number are unsigned numbers stored in signed integers, and that + // callers that want to compare them to integers should either cast those integers, or + // convert them to unsigned using Byte.toUnsignedInt() and Short.toUnsignedInt(). + public final short srcPort; + public final short dstPort; + public final byte protoNum; + + public Tuple(TupleIpv4 ip, TupleProto proto) { + this.srcIp = ip.src; + this.dstIp = ip.dst; + this.srcPort = proto.srcPort; + this.dstPort = proto.dstPort; + this.protoNum = proto.protoNum; + } + + @Override + @VisibleForTesting + public boolean equals(Object o) { + if (!(o instanceof Tuple)) return false; + Tuple that = (Tuple) o; + return Objects.equals(this.srcIp, that.srcIp) + && Objects.equals(this.dstIp, that.dstIp) + && this.srcPort == that.srcPort + && this.dstPort == that.dstPort + && this.protoNum == that.protoNum; + } + + @Override + public int hashCode() { + return Objects.hash(srcIp, dstIp, srcPort, dstPort, protoNum); + } + + @Override + public String toString() { + final String srcIpStr = (srcIp == null) ? "null" : srcIp.getHostAddress(); + final String dstIpStr = (dstIp == null) ? "null" : dstIp.getHostAddress(); + final String protoStr = NetlinkConstants.stringForProtocol(protoNum); + + return "Tuple{" + + protoStr + ": " + + srcIpStr + ":" + Short.toUnsignedInt(srcPort) + " -> " + + dstIpStr + ":" + Short.toUnsignedInt(dstPort) + + "}"; + } + } + + /** + * A tuple for the conntrack connection address. + * + * see also CTA_TUPLE_IP. + */ + public static class TupleIpv4 { + public final Inet4Address src; + public final Inet4Address dst; + + public TupleIpv4(Inet4Address src, Inet4Address dst) { + this.src = src; + this.dst = dst; + } + } + + /** + * A tuple for the conntrack connection protocol. + * + * see also CTA_TUPLE_PROTO. + */ + public static class TupleProto { + public final byte protoNum; + public final short srcPort; + public final short dstPort; + + public TupleProto(byte protoNum, short srcPort, short dstPort) { + this.protoNum = protoNum; + this.srcPort = srcPort; + this.dstPort = dstPort; + } + } + + /** + * Create a netlink message to refresh IPv4 conntrack entry timeout. + */ + public static byte[] newIPv4TimeoutUpdateRequest( + int proto, Inet4Address src, int sport, Inet4Address dst, int dport, int timeoutSec) { + // *** STYLE WARNING *** + // + // Code below this point uses extra block indentation to highlight the + // packing of nested tuple netlink attribute types. + final StructNlAttr ctaTupleOrig = new StructNlAttr(CTA_TUPLE_ORIG, + new StructNlAttr(CTA_TUPLE_IP, + new StructNlAttr(CTA_IP_V4_SRC, src), + new StructNlAttr(CTA_IP_V4_DST, dst)), + new StructNlAttr(CTA_TUPLE_PROTO, + new StructNlAttr(CTA_PROTO_NUM, (byte) proto), + new StructNlAttr(CTA_PROTO_SRC_PORT, (short) sport, BIG_ENDIAN), + new StructNlAttr(CTA_PROTO_DST_PORT, (short) dport, BIG_ENDIAN))); + + final StructNlAttr ctaTimeout = new StructNlAttr(CTA_TIMEOUT, timeoutSec, BIG_ENDIAN); + + final int payloadLength = ctaTupleOrig.getAlignedLength() + ctaTimeout.getAlignedLength(); + final byte[] bytes = new byte[STRUCT_SIZE + payloadLength]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final ConntrackMessage ctmsg = new ConntrackMessage(); + ctmsg.mHeader.nlmsg_len = bytes.length; + ctmsg.mHeader.nlmsg_type = (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8) + | NetlinkConstants.IPCTNL_MSG_CT_NEW; + ctmsg.mHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE; + ctmsg.mHeader.nlmsg_seq = 1; + ctmsg.pack(byteBuffer); + + ctaTupleOrig.pack(byteBuffer); + ctaTimeout.pack(byteBuffer); + + return bytes; + } + + /** + * Parses a netfilter conntrack message from a {@link ByteBuffer}. + * + * @param header the netlink message header. + * @param byteBuffer The buffer from which to parse the netfilter conntrack message. + * @return the parsed netfilter conntrack message, or {@code null} if the netfilter conntrack + * message could not be parsed successfully (for example, if it was truncated). + */ + public static ConntrackMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) { + // Just build the netlink header and netfilter header for now and pretend the whole message + // was consumed. + // TODO: Parse the conntrack attributes. + final StructNfGenMsg nfGenMsg = StructNfGenMsg.parse(byteBuffer); + if (nfGenMsg == null) { + return null; + } + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_STATUS, byteBuffer); + int status = 0; + if (nlAttr != null) { + status = nlAttr.getValueAsBe32(0); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(CTA_TIMEOUT, byteBuffer); + int timeoutSec = 0; + if (nlAttr != null) { + timeoutSec = nlAttr.getValueAsBe32(0); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_ORIG), byteBuffer); + Tuple tupleOrig = null; + if (nlAttr != null) { + tupleOrig = parseTuple(nlAttr.getValueAsByteBuffer()); + } + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_REPLY), byteBuffer); + Tuple tupleReply = null; + if (nlAttr != null) { + tupleReply = parseTuple(nlAttr.getValueAsByteBuffer()); + } + + // Advance to the end of the message. + byteBuffer.position(baseOffset); + final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; + final int kAdditionalSpace = NetlinkConstants.alignedLengthOf( + header.nlmsg_len - kMinConsumed); + if (byteBuffer.remaining() < kAdditionalSpace) { + return null; + } + byteBuffer.position(baseOffset + kAdditionalSpace); + + return new ConntrackMessage(header, nfGenMsg, tupleOrig, tupleReply, status, timeoutSec); + } + + /** + * Parses a conntrack tuple from a {@link ByteBuffer}. + * + * The attribute parsing is interesting on: + * - CTA_TUPLE_IP + * CTA_IP_V4_SRC + * CTA_IP_V4_DST + * - CTA_TUPLE_PROTO + * CTA_PROTO_NUM + * CTA_PROTO_SRC_PORT + * CTA_PROTO_DST_PORT + * + * Assume that the minimum size is the sum of CTA_TUPLE_IP (size: 20) and CTA_TUPLE_PROTO + * (size: 28). Here is an example for an expected CTA_TUPLE_ORIG message in raw data: + * +--------------------------------------------------------------------------------------+ + * | CTA_TUPLE_ORIG | + * +--------------------------+-----------------------------------------------------------+ + * | 1400 | nla_len = 20 | + * | 0180 | nla_type = nested CTA_TUPLE_IP | + * | 0800 0100 C0A8500C | nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 | + * | 0800 0200 8C700874 | nla_type=CTA_IP_V4_DST, ip=140.112.8.116 | + * | 1C00 | nla_len = 28 | + * | 0280 | nla_type = nested CTA_TUPLE_PROTO | + * | 0500 0100 06 000000 | nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) | + * | 0600 0200 F3F1 0000 | nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) | + * | 0600 0300 01BB 0000 | nla_type=CTA_PROTO_DST_PORT, port=433 (big endian) | + * +--------------------------+-----------------------------------------------------------+ + * + * The position of the byte buffer doesn't set to the end when the function returns. It is okay + * because the caller ConntrackMessage#parse has passed a copy which is used for this parser + * only. Moreover, the parser behavior is the same as other existing netlink struct class + * parser. Ex: StructInetDiagMsg#parse. + */ + @Nullable + private static Tuple parseTuple(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + TupleIpv4 tupleIpv4 = null; + TupleProto tupleProto = null; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_IP), byteBuffer); + if (nlAttr != null) { + tupleIpv4 = parseTupleIpv4(nlAttr.getValueAsByteBuffer()); + } + if (tupleIpv4 == null) return null; + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(makeNestedType(CTA_TUPLE_PROTO), byteBuffer); + if (nlAttr != null) { + tupleProto = parseTupleProto(nlAttr.getValueAsByteBuffer()); + } + if (tupleProto == null) return null; + + return new Tuple(tupleIpv4, tupleProto); + } + + @Nullable + private static Inet4Address castToInet4Address(@Nullable InetAddress address) { + if (address == null || !(address instanceof Inet4Address)) return null; + return (Inet4Address) address; + } + + @Nullable + private static TupleIpv4 parseTupleIpv4(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + Inet4Address src = null; + Inet4Address dst = null; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_IP_V4_SRC, byteBuffer); + if (nlAttr != null) { + src = castToInet4Address(nlAttr.getValueAsInetAddress()); + } + if (src == null) return null; + + byteBuffer.position(baseOffset); + nlAttr = findNextAttrOfType(CTA_IP_V4_DST, byteBuffer); + if (nlAttr != null) { + dst = castToInet4Address(nlAttr.getValueAsInetAddress()); + } + if (dst == null) return null; + + return new TupleIpv4(src, dst); + } + + @Nullable + private static TupleProto parseTupleProto(@Nullable ByteBuffer byteBuffer) { + if (byteBuffer == null) return null; + + byte protoNum = 0; + short srcPort = 0; + short dstPort = 0; + + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = findNextAttrOfType(CTA_PROTO_NUM, byteBuffer); + if (nlAttr != null) { + protoNum = nlAttr.getValueAsByte((byte) 0); + } + if (!(protoNum == IPPROTO_TCP || protoNum == IPPROTO_UDP)) return null; + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_SRC_PORT, byteBuffer); + if (nlAttr != null) { + srcPort = nlAttr.getValueAsBe16((short) 0); + } + if (srcPort == 0) return null; + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(CTA_PROTO_DST_PORT, byteBuffer); + if (nlAttr != null) { + dstPort = nlAttr.getValueAsBe16((short) 0); + } + if (dstPort == 0) return null; + + return new TupleProto(protoNum, srcPort, dstPort); + } + + /** + * Netfilter header. + */ + public final StructNfGenMsg nfGenMsg; + /** + * Original direction conntrack tuple. + * + * The tuple is determined by the parsed attribute value CTA_TUPLE_ORIG, or null if the + * tuple could not be parsed successfully (for example, if it was truncated or absent). + */ + @Nullable + public final Tuple tupleOrig; + /** + * Reply direction conntrack tuple. + * + * The tuple is determined by the parsed attribute value CTA_TUPLE_REPLY, or null if the + * tuple could not be parsed successfully (for example, if it was truncated or absent). + */ + @Nullable + public final Tuple tupleReply; + /** + * Connection status. A bitmask of ip_conntrack_status enum flags. + * + * The status is determined by the parsed attribute value CTA_STATUS, or 0 if the status could + * not be parsed successfully (for example, if it was truncated or absent). For the message + * from kernel, the valid status is non-zero. For the message from user space, the status may + * be 0 (absent). + */ + public final int status; + /** + * Conntrack timeout. + * + * The timeout is determined by the parsed attribute value CTA_TIMEOUT, or 0 if the timeout + * could not be parsed successfully (for example, if it was truncated or absent). For + * IPCTNL_MSG_CT_NEW event, the valid timeout is non-zero. For IPCTNL_MSG_CT_DELETE event, the + * timeout is 0 (absent). + */ + public final int timeoutSec; + + private ConntrackMessage() { + super(new StructNlMsgHdr()); + nfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET); + + // This constructor is only used by #newIPv4TimeoutUpdateRequest which doesn't use these + // data member for packing message. Simply fill them to null or 0. + tupleOrig = null; + tupleReply = null; + status = 0; + timeoutSec = 0; + } + + private ConntrackMessage(@NonNull StructNlMsgHdr header, @NonNull StructNfGenMsg nfGenMsg, + @Nullable Tuple tupleOrig, @Nullable Tuple tupleReply, int status, int timeoutSec) { + super(header); + this.nfGenMsg = nfGenMsg; + this.tupleOrig = tupleOrig; + this.tupleReply = tupleReply; + this.status = status; + this.timeoutSec = timeoutSec; + } + + /** + * Write a netfilter message to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + mHeader.pack(byteBuffer); + nfGenMsg.pack(byteBuffer); + } + + public short getMessageType() { + return (short) (getHeader().nlmsg_type & ~(NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8)); + } + + /** + * Convert an ip conntrack status to a string. + */ + public static String stringForIpConntrackStatus(int flags) { + final StringBuilder sb = new StringBuilder(); + + if ((flags & IPS_EXPECTED) != 0) { + sb.append("IPS_EXPECTED"); + } + if ((flags & IPS_SEEN_REPLY) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SEEN_REPLY"); + } + if ((flags & IPS_ASSURED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_ASSURED"); + } + if ((flags & IPS_CONFIRMED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_CONFIRMED"); + } + if ((flags & IPS_SRC_NAT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SRC_NAT"); + } + if ((flags & IPS_DST_NAT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DST_NAT"); + } + if ((flags & IPS_SEQ_ADJUST) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SEQ_ADJUST"); + } + if ((flags & IPS_SRC_NAT_DONE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_SRC_NAT_DONE"); + } + if ((flags & IPS_DST_NAT_DONE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DST_NAT_DONE"); + } + if ((flags & IPS_DYING) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_DYING"); + } + if ((flags & IPS_FIXED_TIMEOUT) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_FIXED_TIMEOUT"); + } + if ((flags & IPS_TEMPLATE) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_TEMPLATE"); + } + if ((flags & IPS_UNTRACKED) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_UNTRACKED"); + } + if ((flags & IPS_HELPER) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_HELPER"); + } + if ((flags & IPS_OFFLOAD) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_OFFLOAD"); + } + if ((flags & IPS_HW_OFFLOAD) != 0) { + if (sb.length() > 0) sb.append("|"); + sb.append("IPS_HW_OFFLOAD"); + } + return sb.toString(); + } + + @Override + public String toString() { + return "ConntrackMessage{" + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_NETFILTER)) + + "}, " + + "nfgenmsg{" + nfGenMsg + "}, " + + "tuple_orig{" + tupleOrig + "}, " + + "tuple_reply{" + tupleReply + "}, " + + "status{" + status + "(" + stringForIpConntrackStatus(status) + ")" + "}, " + + "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}" + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java new file mode 100644 index 0000000000..7b200e7597 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.os.Process.INVALID_UID; +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.NETLINK_INET_DIAG; + +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; +import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import android.net.util.SocketUtils; +import android.system.ErrnoException; +import android.util.Log; + +import androidx.annotation.Nullable; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for netlink inet_diag messages. + * + * see also: <linux_src>/include/uapi/linux/inet_diag.h + * + * @hide + */ +public class InetDiagMessage extends NetlinkMessage { + public static final String TAG = "InetDiagMessage"; + private static final int TIMEOUT_MS = 500; + + /** + * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException} + * if local and remote are not both null or both non-null. + */ + public static byte[] inetDiagReqV2(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags) { + return inetDiagReqV2(protocol, local, remote, family, flags, 0 /* pad */, + 0 /* idiagExt */, StructInetDiagReqV2.INET_DIAG_REQ_V2_ALL_STATES); + } + + /** + * Construct an inet_diag_req_v2 message. This method will throw {@code NullPointerException} + * if local and remote are not both null or both non-null. + * + * @param protocol the request protocol type. This should be set to one of IPPROTO_TCP, + * IPPROTO_UDP, or IPPROTO_UDPLITE. + * @param local local socket address of the target socket. This will be packed into a + * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of + * local or remote address is null. + * @param remote remote socket address of the target socket. This will be packed into a + * {@Code StructInetDiagSockId}. Request to diagnose for all sockets if both of + * local or remote address is null. + * @param family the ip family of the request message. This should be set to either AF_INET or + * AF_INET6 for IPv4 or IPv6 sockets respectively. + * @param flags message flags. See <linux_src>/include/uapi/linux/netlink.h. + * @param pad for raw socket protocol specification. + * @param idiagExt a set of flags defining what kind of extended information to report. + * @param state a bit mask that defines a filter of socket states. + * + * @return bytes array representation of the message + */ + public static byte[] inetDiagReqV2(int protocol, @Nullable InetSocketAddress local, + @Nullable InetSocketAddress remote, int family, short flags, int pad, int idiagExt, + int state) throws NullPointerException { + final byte[] bytes = new byte[StructNlMsgHdr.STRUCT_SIZE + StructInetDiagReqV2.STRUCT_SIZE]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlMsgHdr = new StructNlMsgHdr(); + nlMsgHdr.nlmsg_len = bytes.length; + nlMsgHdr.nlmsg_type = SOCK_DIAG_BY_FAMILY; + nlMsgHdr.nlmsg_flags = flags; + nlMsgHdr.pack(byteBuffer); + final StructInetDiagReqV2 inetDiagReqV2 = + new StructInetDiagReqV2(protocol, local, remote, family, pad, idiagExt, state); + + inetDiagReqV2.pack(byteBuffer); + return bytes; + } + + public StructInetDiagMsg mStructInetDiagMsg; + + private InetDiagMessage(StructNlMsgHdr header) { + super(header); + mStructInetDiagMsg = new StructInetDiagMsg(); + } + + /** + * Parse an inet_diag_req_v2 message from buffer. + */ + public static InetDiagMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) { + final InetDiagMessage msg = new InetDiagMessage(header); + msg.mStructInetDiagMsg = StructInetDiagMsg.parse(byteBuffer); + return msg; + } + + private static int lookupUidByFamily(int protocol, InetSocketAddress local, + InetSocketAddress remote, int family, short flags, + FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + byte[] msg = inetDiagReqV2(protocol, local, remote, family, flags); + NetlinkSocket.sendMessage(fd, msg, 0, msg.length, TIMEOUT_MS); + ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TIMEOUT_MS); + + final NetlinkMessage nlMsg = NetlinkMessage.parse(response, NETLINK_INET_DIAG); + final StructNlMsgHdr hdr = nlMsg.getHeader(); + if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + return INVALID_UID; + } + if (nlMsg instanceof InetDiagMessage) { + return ((InetDiagMessage) nlMsg).mStructInetDiagMsg.idiag_uid; + } + return INVALID_UID; + } + + private static final int[] FAMILY = {AF_INET6, AF_INET}; + + private static int lookupUid(int protocol, InetSocketAddress local, + InetSocketAddress remote, FileDescriptor fd) + throws ErrnoException, InterruptedIOException { + int uid; + + for (int family : FAMILY) { + /** + * For exact match lookup, swap local and remote for UDP lookups due to kernel + * bug which will not be fixed. See aosp/755889 and + * https://www.mail-archive.com/netdev@vger.kernel.org/msg248638.html + */ + if (protocol == IPPROTO_UDP) { + uid = lookupUidByFamily(protocol, remote, local, family, NLM_F_REQUEST, fd); + } else { + uid = lookupUidByFamily(protocol, local, remote, family, NLM_F_REQUEST, fd); + } + if (uid != INVALID_UID) { + return uid; + } + } + + /** + * For UDP it's possible for a socket to send packets to arbitrary destinations, even if the + * socket is not connected (and even if the socket is connected to a different destination). + * If we want this API to work for such packets, then on miss we need to do a second lookup + * with only the local address and port filled in. + * Always use flags == NLM_F_REQUEST | NLM_F_DUMP for wildcard. + */ + if (protocol == IPPROTO_UDP) { + try { + InetSocketAddress wildcard = new InetSocketAddress( + Inet6Address.getByName("::"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET6, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + wildcard = new InetSocketAddress(Inet4Address.getByName("0.0.0.0"), 0); + uid = lookupUidByFamily(protocol, local, wildcard, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP), fd); + if (uid != INVALID_UID) { + return uid; + } + } catch (UnknownHostException e) { + Log.e(TAG, e.toString()); + } + } + return INVALID_UID; + } + + /** + * Use an inet_diag socket to look up the UID associated with the input local and remote + * address/port and protocol of a connection. + */ + public static int getConnectionOwnerUid(int protocol, InetSocketAddress local, + InetSocketAddress remote) { + int uid = INVALID_UID; + FileDescriptor fd = null; + try { + fd = NetlinkSocket.forProto(NETLINK_INET_DIAG); + NetlinkSocket.connectToKernel(fd); + uid = lookupUid(protocol, local, remote, fd); + } catch (ErrnoException | SocketException | IllegalArgumentException + | InterruptedIOException e) { + Log.e(TAG, e.toString()); + } finally { + if (fd != null) { + try { + SocketUtils.closeSocket(fd); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + } + } + return uid; + } + + @Override + public String toString() { + return "InetDiagMessage{ " + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(NETLINK_INET_DIAG)) + "}, " + + "inet_diag_msg{" + + (mStructInetDiagMsg == null ? "" : mStructInetDiagMsg.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NdOption.java b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java new file mode 100644 index 0000000000..50a3496660 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NdOption.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import java.nio.ByteBuffer; + +/** + * Base class for IPv6 neighbour discovery options. + */ +public class NdOption { + public static final int STRUCT_SIZE = 2; + + /** The option type. */ + public final byte type; + /** The length of the option in 8-byte units. Actually an unsigned 8-bit integer */ + public final int length; + + /** Constructs a new NdOption. */ + NdOption(byte type, int length) { + this.type = type; + this.length = length; + } + + /** + * Parses a neighbour discovery option. + * + * Parses (and consumes) the option if it is of a known type. If the option is of an unknown + * type, advances the buffer (so the caller can continue parsing if desired) and returns + * {@link #UNKNOWN}. If the option claims a length of 0, returns null because parsing cannot + * continue. + * + * No checks are performed on the length other than ensuring it is not 0, so if a caller wants + * to deal with options that might overflow the structure that contains them, it must explicitly + * set the buffer's limit to the position at which that structure ends. + * + * @param buf the buffer to parse. + * @return a subclass of {@link NdOption}, or {@code null} for an unknown or malformed option. + */ + public static NdOption parse(ByteBuffer buf) { + if (buf == null || buf.remaining() < STRUCT_SIZE) return null; + + // Peek the type without advancing the buffer. + byte type = buf.get(buf.position()); + int length = Byte.toUnsignedInt(buf.get(buf.position() + 1)); + if (length == 0) return null; + + switch (type) { + case StructNdOptPref64.TYPE: + return StructNdOptPref64.parse(buf); + + default: + int newPosition = Math.min(buf.limit(), buf.position() + length * 8); + buf.position(newPosition); + return UNKNOWN; + } + } + + void writeToByteBuffer(ByteBuffer buf) { + buf.put(type); + buf.put((byte) length); + } + + @Override + public String toString() { + return String.format("NdOption(%d, %d)", Byte.toUnsignedInt(type), length); + } + + public static final NdOption UNKNOWN = new NdOption((byte) 0, 0); +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java new file mode 100644 index 0000000000..4e3b9f2da6 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NduseroptMessage.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET6; + +import androidx.annotation.NonNull; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for RTM_NEWNDUSEROPT messages. + */ +public class NduseroptMessage extends NetlinkMessage { + public static final int STRUCT_SIZE = 16; + + static final int NDUSEROPT_SRCADDR = 1; + + /** The address family. Presumably always AF_INET6. */ + public final byte family; + /** + * The total length in bytes of the options that follow this structure. + * Actually a 16-bit unsigned integer. + */ + public final int opts_len; + /** The interface index on which the options were received. */ + public final int ifindex; + /** The ICMP type of the packet that contained the options. */ + public final byte icmp_type; + /** The ICMP code of the packet that contained the options. */ + public final byte icmp_code; + + /** + * ND option that was in this message. + * Even though the length field is called "opts_len", the kernel only ever sends one option per + * message. It is unlikely that this will ever change as it would break existing userspace code. + * But if it does, we can simply update this code, since userspace is typically newer than the + * kernel. + */ + public final NdOption option; + + /** The IP address that sent the packet containing the option. */ + public final InetAddress srcaddr; + + NduseroptMessage(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) + throws UnknownHostException { + super(header); + + // The structure itself. + buf.order(ByteOrder.nativeOrder()); // Restored in the finally clause inside parse(). + final int start = buf.position(); + family = buf.get(); + buf.get(); // Skip 1 byte of padding. + opts_len = Short.toUnsignedInt(buf.getShort()); + ifindex = buf.getInt(); + icmp_type = buf.get(); + icmp_code = buf.get(); + buf.position(buf.position() + 6); // Skip 6 bytes of padding. + + // The ND option. + // Ensure we don't read past opts_len even if the option length is invalid. + // Note that this check is not really necessary since if the option length is not valid, + // this struct won't be very useful to the caller. + buf.order(ByteOrder.BIG_ENDIAN); + int oldLimit = buf.limit(); + buf.limit(start + STRUCT_SIZE + opts_len); + try { + option = NdOption.parse(buf); + } finally { + buf.limit(oldLimit); + } + + // The source address. + int newPosition = start + STRUCT_SIZE + opts_len; + if (newPosition >= buf.limit()) { + throw new IllegalArgumentException("ND options extend past end of buffer"); + } + buf.position(newPosition); + + StructNlAttr nla = StructNlAttr.parse(buf); + if (nla == null || nla.nla_type != NDUSEROPT_SRCADDR || nla.nla_value == null) { + throw new IllegalArgumentException("Invalid source address in ND useropt"); + } + if (family == AF_INET6) { + // InetAddress.getByAddress only looks at the ifindex if the address type needs one. + srcaddr = Inet6Address.getByAddress(null /* hostname */, nla.nla_value, ifindex); + } else { + srcaddr = InetAddress.getByAddress(nla.nla_value); + } + } + + /** + * Parses a StructNduseroptmsg from a {@link ByteBuffer}. + * + * @param header the netlink message header. + * @param buf The buffer from which to parse the option. The buffer's byte order must be + * {@link java.nio.ByteOrder#BIG_ENDIAN}. + * @return the parsed option, or {@code null} if the option could not be parsed successfully + * (for example, if it was truncated, or if the prefix length code was wrong). + */ + public static NduseroptMessage parse(@NonNull StructNlMsgHdr header, @NonNull ByteBuffer buf) { + if (buf == null || buf.remaining() < STRUCT_SIZE) return null; + ByteOrder oldOrder = buf.order(); + try { + return new NduseroptMessage(header, buf); + } catch (IllegalArgumentException | UnknownHostException | BufferUnderflowException e) { + // Not great, but better than throwing an exception that might crash the caller. + // Convention in this package is that null indicates that the option was truncated, so + // callers must already handle it. + return null; + } finally { + buf.order(oldOrder); + } + } + + @Override + public String toString() { + return String.format("Nduseroptmsg(%d, %d, %d, %d, %d, %s)", + family, opts_len, ifindex, Byte.toUnsignedInt(icmp_type), + Byte.toUnsignedInt(icmp_code), srcaddr.getHostAddress()); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java new file mode 100644 index 0000000000..cf9d2c54b2 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkConstants.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; + +import java.nio.ByteBuffer; + +/** + * Various constants and static helper methods for netlink communications. + * + * Values taken from: + * + * include/uapi/linux/netfilter/nfnetlink.h + * include/uapi/linux/netfilter/nfnetlink_conntrack.h + * include/uapi/linux/netlink.h + * include/uapi/linux/rtnetlink.h + * + * @hide + */ +public class NetlinkConstants { + private NetlinkConstants() {} + + public static final int NLA_ALIGNTO = 4; + /** + * Flag for dumping struct tcp_info. + * Corresponding to enum definition in external/strace/linux/inet_diag.h. + */ + public static final int INET_DIAG_MEMINFO = 1; + + public static final int SOCKDIAG_MSG_HEADER_SIZE = + StructNlMsgHdr.STRUCT_SIZE + StructInetDiagMsg.STRUCT_SIZE; + + /** + * Get the aligned length based on a Short type number. + */ + public static final int alignedLengthOf(short length) { + final int intLength = (int) length & 0xffff; + return alignedLengthOf(intLength); + } + + /** + * Get the aligned length based on a Integer type number. + */ + public static final int alignedLengthOf(int length) { + if (length <= 0) return 0; + return (((length + NLA_ALIGNTO - 1) / NLA_ALIGNTO) * NLA_ALIGNTO); + } + + /** + * Convert a address family type to a string. + */ + public static String stringForAddressFamily(int family) { + if (family == OsConstants.AF_INET) return "AF_INET"; + if (family == OsConstants.AF_INET6) return "AF_INET6"; + if (family == OsConstants.AF_NETLINK) return "AF_NETLINK"; + return String.valueOf(family); + } + + /** + * Convert a protocol type to a string. + */ + public static String stringForProtocol(int protocol) { + if (protocol == OsConstants.IPPROTO_TCP) return "IPPROTO_TCP"; + if (protocol == OsConstants.IPPROTO_UDP) return "IPPROTO_UDP"; + return String.valueOf(protocol); + } + + /** + * Convert a byte array to a hexadecimal string. + */ + public static String hexify(byte[] bytes) { + if (bytes == null) return "(null)"; + return toHexString(bytes, 0, bytes.length); + } + + /** + * Convert a {@link ByteBuffer} to a hexadecimal string. + */ + public static String hexify(ByteBuffer buffer) { + if (buffer == null) return "(null)"; + return toHexString( + buffer.array(), buffer.position(), buffer.remaining()); + } + + // Known values for struct nlmsghdr nlm_type. + public static final short NLMSG_NOOP = 1; // Nothing + public static final short NLMSG_ERROR = 2; // Error + public static final short NLMSG_DONE = 3; // End of a dump + public static final short NLMSG_OVERRUN = 4; // Data lost + public static final short NLMSG_MAX_RESERVED = 15; // Max reserved value + + public static final short RTM_NEWLINK = 16; + public static final short RTM_DELLINK = 17; + public static final short RTM_GETLINK = 18; + public static final short RTM_SETLINK = 19; + public static final short RTM_NEWADDR = 20; + public static final short RTM_DELADDR = 21; + public static final short RTM_GETADDR = 22; + public static final short RTM_NEWROUTE = 24; + public static final short RTM_DELROUTE = 25; + public static final short RTM_GETROUTE = 26; + public static final short RTM_NEWNEIGH = 28; + public static final short RTM_DELNEIGH = 29; + public static final short RTM_GETNEIGH = 30; + public static final short RTM_NEWRULE = 32; + public static final short RTM_DELRULE = 33; + public static final short RTM_GETRULE = 34; + public static final short RTM_NEWNDUSEROPT = 68; + + // Netfilter netlink message types are presented by two bytes: high byte subsystem and + // low byte operation. See the macro NFNL_SUBSYS_ID and NFNL_MSG_TYPE in + // include/uapi/linux/netfilter/nfnetlink.h + public static final short NFNL_SUBSYS_CTNETLINK = 1; + + public static final short IPCTNL_MSG_CT_NEW = 0; + public static final short IPCTNL_MSG_CT_GET = 1; + public static final short IPCTNL_MSG_CT_DELETE = 2; + public static final short IPCTNL_MSG_CT_GET_CTRZERO = 3; + public static final short IPCTNL_MSG_CT_GET_STATS_CPU = 4; + public static final short IPCTNL_MSG_CT_GET_STATS = 5; + public static final short IPCTNL_MSG_CT_GET_DYING = 6; + public static final short IPCTNL_MSG_CT_GET_UNCONFIRMED = 7; + + /* see include/uapi/linux/sock_diag.h */ + public static final short SOCK_DIAG_BY_FAMILY = 20; + + // Netlink groups. + public static final int RTNLGRP_ND_USEROPT = 20; + public static final int RTMGRP_ND_USEROPT = 1 << (RTNLGRP_ND_USEROPT - 1); + + /** + * Convert a netlink message type to a string for control message. + */ + @NonNull + private static String stringForCtlMsgType(short nlmType) { + switch (nlmType) { + case NLMSG_NOOP: return "NLMSG_NOOP"; + case NLMSG_ERROR: return "NLMSG_ERROR"; + case NLMSG_DONE: return "NLMSG_DONE"; + case NLMSG_OVERRUN: return "NLMSG_OVERRUN"; + default: return "unknown control message type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_ROUTE. + */ + @NonNull + private static String stringForRtMsgType(short nlmType) { + switch (nlmType) { + case RTM_NEWLINK: return "RTM_NEWLINK"; + case RTM_DELLINK: return "RTM_DELLINK"; + case RTM_GETLINK: return "RTM_GETLINK"; + case RTM_SETLINK: return "RTM_SETLINK"; + case RTM_NEWADDR: return "RTM_NEWADDR"; + case RTM_DELADDR: return "RTM_DELADDR"; + case RTM_GETADDR: return "RTM_GETADDR"; + case RTM_NEWROUTE: return "RTM_NEWROUTE"; + case RTM_DELROUTE: return "RTM_DELROUTE"; + case RTM_GETROUTE: return "RTM_GETROUTE"; + case RTM_NEWNEIGH: return "RTM_NEWNEIGH"; + case RTM_DELNEIGH: return "RTM_DELNEIGH"; + case RTM_GETNEIGH: return "RTM_GETNEIGH"; + case RTM_NEWRULE: return "RTM_NEWRULE"; + case RTM_DELRULE: return "RTM_DELRULE"; + case RTM_GETRULE: return "RTM_GETRULE"; + case RTM_NEWNDUSEROPT: return "RTM_NEWNDUSEROPT"; + default: return "unknown RTM type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_INET_DIAG. + */ + @NonNull + private static String stringForInetDiagMsgType(short nlmType) { + switch (nlmType) { + case SOCK_DIAG_BY_FAMILY: return "SOCK_DIAG_BY_FAMILY"; + default: return "unknown SOCK_DIAG type: " + String.valueOf(nlmType); + } + } + + /** + * Convert a netlink message type to a string for NETLINK_NETFILTER. + */ + @NonNull + private static String stringForNfMsgType(short nlmType) { + final byte subsysId = (byte) (nlmType >> 8); + final byte msgType = (byte) nlmType; + switch (subsysId) { + case NFNL_SUBSYS_CTNETLINK: + switch (msgType) { + case IPCTNL_MSG_CT_NEW: return "IPCTNL_MSG_CT_NEW"; + case IPCTNL_MSG_CT_GET: return "IPCTNL_MSG_CT_GET"; + case IPCTNL_MSG_CT_DELETE: return "IPCTNL_MSG_CT_DELETE"; + case IPCTNL_MSG_CT_GET_CTRZERO: return "IPCTNL_MSG_CT_GET_CTRZERO"; + case IPCTNL_MSG_CT_GET_STATS_CPU: return "IPCTNL_MSG_CT_GET_STATS_CPU"; + case IPCTNL_MSG_CT_GET_STATS: return "IPCTNL_MSG_CT_GET_STATS"; + case IPCTNL_MSG_CT_GET_DYING: return "IPCTNL_MSG_CT_GET_DYING"; + case IPCTNL_MSG_CT_GET_UNCONFIRMED: return "IPCTNL_MSG_CT_GET_UNCONFIRMED"; + } + break; + } + return "unknown NETFILTER type: " + String.valueOf(nlmType); + } + + /** + * Convert a netlink message type to a string by netlink family. + */ + @NonNull + public static String stringForNlMsgType(short nlmType, int nlFamily) { + // Reserved control messages. The netlink family is ignored. + // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h. + if (nlmType <= NLMSG_MAX_RESERVED) return stringForCtlMsgType(nlmType); + + // Netlink family messages. The netlink family is required. Note that the reason for using + // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are + // not constant. + if (nlFamily == OsConstants.NETLINK_ROUTE) return stringForRtMsgType(nlmType); + if (nlFamily == OsConstants.NETLINK_INET_DIAG) return stringForInetDiagMsgType(nlmType); + if (nlFamily == OsConstants.NETLINK_NETFILTER) return stringForNfMsgType(nlmType); + + return "unknown type: " + String.valueOf(nlmType); + } + + private static final char[] HEX_DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F' }; + /** + * Convert a byte array to a hexadecimal string. + */ + public static String toHexString(byte[] array, int offset, int length) { + char[] buf = new char[length * 2]; + + int bufIndex = 0; + for (int i = offset; i < offset + length; i++) { + byte b = array[i]; + buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F]; + buf[bufIndex++] = HEX_DIGITS[b & 0x0F]; + } + + return new String(buf); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java new file mode 100644 index 0000000000..d9fb09e64f --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import java.nio.ByteBuffer; + +/** + * A NetlinkMessage subclass for netlink error messages. + * + * @hide + */ +public class NetlinkErrorMessage extends NetlinkMessage { + + /** + * Parse a netlink error message from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink error message. + * @return the parsed netlink error message, or {@code null} if the netlink error message + * could not be parsed successfully (for example, if it was truncated). + */ + public static NetlinkErrorMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) { + final NetlinkErrorMessage errorMsg = new NetlinkErrorMessage(header); + + errorMsg.mNlMsgErr = StructNlMsgErr.parse(byteBuffer); + if (errorMsg.mNlMsgErr == null) { + return null; + } + + return errorMsg; + } + + private StructNlMsgErr mNlMsgErr; + + NetlinkErrorMessage(StructNlMsgHdr header) { + super(header); + mNlMsgErr = null; + } + + public StructNlMsgErr getNlMsgError() { + return mNlMsgErr; + } + + @Override + public String toString() { + return "NetlinkErrorMessage{ " + + "nlmsghdr{" + (mHeader == null ? "" : mHeader.toString()) + "}, " + + "nlmsgerr{" + (mNlMsgErr == null ? "" : mNlMsgErr.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java new file mode 100644 index 0000000000..f425384f34 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkMessage.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * NetlinkMessage base class for other, more specific netlink message types. + * + * Classes that extend NetlinkMessage should: + * - implement a public static parse(StructNlMsgHdr, ByteBuffer) method + * - returning either null (parse errors) or a new object of the subclass + * type (cast-able to NetlinkMessage) + * + * NetlinkMessage.parse() should be updated to know which nlmsg_type values + * correspond with which message subclasses. + * + * @hide + */ +public class NetlinkMessage { + private static final String TAG = "NetlinkMessage"; + + /** + * Parsing netlink messages for reserved control message or specific netlink message. The + * netlink family is required for parsing specific netlink message. See man-pages/netlink. + */ + @Nullable + public static NetlinkMessage parse(@NonNull ByteBuffer byteBuffer, int nlFamily) { + final int startPosition = (byteBuffer != null) ? byteBuffer.position() : -1; + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(byteBuffer); + if (nlmsghdr == null) { + return null; + } + + int payloadLength = NetlinkConstants.alignedLengthOf(nlmsghdr.nlmsg_len); + payloadLength -= StructNlMsgHdr.STRUCT_SIZE; + if (payloadLength < 0 || payloadLength > byteBuffer.remaining()) { + // Malformed message or runt buffer. Pretend the buffer was consumed. + byteBuffer.position(byteBuffer.limit()); + return null; + } + + // Reserved control messages. The netlink family is ignored. + // See NLMSG_MIN_TYPE in include/uapi/linux/netlink.h. + if (nlmsghdr.nlmsg_type <= NetlinkConstants.NLMSG_MAX_RESERVED) { + return parseCtlMessage(nlmsghdr, byteBuffer, payloadLength); + } + + // Netlink family messages. The netlink family is required. Note that the reason for using + // if-statement is that switch-case can't be used because the OsConstants.NETLINK_* are + // not constant. + if (nlFamily == OsConstants.NETLINK_ROUTE) { + return parseRtMessage(nlmsghdr, byteBuffer); + } else if (nlFamily == OsConstants.NETLINK_INET_DIAG) { + return parseInetDiagMessage(nlmsghdr, byteBuffer); + } else if (nlFamily == OsConstants.NETLINK_NETFILTER) { + return parseNfMessage(nlmsghdr, byteBuffer); + } + + return null; + } + + protected StructNlMsgHdr mHeader; + + public NetlinkMessage(StructNlMsgHdr nlmsghdr) { + mHeader = nlmsghdr; + } + + public StructNlMsgHdr getHeader() { + return mHeader; + } + + @Override + public String toString() { + // The netlink family is not provided to StructNlMsgHdr#toString because NetlinkMessage + // doesn't store the information. So the netlink message type can't be transformed into + // a string by StructNlMsgHdr#toString and just keep as an integer. The specific message + // which inherits NetlinkMessage could override NetlinkMessage#toString and provide the + // specific netlink family to StructNlMsgHdr#toString. + return "NetlinkMessage{" + (mHeader == null ? "" : mHeader.toString()) + "}"; + } + + @NonNull + private static NetlinkMessage parseCtlMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer, int payloadLength) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.NLMSG_ERROR: + return (NetlinkMessage) NetlinkErrorMessage.parse(nlmsghdr, byteBuffer); + default: { + // Other netlink control messages. Just parse the header for now, + // pretending the whole message was consumed. + byteBuffer.position(byteBuffer.position() + payloadLength); + return new NetlinkMessage(nlmsghdr); + } + } + } + + @Nullable + private static NetlinkMessage parseRtMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.RTM_NEWNEIGH: + case NetlinkConstants.RTM_DELNEIGH: + case NetlinkConstants.RTM_GETNEIGH: + return (NetlinkMessage) RtNetlinkNeighborMessage.parse(nlmsghdr, byteBuffer); + case NetlinkConstants.RTM_NEWNDUSEROPT: + return (NetlinkMessage) NduseroptMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } + + @Nullable + private static NetlinkMessage parseInetDiagMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.SOCK_DIAG_BY_FAMILY: + return (NetlinkMessage) InetDiagMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } + + @Nullable + private static NetlinkMessage parseNfMessage(@NonNull StructNlMsgHdr nlmsghdr, + @NonNull ByteBuffer byteBuffer) { + switch (nlmsghdr.nlmsg_type) { + case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 + | NetlinkConstants.IPCTNL_MSG_CT_NEW: + case NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 + | NetlinkConstants.IPCTNL_MSG_CT_DELETE: + return (NetlinkMessage) ConntrackMessage.parse(nlmsghdr, byteBuffer); + default: return null; + } + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkSocket.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkSocket.java new file mode 100644 index 0000000000..ec326dd7c2 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkSocket.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.net.util.SocketUtils.makeNetlinkSocketAddress; +import static android.system.OsConstants.AF_NETLINK; +import static android.system.OsConstants.EIO; +import static android.system.OsConstants.EPROTO; +import static android.system.OsConstants.ETIMEDOUT; +import static android.system.OsConstants.SOCK_DGRAM; +import static android.system.OsConstants.SOL_SOCKET; +import static android.system.OsConstants.SO_RCVBUF; +import static android.system.OsConstants.SO_RCVTIMEO; +import static android.system.OsConstants.SO_SNDTIMEO; + +import android.net.util.SocketUtils; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructTimeval; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.SocketException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** + * NetlinkSocket + * + * A small static class to assist with AF_NETLINK socket operations. + * + * @hide + */ +public class NetlinkSocket { + private static final String TAG = "NetlinkSocket"; + private static final long IO_TIMEOUT_MS = 300L; + + public static final int DEFAULT_RECV_BUFSIZE = 8 * 1024; + public static final int SOCKET_RECV_BUFSIZE = 64 * 1024; + + /** + * Send one netlink message to kernel via netlink socket. + * + * @param nlProto netlink protocol type. + * @param msg the raw bytes of netlink message to be sent. + */ + public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { + final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; + final FileDescriptor fd = forProto(nlProto); + + try { + connectToKernel(fd); + sendMessage(fd, msg, 0, msg.length, IO_TIMEOUT_MS); + final ByteBuffer bytes = recvMessage(fd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS); + // recvMessage() guaranteed to not return null if it did not throw. + final NetlinkMessage response = NetlinkMessage.parse(bytes, nlProto); + if (response != null && response instanceof NetlinkErrorMessage + && (((NetlinkErrorMessage) response).getNlMsgError() != null)) { + final int errno = ((NetlinkErrorMessage) response).getNlMsgError().error; + if (errno != 0) { + // TODO: consider ignoring EINVAL (-22), which appears to be + // normal when probing a neighbor for which the kernel does + // not already have / no longer has a link layer address. + Log.e(TAG, errPrefix + ", errmsg=" + response.toString()); + // Note: convert kernel errnos (negative) into userspace errnos (positive). + throw new ErrnoException(response.toString(), Math.abs(errno)); + } + } else { + final String errmsg; + if (response == null) { + bytes.position(0); + errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); + } else { + errmsg = response.toString(); + } + Log.e(TAG, errPrefix + ", errmsg=" + errmsg); + throw new ErrnoException(errmsg, EPROTO); + } + } catch (InterruptedIOException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, ETIMEDOUT, e); + } catch (SocketException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, EIO, e); + } finally { + try { + SocketUtils.closeSocket(fd); + } catch (IOException e) { + // Nothing we can do here + } + } + } + + /** + * Create netlink socket with the given netlink protocol type. + * + * @return fd the fileDescriptor of the socket. + * Throw ErrnoException if the exception is thrown. + */ + public static FileDescriptor forProto(int nlProto) throws ErrnoException { + final FileDescriptor fd = Os.socket(AF_NETLINK, SOCK_DGRAM, nlProto); + Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, SOCKET_RECV_BUFSIZE); + return fd; + } + + /** + * Connect to kernel via netlink socket. + * + * Throw ErrnoException, SocketException if the exception is thrown. + */ + public static void connectToKernel(FileDescriptor fd) throws ErrnoException, SocketException { + Os.connect(fd, makeNetlinkSocketAddress(0, 0)); + } + + private static void checkTimeout(long timeoutMs) { + if (timeoutMs < 0) { + throw new IllegalArgumentException("Negative timeouts not permitted"); + } + } + + /** + * Wait up to |timeoutMs| (or until underlying socket error) for a + * netlink message of at most |bufsize| size. + * + * Multi-threaded calls with different timeouts will cause unexpected results. + */ + public static ByteBuffer recvMessage(FileDescriptor fd, int bufsize, long timeoutMs) + throws ErrnoException, IllegalArgumentException, InterruptedIOException { + checkTimeout(timeoutMs); + + Os.setsockoptTimeval(fd, SOL_SOCKET, SO_RCVTIMEO, StructTimeval.fromMillis(timeoutMs)); + + ByteBuffer byteBuffer = ByteBuffer.allocate(bufsize); + int length = Os.read(fd, byteBuffer); + if (length == bufsize) { + Log.w(TAG, "maximum read"); + } + byteBuffer.position(0); + byteBuffer.limit(length); + byteBuffer.order(ByteOrder.nativeOrder()); + return byteBuffer; + } + + /** + * Send a message to a peer to which this socket has previously connected, + * waiting at most |timeoutMs| milliseconds for the send to complete. + * + * Multi-threaded calls with different timeouts will cause unexpected results. + */ + public static int sendMessage( + FileDescriptor fd, byte[] bytes, int offset, int count, long timeoutMs) + throws ErrnoException, IllegalArgumentException, InterruptedIOException { + checkTimeout(timeoutMs); + Os.setsockoptTimeval(fd, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(timeoutMs)); + return Os.write(fd, bytes, offset, count); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java new file mode 100644 index 0000000000..a75ef8dd23 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import android.system.OsConstants; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * A NetlinkMessage subclass for rtnetlink neighbor messages. + * + * see also: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class RtNetlinkNeighborMessage extends NetlinkMessage { + public static final short NDA_UNSPEC = 0; + public static final short NDA_DST = 1; + public static final short NDA_LLADDR = 2; + public static final short NDA_CACHEINFO = 3; + public static final short NDA_PROBES = 4; + public static final short NDA_VLAN = 5; + public static final short NDA_PORT = 6; + public static final short NDA_VNI = 7; + public static final short NDA_IFINDEX = 8; + public static final short NDA_MASTER = 9; + + /** + * Parse routing socket netlink neighbor message from ByteBuffer. + * + * @param header netlink message header. + * @param byteBuffer the ByteBuffer instance that wraps the raw netlink message bytes. + */ + public static RtNetlinkNeighborMessage parse(StructNlMsgHdr header, ByteBuffer byteBuffer) { + final RtNetlinkNeighborMessage neighMsg = new RtNetlinkNeighborMessage(header); + + neighMsg.mNdmsg = StructNdMsg.parse(byteBuffer); + if (neighMsg.mNdmsg == null) { + return null; + } + + // Some of these are message-type dependent, and not always present. + final int baseOffset = byteBuffer.position(); + StructNlAttr nlAttr = StructNlAttr.findNextAttrOfType(NDA_DST, byteBuffer); + if (nlAttr != null) { + neighMsg.mDestination = nlAttr.getValueAsInetAddress(); + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_LLADDR, byteBuffer); + if (nlAttr != null) { + neighMsg.mLinkLayerAddr = nlAttr.nla_value; + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_PROBES, byteBuffer); + if (nlAttr != null) { + neighMsg.mNumProbes = nlAttr.getValueAsInt(0); + } + + byteBuffer.position(baseOffset); + nlAttr = StructNlAttr.findNextAttrOfType(NDA_CACHEINFO, byteBuffer); + if (nlAttr != null) { + neighMsg.mCacheInfo = StructNdaCacheInfo.parse(nlAttr.getValueAsByteBuffer()); + } + + final int kMinConsumed = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + final int kAdditionalSpace = NetlinkConstants.alignedLengthOf( + neighMsg.mHeader.nlmsg_len - kMinConsumed); + if (byteBuffer.remaining() < kAdditionalSpace) { + byteBuffer.position(byteBuffer.limit()); + } else { + byteBuffer.position(baseOffset + kAdditionalSpace); + } + + return neighMsg; + } + + /** + * A convenience method to create an RTM_GETNEIGH request message. + */ + public static byte[] newGetNeighborsRequest(int seqNo) { + final int length = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + final byte[] bytes = new byte[length]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_len = length; + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETNEIGH; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlmsghdr.nlmsg_seq = seqNo; + nlmsghdr.pack(byteBuffer); + + final StructNdMsg ndmsg = new StructNdMsg(); + ndmsg.pack(byteBuffer); + + return bytes; + } + + /** + * A convenience method to create an RTM_NEWNEIGH message, to modify + * the kernel's state information for a specific neighbor. + */ + public static byte[] newNewNeighborMessage( + int seqNo, InetAddress ip, short nudState, int ifIndex, byte[] llAddr) { + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_NEWNEIGH; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE; + nlmsghdr.nlmsg_seq = seqNo; + + final RtNetlinkNeighborMessage msg = new RtNetlinkNeighborMessage(nlmsghdr); + msg.mNdmsg = new StructNdMsg(); + msg.mNdmsg.ndm_family = + (byte) ((ip instanceof Inet6Address) ? OsConstants.AF_INET6 : OsConstants.AF_INET); + msg.mNdmsg.ndm_ifindex = ifIndex; + msg.mNdmsg.ndm_state = nudState; + msg.mDestination = ip; + msg.mLinkLayerAddr = llAddr; // might be null + + final byte[] bytes = new byte[msg.getRequiredSpace()]; + nlmsghdr.nlmsg_len = bytes.length; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + msg.pack(byteBuffer); + return bytes; + } + + private StructNdMsg mNdmsg; + private InetAddress mDestination; + private byte[] mLinkLayerAddr; + private int mNumProbes; + private StructNdaCacheInfo mCacheInfo; + + private RtNetlinkNeighborMessage(StructNlMsgHdr header) { + super(header); + mNdmsg = null; + mDestination = null; + mLinkLayerAddr = null; + mNumProbes = 0; + mCacheInfo = null; + } + + public StructNdMsg getNdHeader() { + return mNdmsg; + } + + public InetAddress getDestination() { + return mDestination; + } + + public byte[] getLinkLayerAddress() { + return mLinkLayerAddr; + } + + public int getProbes() { + return mNumProbes; + } + + public StructNdaCacheInfo getCacheInfo() { + return mCacheInfo; + } + + private int getRequiredSpace() { + int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructNdMsg.STRUCT_SIZE; + if (mDestination != null) { + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + mDestination.getAddress().length); + } + if (mLinkLayerAddr != null) { + spaceRequired += NetlinkConstants.alignedLengthOf( + StructNlAttr.NLA_HEADERLEN + mLinkLayerAddr.length); + } + // Currently we don't write messages with NDA_PROBES nor NDA_CACHEINFO + // attributes appended. Fix later, if necessary. + return spaceRequired; + } + + private static void packNlAttr(short nlType, byte[] nlValue, ByteBuffer byteBuffer) { + final StructNlAttr nlAttr = new StructNlAttr(); + nlAttr.nla_type = nlType; + nlAttr.nla_value = nlValue; + nlAttr.nla_len = (short) (StructNlAttr.NLA_HEADERLEN + nlAttr.nla_value.length); + nlAttr.pack(byteBuffer); + } + + /** + * Write a neighbor discovery netlink message to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + getHeader().pack(byteBuffer); + mNdmsg.pack(byteBuffer); + + if (mDestination != null) { + packNlAttr(NDA_DST, mDestination.getAddress(), byteBuffer); + } + if (mLinkLayerAddr != null) { + packNlAttr(NDA_LLADDR, mLinkLayerAddr, byteBuffer); + } + } + + @Override + public String toString() { + final String ipLiteral = (mDestination == null) ? "" : mDestination.getHostAddress(); + return "RtNetlinkNeighborMessage{ " + + "nlmsghdr{" + + (mHeader == null ? "" : mHeader.toString(OsConstants.NETLINK_ROUTE)) + "}, " + + "ndmsg{" + (mNdmsg == null ? "" : mNdmsg.toString()) + "}, " + + "destination{" + ipLiteral + "} " + + "linklayeraddr{" + NetlinkConstants.hexify(mLinkLayerAddr) + "} " + + "probes{" + mNumProbes + "} " + + "cacheinfo{" + (mCacheInfo == null ? "" : mCacheInfo.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java new file mode 100644 index 0000000000..205656e3b5 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagMsg.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import java.nio.ByteBuffer; + +/** + * struct inet_diag_msg + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_msg { + * __u8 idiag_family; + * __u8 idiag_state; + * __u8 idiag_timer; + * __u8 idiag_retrans; + * struct inet_diag_sockid id; + * __u32 idiag_expires; + * __u32 idiag_rqueue; + * __u32 idiag_wqueue; + * __u32 idiag_uid; + * __u32 idiag_inode; + * }; + * + * @hide + */ +public class StructInetDiagMsg { + public static final int STRUCT_SIZE = 4 + StructInetDiagSockId.STRUCT_SIZE + 20; + private static final int IDIAG_UID_OFFSET = StructNlMsgHdr.STRUCT_SIZE + 4 + + StructInetDiagSockId.STRUCT_SIZE + 12; + public int idiag_uid; + + /** + * Parse inet diag netlink message from buffer. + */ + public static StructInetDiagMsg parse(ByteBuffer byteBuffer) { + StructInetDiagMsg struct = new StructInetDiagMsg(); + struct.idiag_uid = byteBuffer.getInt(IDIAG_UID_OFFSET); + return struct; + } + + @Override + public String toString() { + return "StructInetDiagMsg{ " + + "idiag_uid{" + idiag_uid + "}, " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java new file mode 100644 index 0000000000..6eef8653fe --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.Nullable; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_req_v2 { + * __u8 sdiag_family; + * __u8 sdiag_protocol; + * __u8 idiag_ext; + * __u8 pad; + * __u32 idiag_states; + * struct inet_diag_sockid id; + * }; + * + * @hide + */ +public class StructInetDiagReqV2 { + public static final int STRUCT_SIZE = 8 + StructInetDiagSockId.STRUCT_SIZE; + + private final byte mSdiagFamily; + private final byte mSdiagProtocol; + private final byte mIdiagExt; + private final byte mPad; + private final StructInetDiagSockId mId; + private final int mState; + public static final int INET_DIAG_REQ_V2_ALL_STATES = (int) 0xffffffff; + + public StructInetDiagReqV2(int protocol, InetSocketAddress local, InetSocketAddress remote, + int family) { + this(protocol, local, remote, family, 0 /* pad */, 0 /* extension */, + INET_DIAG_REQ_V2_ALL_STATES); + } + + public StructInetDiagReqV2(int protocol, @Nullable InetSocketAddress local, + @Nullable InetSocketAddress remote, int family, int pad, int extension, int state) + throws NullPointerException { + mSdiagFamily = (byte) family; + mSdiagProtocol = (byte) protocol; + // Request for all sockets if no specific socket is requested. Specify the local and remote + // socket address information for target request socket. + if ((local == null) != (remote == null)) { + throw new NullPointerException("Local and remote must be both null or both non-null"); + } + mId = ((local != null && remote != null) ? new StructInetDiagSockId(local, remote) : null); + mPad = (byte) pad; + mIdiagExt = (byte) extension; + mState = state; + } + + /** + * Write the int diag request v2 message to ByteBuffer. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. + byteBuffer.put((byte) mSdiagFamily); + byteBuffer.put((byte) mSdiagProtocol); + byteBuffer.put((byte) mIdiagExt); + byteBuffer.put((byte) mPad); + byteBuffer.putInt(mState); + if (mId != null) mId.pack(byteBuffer); + } + + @Override + public String toString() { + final String familyStr = NetlinkConstants.stringForAddressFamily(mSdiagFamily); + final String protocolStr = NetlinkConstants.stringForAddressFamily(mSdiagProtocol); + + return "StructInetDiagReqV2{ " + + "sdiag_family{" + familyStr + "}, " + + "sdiag_protocol{" + protocolStr + "}, " + + "idiag_ext{" + mIdiagExt + ")}, " + + "pad{" + mPad + "}, " + + "idiag_states{" + Integer.toHexString(mState) + "}, " + + ((mId != null) ? mId.toString() : "inet_diag_sockid=null") + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java new file mode 100644 index 0000000000..95d60e5319 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructInetDiagSockId.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static java.nio.ByteOrder.BIG_ENDIAN; + +import java.net.Inet4Address; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * struct inet_diag_req_v2 + * + * see <linux_src>/include/uapi/linux/inet_diag.h + * + * struct inet_diag_sockid { + * __be16 idiag_sport; + * __be16 idiag_dport; + * __be32 idiag_src[4]; + * __be32 idiag_dst[4]; + * __u32 idiag_if; + * __u32 idiag_cookie[2]; + * #define INET_DIAG_NOCOOKIE (~0U) + * }; + * + * @hide + */ +public class StructInetDiagSockId { + public static final int STRUCT_SIZE = 48; + + private static final byte[] INET_DIAG_NOCOOKIE = new byte[]{ + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + private static final byte[] IPV4_PADDING = new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + private final InetSocketAddress mLocSocketAddress; + private final InetSocketAddress mRemSocketAddress; + + public StructInetDiagSockId(InetSocketAddress loc, InetSocketAddress rem) { + mLocSocketAddress = loc; + mRemSocketAddress = rem; + } + + /** + * Write inet diag socket id message to ByteBuffer in big endian. + */ + public void pack(ByteBuffer byteBuffer) { + byteBuffer.order(BIG_ENDIAN); + byteBuffer.putShort((short) mLocSocketAddress.getPort()); + byteBuffer.putShort((short) mRemSocketAddress.getPort()); + byteBuffer.put(mLocSocketAddress.getAddress().getAddress()); + if (mLocSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.put(mRemSocketAddress.getAddress().getAddress()); + if (mRemSocketAddress.getAddress() instanceof Inet4Address) { + byteBuffer.put(IPV4_PADDING); + } + byteBuffer.order(ByteOrder.nativeOrder()); + byteBuffer.putInt(0); + byteBuffer.put(INET_DIAG_NOCOOKIE); + } + + @Override + public String toString() { + return "StructInetDiagSockId{ " + + "idiag_sport{" + mLocSocketAddress.getPort() + "}, " + + "idiag_dport{" + mRemSocketAddress.getPort() + "}, " + + "idiag_src{" + mLocSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_dst{" + mRemSocketAddress.getAddress().getHostAddress() + "}, " + + "idiag_if{" + 0 + "} " + + "idiag_cookie{INET_DIAG_NOCOOKIE}" + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java new file mode 100644 index 0000000000..53ce89918a --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdMsg.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.OsConstants; + +import java.nio.ByteBuffer; + +/** + * struct ndmsg + * + * see: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class StructNdMsg { + // Already aligned. + public static final int STRUCT_SIZE = 12; + + // Neighbor Cache Entry States + public static final short NUD_NONE = 0x00; + public static final short NUD_INCOMPLETE = 0x01; + public static final short NUD_REACHABLE = 0x02; + public static final short NUD_STALE = 0x04; + public static final short NUD_DELAY = 0x08; + public static final short NUD_PROBE = 0x10; + public static final short NUD_FAILED = 0x20; + public static final short NUD_NOARP = 0x40; + public static final short NUD_PERMANENT = 0x80; + + /** + * Convert neighbor cache entry state integer to string. + */ + public static String stringForNudState(short nudState) { + switch (nudState) { + case NUD_NONE: return "NUD_NONE"; + case NUD_INCOMPLETE: return "NUD_INCOMPLETE"; + case NUD_REACHABLE: return "NUD_REACHABLE"; + case NUD_STALE: return "NUD_STALE"; + case NUD_DELAY: return "NUD_DELAY"; + case NUD_PROBE: return "NUD_PROBE"; + case NUD_FAILED: return "NUD_FAILED"; + case NUD_NOARP: return "NUD_NOARP"; + case NUD_PERMANENT: return "NUD_PERMANENT"; + default: + return "unknown NUD state: " + String.valueOf(nudState); + } + } + + /** + * Check whether a neighbor is connected or not. + */ + public static boolean isNudStateConnected(short nudState) { + return ((nudState & (NUD_PERMANENT | NUD_NOARP | NUD_REACHABLE)) != 0); + } + + /** + * Check whether a neighbor is in the valid NUD state or not. + */ + public static boolean isNudStateValid(short nudState) { + return (isNudStateConnected(nudState) + || ((nudState & (NUD_PROBE | NUD_STALE | NUD_DELAY)) != 0)); + } + + // Neighbor Cache Entry Flags + public static byte NTF_USE = (byte) 0x01; + public static byte NTF_SELF = (byte) 0x02; + public static byte NTF_MASTER = (byte) 0x04; + public static byte NTF_PROXY = (byte) 0x08; + public static byte NTF_ROUTER = (byte) 0x80; + + private static String stringForNudFlags(byte flags) { + final StringBuilder sb = new StringBuilder(); + if ((flags & NTF_USE) != 0) { + sb.append("NTF_USE"); + } + if ((flags & NTF_SELF) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_SELF"); + } + if ((flags & NTF_MASTER) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_MASTER"); + } + if ((flags & NTF_PROXY) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_PROXY"); + } + if ((flags & NTF_ROUTER) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NTF_ROUTER"); + } + return sb.toString(); + } + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a neighbor discovery netlink message header from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the nd netlink message header. + * @return the parsed nd netlink message header, or {@code null} if the nd netlink message + * header could not be parsed successfully (for example, if it was truncated). + */ + public static StructNdMsg parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + final StructNdMsg struct = new StructNdMsg(); + struct.ndm_family = byteBuffer.get(); + final byte pad1 = byteBuffer.get(); + final short pad2 = byteBuffer.getShort(); + struct.ndm_ifindex = byteBuffer.getInt(); + struct.ndm_state = byteBuffer.getShort(); + struct.ndm_flags = byteBuffer.get(); + struct.ndm_type = byteBuffer.get(); + return struct; + } + + public byte ndm_family; + public int ndm_ifindex; + public short ndm_state; + public byte ndm_flags; + public byte ndm_type; + + public StructNdMsg() { + ndm_family = (byte) OsConstants.AF_UNSPEC; + } + + /** + * Write the neighbor discovery message header to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + byteBuffer.put(ndm_family); + byteBuffer.put((byte) 0); // pad1 + byteBuffer.putShort((short) 0); // pad2 + byteBuffer.putInt(ndm_ifindex); + byteBuffer.putShort(ndm_state); + byteBuffer.put(ndm_flags); + byteBuffer.put(ndm_type); + } + + /** + * Check whether a neighbor is connected or not. + */ + public boolean nudConnected() { + return isNudStateConnected(ndm_state); + } + + /** + * Check whether a neighbor is in the valid NUD state or not. + */ + public boolean nudValid() { + return isNudStateValid(ndm_state); + } + + @Override + public String toString() { + final String stateStr = "" + ndm_state + " (" + stringForNudState(ndm_state) + ")"; + final String flagsStr = "" + ndm_flags + " (" + stringForNudFlags(ndm_flags) + ")"; + return "StructNdMsg{ " + + "family{" + NetlinkConstants.stringForAddressFamily((int) ndm_family) + "}, " + + "ifindex{" + ndm_ifindex + "}, " + + "state{" + stateStr + "}, " + + "flags{" + flagsStr + "}, " + + "type{" + ndm_type + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java new file mode 100644 index 0000000000..bde6983187 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdOptPref64.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.net.IpPrefix; +import android.util.Log; + +import androidx.annotation.NonNull; + +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.util.Objects; + +/** + * The PREF64 router advertisement option. RFC 8781. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Type | Length | Scaled Lifetime | PLC | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | Highest 96 bits of the Prefix | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + */ +public class StructNdOptPref64 extends NdOption { + public static final int STRUCT_SIZE = 16; + public static final int TYPE = 38; + public static final byte LENGTH = 2; + + private static final String TAG = StructNdOptPref64.class.getSimpleName(); + + /** + * How many seconds the prefix is expected to remain valid. + * Valid values are from 0 to 65528 in multiples of 8. + */ + public final int lifetime; + /** The NAT64 prefix. */ + @NonNull public final IpPrefix prefix; + + static int plcToPrefixLength(int plc) { + switch (plc) { + case 0: return 96; + case 1: return 64; + case 2: return 56; + case 3: return 48; + case 4: return 40; + case 5: return 32; + default: + throw new IllegalArgumentException("Invalid prefix length code " + plc); + } + } + + static int prefixLengthToPlc(int prefixLength) { + switch (prefixLength) { + case 96: return 0; + case 64: return 1; + case 56: return 2; + case 48: return 3; + case 40: return 4; + case 32: return 5; + default: + throw new IllegalArgumentException("Invalid prefix length " + prefixLength); + } + } + + /** + * Returns the 2-byte "scaled lifetime and prefix length code" field: 13-bit lifetime, 3-bit PLC + */ + static short getScaledLifetimePlc(int lifetime, int prefixLengthCode) { + return (short) ((lifetime & 0xfff8) | (prefixLengthCode & 0x7)); + } + + public StructNdOptPref64(@NonNull IpPrefix prefix, int lifetime) { + super((byte) TYPE, LENGTH); + + Objects.requireNonNull(prefix, "prefix must not be null"); + if (!(prefix.getAddress() instanceof Inet6Address)) { + throw new IllegalArgumentException("Must be an IPv6 prefix: " + prefix); + } + prefixLengthToPlc(prefix.getPrefixLength()); // Throw if the prefix length is invalid. + this.prefix = prefix; + + if (lifetime < 0 || lifetime > 0xfff8) { + throw new IllegalArgumentException("Invalid lifetime " + lifetime); + } + this.lifetime = lifetime & 0xfff8; + } + + private StructNdOptPref64(@NonNull ByteBuffer buf) { + super(buf.get(), Byte.toUnsignedInt(buf.get())); + if (type != TYPE) throw new IllegalArgumentException("Invalid type " + type); + if (length != LENGTH) throw new IllegalArgumentException("Invalid length " + length); + + int scaledLifetimePlc = Short.toUnsignedInt(buf.getShort()); + lifetime = scaledLifetimePlc & 0xfff8; + + byte[] addressBytes = new byte[16]; + buf.get(addressBytes, 0, 12); + InetAddress addr; + try { + addr = InetAddress.getByAddress(addressBytes); + } catch (UnknownHostException e) { + throw new AssertionError("16-byte array not valid InetAddress?"); + } + prefix = new IpPrefix(addr, plcToPrefixLength(scaledLifetimePlc & 7)); + } + + /** + * Parses an option from a {@link ByteBuffer}. + * + * @param buf The buffer from which to parse the option. The buffer's byte order must be + * {@link java.nio.ByteOrder#BIG_ENDIAN}. + * @return the parsed option, or {@code null} if the option could not be parsed successfully + * (for example, if it was truncated, or if the prefix length code was wrong). + */ + public static StructNdOptPref64 parse(@NonNull ByteBuffer buf) { + if (buf == null || buf.remaining() < STRUCT_SIZE) return null; + try { + return new StructNdOptPref64(buf); + } catch (IllegalArgumentException e) { + // Not great, but better than throwing an exception that might crash the caller. + // Convention in this package is that null indicates that the option was truncated, so + // callers must already handle it. + Log.d(TAG, "Invalid PREF64 option: " + e); + return null; + } + } + + protected void writeToByteBuffer(ByteBuffer buf) { + super.writeToByteBuffer(buf); + buf.putShort(getScaledLifetimePlc(lifetime, prefixLengthToPlc(prefix.getPrefixLength()))); + buf.put(prefix.getRawAddress(), 0, 12); + } + + /** Outputs the wire format of the option to a new big-endian ByteBuffer. */ + public ByteBuffer toByteBuffer() { + ByteBuffer buf = ByteBuffer.allocate(STRUCT_SIZE); + writeToByteBuffer(buf); + buf.flip(); + return buf; + } + + @Override + @NonNull + public String toString() { + return String.format("NdOptPref64(%s, %d)", prefix, lifetime); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java new file mode 100644 index 0000000000..79d5ff4ad8 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import android.system.Os; +import android.system.OsConstants; + +import java.nio.ByteBuffer; + +/** + * struct nda_cacheinfo + * + * see: <linux_src>/include/uapi/linux/neighbour.h + * + * @hide + */ +public class StructNdaCacheInfo { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a nd cacheinfo netlink attribute from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the nd cacheinfo attribute. + * @return the parsed nd cacheinfo attribute, or {@code null} if the nd cacheinfo attribute + * could not be parsed successfully (for example, if it was truncated). + */ + public static StructNdaCacheInfo parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + final StructNdaCacheInfo struct = new StructNdaCacheInfo(); + struct.ndm_used = byteBuffer.getInt(); + struct.ndm_confirmed = byteBuffer.getInt(); + struct.ndm_updated = byteBuffer.getInt(); + struct.ndm_refcnt = byteBuffer.getInt(); + return struct; + } + + // TODO: investigate whether this can change during device runtime and + // decide what (if anything) should be done about that. + private static final long CLOCK_TICKS_PER_SECOND = Os.sysconf(OsConstants._SC_CLK_TCK); + + private static long ticksToMilliSeconds(int intClockTicks) { + final long longClockTicks = (long) intClockTicks & 0xffffffff; + return (longClockTicks * 1000) / CLOCK_TICKS_PER_SECOND; + } + + /** + * Explanatory notes, for reference. + * + * Before being returned to user space, the neighbor entry times are + * converted to clock_t's like so: + * + * ndm_used = jiffies_to_clock_t(now - neigh->used); + * ndm_confirmed = jiffies_to_clock_t(now - neigh->confirmed); + * ndm_updated = jiffies_to_clock_t(now - neigh->updated); + * + * meaning that these values are expressed as "clock ticks ago". To + * convert these clock ticks to seconds divide by sysconf(_SC_CLK_TCK). + * When _SC_CLK_TCK is 100, for example, the ndm_* times are expressed + * in centiseconds. + * + * These values are unsigned, but fortunately being expressed as "some + * clock ticks ago", these values are typically very small (and + * 2^31 centiseconds = 248 days). + * + * By observation, it appears that: + * ndm_used: the last time ARP/ND took place for this neighbor + * ndm_confirmed: the last time ARP/ND succeeded for this neighbor OR + * higher layer confirmation (TCP or MSG_CONFIRM) + * was received + * ndm_updated: the time when the current NUD state was entered + */ + public int ndm_used; + public int ndm_confirmed; + public int ndm_updated; + public int ndm_refcnt; + + public StructNdaCacheInfo() {} + + /** + * The last time ARP/ND took place for this neighbor. + */ + public long lastUsed() { + return ticksToMilliSeconds(ndm_used); + } + + /** + * The last time ARP/ND succeeded for this neighbor or higher layer confirmation (TCP or + * MSG_CONFIRM) was received. + */ + public long lastConfirmed() { + return ticksToMilliSeconds(ndm_confirmed); + } + + /** + * The time when the current NUD state was entered. + */ + public long lastUpdated() { + return ticksToMilliSeconds(ndm_updated); + } + + @Override + public String toString() { + return "NdaCacheInfo{ " + + "ndm_used{" + lastUsed() + "}, " + + "ndm_confirmed{" + lastConfirmed() + "}, " + + "ndm_updated{" + lastUpdated() + "}, " + + "ndm_refcnt{" + ndm_refcnt + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java new file mode 100644 index 0000000000..2de5490bce --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNfGenMsg.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +/** + * struct nfgenmsg + * + * see <linux_src>/include/uapi/linux/netfilter/nfnetlink.h + * + * @hide + */ +public class StructNfGenMsg { + public static final int STRUCT_SIZE = 2 + Short.BYTES; + + public static final int NFNETLINK_V0 = 0; + + public final byte nfgen_family; + public final byte version; + public final short res_id; // N.B.: this is big endian in the kernel + + /** + * Parse a netfilter netlink header from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netfilter netlink header. + * @return the parsed netfilter netlink header, or {@code null} if the netfilter netlink header + * could not be parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructNfGenMsg parse(@NonNull ByteBuffer byteBuffer) { + Objects.requireNonNull(byteBuffer); + + if (!hasAvailableSpace(byteBuffer)) return null; + + final byte nfgen_family = byteBuffer.get(); + final byte version = byteBuffer.get(); + + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.BIG_ENDIAN); + final short res_id = byteBuffer.getShort(); + byteBuffer.order(originalOrder); + + return new StructNfGenMsg(nfgen_family, version, res_id); + } + + public StructNfGenMsg(byte family, byte ver, short id) { + nfgen_family = family; + version = ver; + res_id = id; + } + + public StructNfGenMsg(byte family) { + nfgen_family = family; + version = (byte) NFNETLINK_V0; + res_id = (short) 0; + } + + /** + * Write a netfilter netlink header to a {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + byteBuffer.put(nfgen_family); + byteBuffer.put(version); + + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.BIG_ENDIAN); + byteBuffer.putShort(res_id); + byteBuffer.order(originalOrder); + } + + private static boolean hasAvailableSpace(@NonNull ByteBuffer byteBuffer) { + return byteBuffer.remaining() >= STRUCT_SIZE; + } + + @Override + public String toString() { + final String familyStr = NetlinkConstants.stringForAddressFamily(nfgen_family); + + return "NfGenMsg{ " + + "nfgen_family{" + familyStr + "}, " + + "version{" + Byte.toUnsignedInt(version) + "}, " + + "res_id{" + Short.toUnsignedInt(res_id) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java new file mode 100644 index 0000000000..80f00579bd --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlAttr.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.Nullable; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * struct nlattr + * + * see: <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlAttr { + // Already aligned. + public static final int NLA_HEADERLEN = 4; + public static final int NLA_F_NESTED = (1 << 15); + + /** + * Set carries nested attributes bit. + */ + public static short makeNestedType(short type) { + return (short) (type | NLA_F_NESTED); + } + + /** + * Peek and parse the netlink attribute from {@link ByteBuffer}. + * + * Return a (length, type) object only, without consuming any bytes in + * |byteBuffer| and without copying or interpreting any value bytes. + * This is used for scanning over a packed set of struct nlattr's, + * looking for instances of a particular type. + */ + public static StructNlAttr peek(ByteBuffer byteBuffer) { + if (byteBuffer == null || byteBuffer.remaining() < NLA_HEADERLEN) { + return null; + } + final int baseOffset = byteBuffer.position(); + + final StructNlAttr struct = new StructNlAttr(); + final ByteOrder originalOrder = byteBuffer.order(); + byteBuffer.order(ByteOrder.nativeOrder()); + try { + struct.nla_len = byteBuffer.getShort(); + struct.nla_type = byteBuffer.getShort(); + } finally { + byteBuffer.order(originalOrder); + } + + byteBuffer.position(baseOffset); + if (struct.nla_len < NLA_HEADERLEN) { + // Malformed. + return null; + } + return struct; + } + + /** + * Parse a netlink attribute from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink attriute. + * @return the parsed netlink attribute, or {@code null} if the netlink attribute + * could not be parsed successfully (for example, if it was truncated). + */ + public static StructNlAttr parse(ByteBuffer byteBuffer) { + final StructNlAttr struct = peek(byteBuffer); + if (struct == null || byteBuffer.remaining() < struct.getAlignedLength()) { + return null; + } + + final int baseOffset = byteBuffer.position(); + byteBuffer.position(baseOffset + NLA_HEADERLEN); + + int valueLen = ((int) struct.nla_len) & 0xffff; + valueLen -= NLA_HEADERLEN; + if (valueLen > 0) { + struct.nla_value = new byte[valueLen]; + byteBuffer.get(struct.nla_value, 0, valueLen); + byteBuffer.position(baseOffset + struct.getAlignedLength()); + } + return struct; + } + + /** + * Find next netlink attribute with a given type from {@link ByteBuffer}. + * + * @param attrType The given netlink attribute type is requested for. + * @param byteBuffer The buffer from which to find the netlink attribute. + * @return the found netlink attribute, or {@code null} if the netlink attribute could not be + * found or parsed successfully (for example, if it was truncated). + */ + @Nullable + public static StructNlAttr findNextAttrOfType(short attrType, + @Nullable ByteBuffer byteBuffer) { + while (byteBuffer != null && byteBuffer.remaining() > 0) { + final StructNlAttr nlAttr = StructNlAttr.peek(byteBuffer); + if (nlAttr == null) { + break; + } + if (nlAttr.nla_type == attrType) { + return StructNlAttr.parse(byteBuffer); + } + if (byteBuffer.remaining() < nlAttr.getAlignedLength()) { + break; + } + byteBuffer.position(byteBuffer.position() + nlAttr.getAlignedLength()); + } + return null; + } + + public short nla_len = (short) NLA_HEADERLEN; + public short nla_type; + public byte[] nla_value; + + public StructNlAttr() {} + + public StructNlAttr(short type, byte value) { + nla_type = type; + setValue(new byte[1]); + nla_value[0] = value; + } + + public StructNlAttr(short type, short value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, short value, ByteOrder order) { + nla_type = type; + setValue(new byte[Short.BYTES]); + final ByteBuffer buf = getValueAsByteBuffer(); + final ByteOrder originalOrder = buf.order(); + try { + buf.order(order); + buf.putShort(value); + } finally { + buf.order(originalOrder); + } + } + + public StructNlAttr(short type, int value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, int value, ByteOrder order) { + nla_type = type; + setValue(new byte[Integer.BYTES]); + final ByteBuffer buf = getValueAsByteBuffer(); + final ByteOrder originalOrder = buf.order(); + try { + buf.order(order); + buf.putInt(value); + } finally { + buf.order(originalOrder); + } + } + + public StructNlAttr(short type, InetAddress ip) { + nla_type = type; + setValue(ip.getAddress()); + } + + public StructNlAttr(short type, StructNlAttr... nested) { + this(); + nla_type = makeNestedType(type); + + int payloadLength = 0; + for (StructNlAttr nla : nested) payloadLength += nla.getAlignedLength(); + setValue(new byte[payloadLength]); + + final ByteBuffer buf = getValueAsByteBuffer(); + for (StructNlAttr nla : nested) { + nla.pack(buf); + } + } + + /** + * Get aligned attribute length. + */ + public int getAlignedLength() { + return NetlinkConstants.alignedLengthOf(nla_len); + } + + /** + * Get attribute value as BE16. + */ + public short getValueAsBe16(short defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Short.BYTES) { + return defaultValue; + } + final ByteOrder originalOrder = byteBuffer.order(); + try { + byteBuffer.order(ByteOrder.BIG_ENDIAN); + return byteBuffer.getShort(); + } finally { + byteBuffer.order(originalOrder); + } + } + + /** + * Get attribute value as BE32. + */ + public int getValueAsBe32(int defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) { + return defaultValue; + } + final ByteOrder originalOrder = byteBuffer.order(); + try { + byteBuffer.order(ByteOrder.BIG_ENDIAN); + return byteBuffer.getInt(); + } finally { + byteBuffer.order(originalOrder); + } + } + + /** + * Get attribute value as ByteBuffer. + */ + public ByteBuffer getValueAsByteBuffer() { + if (nla_value == null) return null; + final ByteBuffer byteBuffer = ByteBuffer.wrap(nla_value); + // By convention, all buffers in this library are in native byte order because netlink is in + // native byte order. It's the order that is used by NetlinkSocket.recvMessage and the only + // order accepted by NetlinkMessage.parse. + byteBuffer.order(ByteOrder.nativeOrder()); + return byteBuffer; + } + + /** + * Get attribute value as byte. + */ + public byte getValueAsByte(byte defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Byte.BYTES) { + return defaultValue; + } + return getValueAsByteBuffer().get(); + } + + /** + * Get attribute value as Integer. + */ + public int getValueAsInt(int defaultValue) { + final ByteBuffer byteBuffer = getValueAsByteBuffer(); + if (byteBuffer == null || byteBuffer.remaining() != Integer.BYTES) { + return defaultValue; + } + return getValueAsByteBuffer().getInt(); + } + + /** + * Get attribute value as InetAddress. + */ + public InetAddress getValueAsInetAddress() { + if (nla_value == null) return null; + + try { + return InetAddress.getByAddress(nla_value); + } catch (UnknownHostException ignored) { + return null; + } + } + + /** + * Write the netlink attribute to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + final ByteOrder originalOrder = byteBuffer.order(); + final int originalPosition = byteBuffer.position(); + + byteBuffer.order(ByteOrder.nativeOrder()); + try { + byteBuffer.putShort(nla_len); + byteBuffer.putShort(nla_type); + if (nla_value != null) byteBuffer.put(nla_value); + } finally { + byteBuffer.order(originalOrder); + } + byteBuffer.position(originalPosition + getAlignedLength()); + } + + private void setValue(byte[] value) { + nla_value = value; + nla_len = (short) (NLA_HEADERLEN + ((nla_value != null) ? nla_value.length : 0)); + } + + @Override + public String toString() { + return "StructNlAttr{ " + + "nla_len{" + nla_len + "}, " + + "nla_type{" + nla_type + "}, " + + "nla_value{" + NetlinkConstants.hexify(nla_value) + "}, " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java new file mode 100644 index 0000000000..b6620f3bdc --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgErr.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import java.nio.ByteBuffer; + +/** + * struct nlmsgerr + * + * see <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlMsgErr { + public static final int STRUCT_SIZE = Integer.BYTES + StructNlMsgHdr.STRUCT_SIZE; + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse a netlink error message payload from a {@link ByteBuffer}. + * + * @param byteBuffer The buffer from which to parse the netlink error message payload. + * @return the parsed netlink error message payload, or {@code null} if the netlink error + * message payload could not be parsed successfully (for example, if it was truncated). + */ + public static StructNlMsgErr parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + final StructNlMsgErr struct = new StructNlMsgErr(); + struct.error = byteBuffer.getInt(); + struct.msg = StructNlMsgHdr.parse(byteBuffer); + return struct; + } + + public int error; + public StructNlMsgHdr msg; + + /** + * Write the netlink error message payload to {@link ByteBuffer}. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + byteBuffer.putInt(error); + if (msg != null) { + msg.pack(byteBuffer); + } + } + + @Override + public String toString() { + return "StructNlMsgErr{ " + + "error{" + error + "}, " + + "msg{" + (msg == null ? "" : msg.toString()) + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java new file mode 100644 index 0000000000..ddf1562f17 --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; + +/** + * struct nlmsghdr + * + * see <linux_src>/include/uapi/linux/netlink.h + * + * @hide + */ +public class StructNlMsgHdr { + // Already aligned. + public static final int STRUCT_SIZE = 16; + + public static final short NLM_F_REQUEST = 0x0001; + public static final short NLM_F_MULTI = 0x0002; + public static final short NLM_F_ACK = 0x0004; + public static final short NLM_F_ECHO = 0x0008; + // Flags for a GET request. + public static final short NLM_F_ROOT = 0x0100; + public static final short NLM_F_MATCH = 0x0200; + public static final short NLM_F_DUMP = NLM_F_ROOT | NLM_F_MATCH; + // Flags for a NEW request. + public static final short NLM_F_REPLACE = 0x100; + public static final short NLM_F_EXCL = 0x200; + public static final short NLM_F_CREATE = 0x400; + public static final short NLM_F_APPEND = 0x800; + + // TODO: Probably need to distinguish the flags which have the same value. For example, + // NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200). + private static String stringForNlMsgFlags(short flags) { + final StringBuilder sb = new StringBuilder(); + if ((flags & NLM_F_REQUEST) != 0) { + sb.append("NLM_F_REQUEST"); + } + if ((flags & NLM_F_MULTI) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_MULTI"); + } + if ((flags & NLM_F_ACK) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ACK"); + } + if ((flags & NLM_F_ECHO) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ECHO"); + } + if ((flags & NLM_F_ROOT) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_ROOT"); + } + if ((flags & NLM_F_MATCH) != 0) { + if (sb.length() > 0) { + sb.append("|"); + } + sb.append("NLM_F_MATCH"); + } + return sb.toString(); + } + + private static boolean hasAvailableSpace(ByteBuffer byteBuffer) { + return byteBuffer != null && byteBuffer.remaining() >= STRUCT_SIZE; + } + + /** + * Parse netlink message header from buffer. + */ + public static StructNlMsgHdr parse(ByteBuffer byteBuffer) { + if (!hasAvailableSpace(byteBuffer)) return null; + + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the exception + // of usage within unittests. + final StructNlMsgHdr struct = new StructNlMsgHdr(); + struct.nlmsg_len = byteBuffer.getInt(); + struct.nlmsg_type = byteBuffer.getShort(); + struct.nlmsg_flags = byteBuffer.getShort(); + struct.nlmsg_seq = byteBuffer.getInt(); + struct.nlmsg_pid = byteBuffer.getInt(); + + if (struct.nlmsg_len < STRUCT_SIZE) { + // Malformed. + return null; + } + return struct; + } + + public int nlmsg_len; + public short nlmsg_type; + public short nlmsg_flags; + public int nlmsg_seq; + public int nlmsg_pid; + + public StructNlMsgHdr() { + nlmsg_len = 0; + nlmsg_type = 0; + nlmsg_flags = 0; + nlmsg_seq = 0; + nlmsg_pid = 0; + } + + /** + * Write netlink message header to ByteBuffer. + */ + public void pack(ByteBuffer byteBuffer) { + // The ByteOrder must have already been set by the caller. In most + // cases ByteOrder.nativeOrder() is correct, with the possible + // exception of usage within unittests. + byteBuffer.putInt(nlmsg_len); + byteBuffer.putShort(nlmsg_type); + byteBuffer.putShort(nlmsg_flags); + byteBuffer.putInt(nlmsg_seq); + byteBuffer.putInt(nlmsg_pid); + } + + @Override + public String toString() { + return toString(null /* unknown netlink family */); + } + + /** + * Transform a netlink header into a string. The netlink family is required for transforming + * a netlink type integer into a string. + * @param nlFamily netlink family. Using Integer will not incur autoboxing penalties because + * family values are small, and all Integer objects between -128 and 127 are + * statically cached. See Integer.IntegerCache. + * @return A list of header elements. + */ + @NonNull + public String toString(@Nullable Integer nlFamily) { + final String typeStr = "" + nlmsg_type + + "(" + (nlFamily == null + ? "" : NetlinkConstants.stringForNlMsgType(nlmsg_type, nlFamily)) + + ")"; + final String flagsStr = "" + nlmsg_flags + + "(" + stringForNlMsgFlags(nlmsg_flags) + ")"; + return "StructNlMsgHdr{ " + + "nlmsg_len{" + nlmsg_len + "}, " + + "nlmsg_type{" + typeStr + "}, " + + "nlmsg_flags{" + flagsStr + ")}, " + + "nlmsg_seq{" + nlmsg_seq + "}, " + + "nlmsg_pid{" + nlmsg_pid + "} " + + "}"; + } +} diff --git a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java index 1bdee295d8..49d7654d3a 100644 --- a/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java +++ b/staticlibs/device/com/android/net/module/util/structs/PrefixInformationOption.java @@ -18,9 +18,10 @@ package com.android.net.module.util.structs; import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ND_OPTION_PIO; -import android.annotation.NonNull; import android.net.IpPrefix; +import androidx.annotation.NonNull; + import com.android.net.module.util.Struct; import com.android.net.module.util.Struct.Field; import com.android.net.module.util.Struct.Type; diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java new file mode 100644 index 0000000000..cea763c75c --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/ConntrackMessageTest.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackMessageTest { + private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN); + + private short makeCtType(short msgType) { + return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType); + } + + // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443) + public static final String CT_V4UPDATE_TCP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209 + "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6 + "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00069780"; // nla_value = 432000 (big endian) + public static final byte[] CT_V4UPDATE_TCP_BYTES = + HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + private byte[] makeIPv4TimeoutUpdateRequestTcp() throws Exception { + return ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_TCP, + (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333, + (Inet4Address) InetAddress.getByName("23.211.13.26"), 443, + 432000); + } + + // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443) + public static final String CT_V4UPDATE_UDP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146 + "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17 + "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "000000B4"; // nla_value = 180 (big endian) + public static final byte[] CT_V4UPDATE_UDP_BYTES = + HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false); + + private byte[] makeIPv4TimeoutUpdateRequestUdp() throws Exception { + return ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_UDP, + (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069, + (Inet4Address) InetAddress.getByName("216.58.197.10"), 443, + 180); + } + + @Test + public void testConntrackMakeIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp(); + assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp); + } + + @Test + public void testConntrackParseIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = makeIPv4TimeoutUpdateRequestTcp(); + final ByteBuffer byteBuffer = ByteBuffer.wrap(tcp); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(80, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags); + assertEquals(1, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("192.168.43.209"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("23.211.13.26"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 44333, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertNull(conntrackMessage.tupleReply); + + assertEquals(0 /* absent */, conntrackMessage.status); + assertEquals(432000, conntrackMessage.timeoutSec); + } + + @Test + public void testConntrackMakeIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = makeIPv4TimeoutUpdateRequestUdp(); + assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp); + } + + @Test + public void testConntrackParseIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = makeIPv4TimeoutUpdateRequestUdp(); + final ByteBuffer byteBuffer = ByteBuffer.wrap(udp); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(80, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_REPLACE | StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_ACK), hdr.nlmsg_flags); + assertEquals(1, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("100.96.167.146"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("216.58.197.10"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_UDP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 37069, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertNull(conntrackMessage.tupleReply); + + assertEquals(0 /* absent */, conntrackMessage.status); + assertEquals(180, conntrackMessage.timeoutSec); + } + + public static final String CT_V4NEW_TCP_HEX = + // CHECKSTYLE:OFF IndentationCheck + // struct nlmsghdr + "8C000000" + // length = 140 + "0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0) + "0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9) + "00000000" + // seqno = 0 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "1234" + // res_id = 0x1234 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A8500C" + // nla_type=CTA_IP_V4_SRC, ip=192.168.80.12 + "0800 0200 8C700874" + // nla_type=CTA_IP_V4_DST, ip=140.112.8.116 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 F3F1 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=62449 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "3400" + // nla_len = 52 + "0280" + // nla_type = nested CTA_TUPLE_REPLY + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116 + "0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=IPPROTO_TCP (6) + "0600 0200 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian) + "0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0300" + // nla_type = CTA_STATUS + "00000198" + // nla_value = 0b110011000 (big endian) + // IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) | + // IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00000078"; // nla_value = 120 (big endian) + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_V4NEW_TCP_BYTES = + HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseCtNew() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + final StructNlMsgHdr hdr = conntrackMessage.getHeader(); + assertNotNull(hdr); + assertEquals(140, hdr.nlmsg_len); + assertEquals(makeCtType(IPCTNL_MSG_CT_NEW), hdr.nlmsg_type); + assertEquals((short) (StructNlMsgHdr.NLM_F_CREATE | StructNlMsgHdr.NLM_F_EXCL), + hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNfGenMsg nfmsgHdr = conntrackMessage.nfGenMsg; + assertNotNull(nfmsgHdr); + assertEquals((byte) OsConstants.AF_INET, nfmsgHdr.nfgen_family); + assertEquals((byte) StructNfGenMsg.NFNETLINK_V0, nfmsgHdr.version); + assertEquals((short) 0x1234, nfmsgHdr.res_id); + + assertEquals(InetAddress.parseNumericAddress("192.168.80.12"), + conntrackMessage.tupleOrig.srcIp); + assertEquals(InetAddress.parseNumericAddress("140.112.8.116"), + conntrackMessage.tupleOrig.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleOrig.protoNum); + assertEquals((short) 62449, conntrackMessage.tupleOrig.srcPort); + assertEquals((short) 443, conntrackMessage.tupleOrig.dstPort); + + assertEquals(InetAddress.parseNumericAddress("140.112.8.116"), + conntrackMessage.tupleReply.srcIp); + assertEquals(InetAddress.parseNumericAddress("100.81.179.1"), + conntrackMessage.tupleReply.dstIp); + assertEquals((byte) OsConstants.IPPROTO_TCP, conntrackMessage.tupleReply.protoNum); + assertEquals((short) 443, conntrackMessage.tupleReply.srcPort); + assertEquals((short) 62449, conntrackMessage.tupleReply.dstPort); + + assertEquals(0x198, conntrackMessage.status); + assertEquals(120, conntrackMessage.timeoutSec); + } + + @Test + public void testParseTruncation() { + assumeTrue(USING_LE); + + // Expect no crash while parsing the truncated message which has been truncated to every + // length between 0 and its full length - 1. + for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) { + final byte[] truncated = Arrays.copyOfRange(CT_V4NEW_TCP_BYTES, 0, len); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + } + } + + @Test + public void testParseTruncationWithInvalidByte() { + assumeTrue(USING_LE); + + // Expect no crash while parsing the message which is truncated by invalid bytes. The + // message has been truncated to every length between 0 and its full length - 1. + for (byte invalid : new byte[]{(byte) 0x00, (byte) 0xff}) { + for (int len = 0; len < CT_V4NEW_TCP_BYTES.length; len++) { + final byte[] truncated = new byte[CT_V4NEW_TCP_BYTES.length]; + Arrays.fill(truncated, (byte) invalid); + System.arraycopy(CT_V4NEW_TCP_BYTES, 0, truncated, 0, len); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(truncated); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + } + } + } + + // Malformed conntrack messages. + public static final String CT_MALFORMED_HEX = + // CHECKSTYLE:OFF IndentationCheck + // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG -->| + // CTA_TUPLE_ORIG has no nla_value. + "18000000 0001 0006 00000000 00000000 02 00 0000 0400 0180" + // nested CTA_TUPLE_IP has no nla_value. + + "1C000000 0001 0006 00000000 00000000 02 00 0000 0800 0180 0400 0180" + // nested CTA_IP_V4_SRC has no nla_value. + + "20000000 0001 0006 00000000 00000000 02 00 0000 0C00 0180 0800 0180 0400 0100" + // nested CTA_TUPLE_PROTO has no nla_value. + // <-- nlmsghr -->|<-nfgenmsg->|<-- CTA_TUPLE_ORIG + + "30000000 0001 0006 00000000 00000000 02 00 0000 1C00 0180 1400 0180 0800 0100" + // -->| + + "C0A8500C 0800 0200 8C700874 0400 0280"; + // CHECKSTYLE:ON IndentationCheck + public static final byte[] CT_MALFORMED_BYTES = + HexEncoding.decode(CT_MALFORMED_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseMalformation() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_MALFORMED_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + + // Expect no crash while parsing the malformed message. + int messageCount = 0; + while (byteBuffer.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, + OsConstants.NETLINK_NETFILTER); + messageCount++; + } + assertEquals(4, messageCount); + } + + @Test + public void testToString() { + assumeTrue(USING_LE); + + final ByteBuffer byteBuffer = ByteBuffer.wrap(CT_V4NEW_TCP_BYTES); + byteBuffer.order(ByteOrder.nativeOrder()); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, OsConstants.NETLINK_NETFILTER); + assertNotNull(msg); + assertTrue(msg instanceof ConntrackMessage); + final ConntrackMessage conntrackMessage = (ConntrackMessage) msg; + + // Bug: "nlmsg_flags{1536(NLM_F_MATCH))" is not correct because StructNlMsgHdr + // #stringForNlMsgFlags can't convert all flags (ex: NLM_F_CREATE) and can't distinguish + // the flags which have the same value (ex: NLM_F_MATCH <0x200> and NLM_F_EXCL <0x200>). + // The flags output string should be "NLM_F_CREATE|NLM_F_EXCL" in this case. + // TODO: correct the flag converted string once #stringForNlMsgFlags does. + final String expected = "" + + "ConntrackMessage{" + + "nlmsghdr{StructNlMsgHdr{ nlmsg_len{140}, nlmsg_type{256(IPCTNL_MSG_CT_NEW)}, " + + "nlmsg_flags{1536(NLM_F_MATCH))}, nlmsg_seq{0}, nlmsg_pid{0} }}, " + + "nfgenmsg{NfGenMsg{ nfgen_family{AF_INET}, version{0}, res_id{4660} }}, " + + "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, " + + "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, " + + "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, " + + "timeout_sec{120}}"; + assertEquals(expected, conntrackMessage.toString()); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java new file mode 100644 index 0000000000..6b1482d839 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.IPPROTO_TCP; +import static android.system.OsConstants.IPPROTO_UDP; +import static android.system.OsConstants.NETLINK_INET_DIAG; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class InetDiagSocketTest { + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_UDP_INET4_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0103" + // flags = NLM_F_REQUEST | NLM_F_DUMP + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "02" + // family = AF_INET + "11" + // protcol = IPPROTO_UDP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2 + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_UDP_INET4_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_UDP_INET4_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2UdpInet4() throws Exception { + InetSocketAddress local = new InetSocketAddress(InetAddress.getByName("10.0.100.2"), + 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + final byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_UDP, local, remote, AF_INET, + (short) (NLM_F_REQUEST | NLM_F_DUMP)); + assertArrayEquals(INET_DIAG_REQ_V2_UDP_INET4_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_REQ_V2_TCP_INET6_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "a5de" + // idiag_sport = 42462 + "b971" + // idiag_dport = 47473 + "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b + "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, + NLM_F_REQUEST); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request with extension, INET_DIAG_INFO. + private static final String INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "02" + // family = AF_INET + "06" + // protcol = IPPROTO_TCP + "02" + // idiag_ext = INET_DIAG_INFO + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "3039" + // idiag_sport = 12345 + "d431" + // idiag_dport = 54321 + "01020304000000000000000000000000" + // idiag_src = 1.2.3.4 + "08080404000000000000000000000000" + // idiag_dst = 8.8.4.4 + "00000000" + // idiag_if + "ffffffffffffffff"; // idiag_cookie = INET_DIAG_NOCOOKIE + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_HEX.toCharArray(), false); + private static final int TCP_ALL_STATES = 0xffffffff; + @Test + public void testInetDiagReqV2TcpInetWithExt() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("1.2.3.4"), 12345); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), + 54321); + byte[] msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET, + NLM_F_REQUEST, 0 /* pad */, 2 /* idiagExt */, TCP_ALL_STATES); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET_INET_DIAG_BYTES, msg); + + local = new InetSocketAddress( + InetAddress.getByName("fe80::86c9:b2ff:fe6a:ed4b"), 42462); + remote = new InetSocketAddress(InetAddress.getByName("8.8.8.8"), + 47473); + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, remote, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_BYTES, msg); + } + + // Hexadecimal representation of InetDiagReqV2 request with no socket specified. + private static final String INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX = + // struct nlmsghdr + "48000000" + // length = 72 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0100" + // flags = NLM_F_REQUEST + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct inet_diag_req_v2 + "0a" + // family = AF_INET6 + "06" + // protcol = IPPROTO_TCP + "00" + // idiag_ext + "00" + // pad + "ffffffff" + // idiag_states + // inet_diag_sockid + "0000" + // idiag_sport + "0000" + // idiag_dport + "00000000000000000000000000000000" + // idiag_src + "00000000000000000000000000000000" + // idiag_dst + "00000000" + // idiag_if + "0000000000000000"; // idiag_cookie + + private static final byte[] INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES = + HexEncoding.decode(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_HEX.toCharArray(), false); + + @Test + public void testInetDiagReqV2TcpInet6NoIdSpecified() throws Exception { + InetSocketAddress local = new InetSocketAddress( + InetAddress.getByName("fe80::fe6a:ed4b"), 12345); + InetSocketAddress remote = new InetSocketAddress(InetAddress.getByName("8.8.4.4"), + 54321); + // Verify no socket specified if either local or remote socket address is null. + byte[] msgExt = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + byte[] msg; + try { + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, remote, AF_INET6, + NLM_F_REQUEST); + fail("Both remote and local should be null, expected UnknownHostException"); + } catch (NullPointerException e) { + } + + try { + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, local, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + fail("Both remote and local should be null, expected UnknownHostException"); + } catch (NullPointerException e) { + } + + msg = InetDiagMessage.inetDiagReqV2(IPPROTO_TCP, null, null, AF_INET6, + NLM_F_REQUEST, 0 /* pad */, 0 /* idiagExt */, TCP_ALL_STATES); + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msg); + assertArrayEquals(INET_DIAG_REQ_V2_TCP_INET6_NO_ID_SPECIFIED_BYTES, msgExt); + } + + // Hexadecimal representation of InetDiagReqV2 request. + private static final String INET_DIAG_MSG_HEX = + // struct nlmsghdr + "58000000" + // length = 88 + "1400" + // type = SOCK_DIAG_BY_FAMILY + "0200" + // flags = NLM_F_MULTI + "00000000" + // seqno + "f5220000" + // pid (0 == kernel) + // struct inet_diag_msg + "0a" + // family = AF_INET6 + "01" + // idiag_state + "00" + // idiag_timer + "00" + // idiag_retrans + // inet_diag_sockid + "a817" + // idiag_sport = 43031 + "960f" + // idiag_dport = 38415 + "fe8000000000000086c9b2fffe6aed4b" + // idiag_src = fe80::86c9:b2ff:fe6a:ed4b + "00000000000000000000ffff08080808" + // idiag_dst = 8.8.8.8 + "00000000" + // idiag_if + "ffffffffffffffff" + // idiag_cookie = INET_DIAG_NOCOOKIE + "00000000" + // idiag_expires + "00000000" + // idiag_rqueue + "00000000" + // idiag_wqueue + "a3270000" + // idiag_uid + "A57E1900"; // idiag_inode + private static final byte[] INET_DIAG_MSG_BYTES = + HexEncoding.decode(INET_DIAG_MSG_HEX.toCharArray(), false); + + @Test + public void testParseInetDiagResponse() throws Exception { + final ByteBuffer byteBuffer = ByteBuffer.wrap(INET_DIAG_MSG_BYTES); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG); + assertNotNull(msg); + + assertTrue(msg instanceof InetDiagMessage); + final InetDiagMessage inetDiagMsg = (InetDiagMessage) msg; + assertEquals(10147, inetDiagMsg.mStructInetDiagMsg.idiag_uid); + + final StructNlMsgHdr hdr = inetDiagMsg.getHeader(); + assertNotNull(hdr); + assertEquals(NetlinkConstants.SOCK_DIAG_BY_FAMILY, hdr.nlmsg_type); + assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(8949, hdr.nlmsg_pid); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java new file mode 100644 index 0000000000..538c09bc50 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NduseroptMessageTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.net.InetAddresses.parseNumericAddress; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import android.net.IpPrefix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NduseroptMessageTest { + + private static final byte ICMP_TYPE_RA = (byte) 134; + + private static final int IFINDEX1 = 15715755; + private static final int IFINDEX2 = 1431655765; + + // IPv6, 0 bytes of options, interface index 15715755, type 134 (RA), code 0, padding. + private static final String HDR_EMPTY = "0a00" + "0000" + "abcdef00" + "8600000000000000"; + + // IPv6, 16 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. + private static final String HDR_16BYTE = "0a00" + "1000" + "55555555" + "8600000000000000"; + + // IPv6, 32 bytes of options, interface index 1431655765, type 134 (RA), code 0, padding. + private static final String HDR_32BYTE = "0a00" + "2000" + "55555555" + "8600000000000000"; + + // PREF64 option, 2001:db8:3:4:5:6::/96, lifetime=10064 + private static final String OPT_PREF64 = "2602" + "2750" + "20010db80003000400050006"; + + // Length 20, NDUSEROPT_SRCADDR, fe80:2:3:4:5:6:7:8 + private static final String NLA_SRCADDR = "1400" + "0100" + "fe800002000300040005000600070008"; + + private static final InetAddress SADDR1 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX1); + private static final InetAddress SADDR2 = parseNumericAddress("fe80:2:3:4:5:6:7:8%" + IFINDEX2); + + private static final String MSG_EMPTY = HDR_EMPTY + NLA_SRCADDR; + private static final String MSG_PREF64 = HDR_16BYTE + OPT_PREF64 + NLA_SRCADDR; + + @Test + public void testParsing() { + NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_EMPTY)); + assertMatches(AF_INET6, 0, IFINDEX1, ICMP_TYPE_RA, (byte) 0, SADDR1, msg); + assertNull(msg.option); + + msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + + @Test + public void testParseWithinNetlinkMessage() throws Exception { + // A NduseroptMessage inside a netlink message. Ensure that it parses the same way both by + // parsing the netlink message via NetlinkMessage.parse() and by parsing the option itself + // with NduseroptMessage.parse(). + final String hexBytes = + "44000000440000000000000000000000" // len=68, RTM_NEWNDUSEROPT + + "0A0010001E0000008600000000000000" // IPv6, opt_bytes=16, ifindex=30, RA + + "260202580064FF9B0000000000000000" // pref64, prefix=64:ff9b::/96, 600 + + "14000100FE800000000000000250B6FFFEB7C499"; // srcaddr=fe80::250:b6ff:feb7:c499 + + ByteBuffer buf = toBuffer(hexBytes); + assertEquals(68, buf.limit()); + buf.order(ByteOrder.nativeOrder()); + + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNotNull(nlMsg); + assertTrue(nlMsg instanceof NduseroptMessage); + + NduseroptMessage msg = (NduseroptMessage) nlMsg; + InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30"); + assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertPref64Option("64:ff9b::/96", msg.option); + + final String hexBytesWithoutHeader = hexBytes.substring(StructNlMsgHdr.STRUCT_SIZE * 2); + ByteBuffer bufWithoutHeader = toBuffer(hexBytesWithoutHeader); + assertEquals(52, bufWithoutHeader.limit()); + msg = parseNduseroptMessage(bufWithoutHeader); + assertMatches(AF_INET6, 16, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertPref64Option("64:ff9b::/96", msg.option); + } + + @Test + public void testParseUnknownOptionWithinNetlinkMessage() throws Exception { + final String hexBytes = + "4C0000004400000000000000000000000" + + "A0018001E0000008600000000000000" + + "1903000000001770FD123456789000000000000000000001" // RDNSS option + + "14000100FE800000000000000250B6FFFEB7C499"; + + ByteBuffer buf = toBuffer(hexBytes); + assertEquals(76, buf.limit()); + buf.order(ByteOrder.nativeOrder()); + + NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_ROUTE); + assertNotNull(nlMsg); + assertTrue(nlMsg instanceof NduseroptMessage); + + NduseroptMessage msg = (NduseroptMessage) nlMsg; + InetAddress srcaddr = InetAddress.getByName("fe80::250:b6ff:feb7:c499%30"); + assertMatches(AF_INET6, 24, 30, ICMP_TYPE_RA, (byte) 0, srcaddr, msg); + assertEquals(NdOption.UNKNOWN, msg.option); + } + + @Test + public void testUnknownOption() { + ByteBuffer buf = toBuffer(MSG_PREF64); + // Replace the PREF64 option type (38) with an unknown option number. + final int optionStart = NduseroptMessage.STRUCT_SIZE; + assertEquals(38, buf.get(optionStart)); + buf.put(optionStart, (byte) 42); + + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertEquals(NdOption.UNKNOWN, msg.option); + + buf.flip(); + assertEquals(42, buf.get(optionStart)); + buf.put(optionStart, (byte) 38); + + msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + + @Test + public void testZeroLengthOption() { + // Make sure an unknown option with a 0-byte length is ignored and parsing continues with + // the address, which comes after it. + final String hexString = HDR_16BYTE + "00000000000000000000000000000000" + NLA_SRCADDR; + ByteBuffer buf = toBuffer(hexString); + assertEquals(52, buf.limit()); + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertNull(msg.option); + } + + @Test + public void testTooLongOption() { + // Make sure that if an option's length is too long, it's ignored and parsing continues with + // the address, which comes after it. + final String hexString = HDR_16BYTE + "26030000000000000000000000000000" + NLA_SRCADDR; + ByteBuffer buf = toBuffer(hexString); + assertEquals(52, buf.limit()); + NduseroptMessage msg = parseNduseroptMessage(buf); + assertMatches(AF_INET6, 16, IFINDEX2, ICMP_TYPE_RA, (byte) 0, SADDR2, msg); + assertNull(msg.option); + } + + @Test + public void testOptionsTooLong() { + // Header claims 32 bytes of options. Buffer ends before options end. + String hexString = HDR_32BYTE + OPT_PREF64; + ByteBuffer buf = toBuffer(hexString); + assertEquals(32, buf.limit()); + assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE)); + + // Header claims 32 bytes of options. Buffer ends at end of options with no source address. + hexString = HDR_32BYTE + OPT_PREF64 + OPT_PREF64; + buf = toBuffer(hexString); + assertEquals(48, buf.limit()); + assertNull(NduseroptMessage.parse(toBuffer(hexString), NETLINK_ROUTE)); + } + + @Test + public void testTruncation() { + final int optLen = MSG_PREF64.length() / 2; // 1 byte = 2 hex chars + for (int len = 0; len < optLen; len++) { + ByteBuffer buf = toBuffer(MSG_PREF64.substring(0, len * 2)); + NduseroptMessage msg = parseNduseroptMessage(buf); + if (len < optLen) { + assertNull(msg); + } else { + assertNotNull(msg); + assertPref64Option("2001:db8:3:4:5:6::/96", msg.option); + } + } + } + + @Test + public void testToString() { + NduseroptMessage msg = parseNduseroptMessage(toBuffer(MSG_PREF64)); + assertNotNull(msg); + assertEquals("Nduseroptmsg(10, 16, 1431655765, 134, 0, fe80:2:3:4:5:6:7:8%1431655765)", + msg.toString()); + } + + // Convenience method to parse a NduseroptMessage that's not part of a netlink message. + private NduseroptMessage parseNduseroptMessage(ByteBuffer buf) { + return NduseroptMessage.parse(null, buf); + } + + private ByteBuffer toBuffer(String hexString) { + return ByteBuffer.wrap(HexEncoding.decode(hexString)); + } + + private void assertMatches(int family, int optsLen, int ifindex, byte icmpType, + byte icmpCode, InetAddress srcaddr, NduseroptMessage msg) { + assertNotNull(msg); + assertEquals(family, msg.family); + assertEquals(ifindex, msg.ifindex); + assertEquals(optsLen, msg.opts_len); + assertEquals(icmpType, msg.icmp_type); + assertEquals(icmpCode, msg.icmp_code); + assertEquals(srcaddr, msg.srcaddr); + } + + private void assertPref64Option(String prefix, NdOption opt) { + assertNotNull(opt); + assertTrue(opt instanceof StructNdOptPref64); + StructNdOptPref64 pref64Opt = (StructNdOptPref64) opt; + assertEquals(new IpPrefix(prefix), pref64Opt.prefix); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java new file mode 100644 index 0000000000..143e4d4ac5 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkConstantsTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_INET_DIAG; +import static android.system.OsConstants.NETLINK_NETFILTER; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_CTRZERO; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_DYING; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_STATS_CPU; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_GET_UNCONFIRMED; +import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW; +import static com.android.net.module.util.netlink.NetlinkConstants.NFNL_SUBSYS_CTNETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_ERROR; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_NOOP; +import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_OVERRUN; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_GETRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWADDR; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNDUSEROPT; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWROUTE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWRULE; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_SETLINK; +import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY; +import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkConstantsTest { + private static final short UNKNOWN_FAMILY = 1234; + + private short makeCtType(short msgType) { + return (short) (NFNL_SUBSYS_CTNETLINK << 8 | (byte) msgType); + } + + @Test + public void testStringForNlMsgType() { + assertEquals("RTM_NEWLINK", stringForNlMsgType(RTM_NEWLINK, NETLINK_ROUTE)); + assertEquals("RTM_DELLINK", stringForNlMsgType(RTM_DELLINK, NETLINK_ROUTE)); + assertEquals("RTM_GETLINK", stringForNlMsgType(RTM_GETLINK, NETLINK_ROUTE)); + assertEquals("RTM_SETLINK", stringForNlMsgType(RTM_SETLINK, NETLINK_ROUTE)); + assertEquals("RTM_NEWADDR", stringForNlMsgType(RTM_NEWADDR, NETLINK_ROUTE)); + assertEquals("RTM_DELADDR", stringForNlMsgType(RTM_DELADDR, NETLINK_ROUTE)); + assertEquals("RTM_GETADDR", stringForNlMsgType(RTM_GETADDR, NETLINK_ROUTE)); + assertEquals("RTM_NEWROUTE", stringForNlMsgType(RTM_NEWROUTE, NETLINK_ROUTE)); + assertEquals("RTM_DELROUTE", stringForNlMsgType(RTM_DELROUTE, NETLINK_ROUTE)); + assertEquals("RTM_GETROUTE", stringForNlMsgType(RTM_GETROUTE, NETLINK_ROUTE)); + assertEquals("RTM_NEWNEIGH", stringForNlMsgType(RTM_NEWNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_DELNEIGH", stringForNlMsgType(RTM_DELNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_GETNEIGH", stringForNlMsgType(RTM_GETNEIGH, NETLINK_ROUTE)); + assertEquals("RTM_NEWRULE", stringForNlMsgType(RTM_NEWRULE, NETLINK_ROUTE)); + assertEquals("RTM_DELRULE", stringForNlMsgType(RTM_DELRULE, NETLINK_ROUTE)); + assertEquals("RTM_GETRULE", stringForNlMsgType(RTM_GETRULE, NETLINK_ROUTE)); + assertEquals("RTM_NEWNDUSEROPT", stringForNlMsgType(RTM_NEWNDUSEROPT, NETLINK_ROUTE)); + + assertEquals("SOCK_DIAG_BY_FAMILY", + stringForNlMsgType(SOCK_DIAG_BY_FAMILY, NETLINK_INET_DIAG)); + + assertEquals("IPCTNL_MSG_CT_NEW", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_DELETE", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_DELETE), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_CTRZERO", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_CTRZERO), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_STATS_CPU", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS_CPU), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_STATS", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_STATS), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_DYING", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_DYING), NETLINK_NETFILTER)); + assertEquals("IPCTNL_MSG_CT_GET_UNCONFIRMED", + stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_GET_UNCONFIRMED), NETLINK_NETFILTER)); + } + + @Test + public void testStringForNlMsgType_ControlMessage() { + for (int family : new int[]{NETLINK_ROUTE, NETLINK_INET_DIAG, NETLINK_NETFILTER}) { + assertEquals("NLMSG_NOOP", stringForNlMsgType(NLMSG_NOOP, family)); + assertEquals("NLMSG_ERROR", stringForNlMsgType(NLMSG_ERROR, family)); + assertEquals("NLMSG_DONE", stringForNlMsgType(NLMSG_DONE, family)); + assertEquals("NLMSG_OVERRUN", stringForNlMsgType(NLMSG_OVERRUN, family)); + } + } + + @Test + public void testStringForNlMsgType_UnknownFamily() { + assertTrue(stringForNlMsgType(RTM_NEWLINK, UNKNOWN_FAMILY).startsWith("unknown")); + assertTrue(stringForNlMsgType(SOCK_DIAG_BY_FAMILY, UNKNOWN_FAMILY).startsWith("unknown")); + assertTrue(stringForNlMsgType(makeCtType(IPCTNL_MSG_CT_NEW), UNKNOWN_FAMILY) + .startsWith("unknown")); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java new file mode 100644 index 0000000000..ab7d9cf56c --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkErrorMessageTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_ACK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkErrorMessageTest { + private static final String TAG = "NetlinkErrorMessageTest"; + + // Hexadecimal representation of packet capture. + public static final String NLM_ERROR_OK_HEX = + // struct nlmsghdr + "24000000" + // length = 36 + "0200" + // type = 2 (NLMSG_ERROR) + "0000" + // flags + "26350000" + // seqno + "64100000" + // pid = userspace process + // error integer + "00000000" + // "errno" (0 == OK) + // struct nlmsghdr + "30000000" + // length (48) of original request + "1C00" + // type = 28 (RTM_NEWNEIGH) + "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) + "26350000" + // seqno + "00000000"; // pid = kernel + public static final byte[] NLM_ERROR_OK = + HexEncoding.decode(NLM_ERROR_OK_HEX.toCharArray(), false); + + @Test + public void testParseNlmErrorOk() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(NLM_ERROR_OK); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof NetlinkErrorMessage); + final NetlinkErrorMessage errorMsg = (NetlinkErrorMessage) msg; + + final StructNlMsgHdr hdr = errorMsg.getHeader(); + assertNotNull(hdr); + assertEquals(36, hdr.nlmsg_len); + assertEquals(NetlinkConstants.NLMSG_ERROR, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(13606, hdr.nlmsg_seq); + assertEquals(4196, hdr.nlmsg_pid); + + final StructNlMsgErr err = errorMsg.getNlMsgError(); + assertNotNull(err); + assertEquals(0, err.error); + assertNotNull(err.msg); + assertEquals(48, err.msg.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, err.msg.nlmsg_type); + assertEquals((NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE), err.msg.nlmsg_flags); + assertEquals(13606, err.msg.nlmsg_seq); + assertEquals(0, err.msg.nlmsg_pid); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkSocketTest.java new file mode 100644 index 0000000000..33ea602312 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkSocketTest.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.AF_INET; +import static android.system.OsConstants.AF_INET6; +import static android.system.OsConstants.AF_UNSPEC; +import static android.system.OsConstants.EACCES; +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.NetlinkSocket.DEFAULT_RECV_BUFSIZE; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.content.Context; +import android.system.ErrnoException; +import android.system.NetlinkSocketAddress; +import android.system.Os; + +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.Struct; +import com.android.net.module.util.Struct.Field; +import com.android.net.module.util.Struct.Type; + +import libcore.io.IoUtils; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.FileDescriptor; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class NetlinkSocketTest { + private static final String TAG = "NetlinkSocketTest"; + private static final int TEST_SEQNO = 5; + private static final int TEST_TIMEOUT_MS = 500; + + @Test + public void testGetNeighborsQuery() throws Exception { + final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE); + assertNotNull(fd); + + NetlinkSocket.connectToKernel(fd); + + final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd); + assertNotNull(localAddr); + assertEquals(0, localAddr.getGroupsMask()); + assertTrue(0 != localAddr.getPortId()); + + final byte[] req = RtNetlinkNeighborMessage.newGetNeighborsRequest(TEST_SEQNO); + assertNotNull(req); + + final Context ctx = InstrumentationRegistry.getInstrumentation().getContext(); + final int targetSdk = + ctx.getPackageManager() + .getApplicationInfo(ctx.getPackageName(), 0) + .targetSdkVersion; + + // Apps targeting an SDK version > S are not allowed to send RTM_GETNEIGH{TBL} messages + if (SdkLevel.isAtLeastT() && targetSdk > 31) { + try { + NetlinkSocket.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS); + fail("RTM_GETNEIGH is not allowed for apps targeting SDK > 31 on T+ platforms," + + " target SDK version: " + targetSdk); + } catch (ErrnoException e) { + // Expected + assertEquals(e.errno, EACCES); + return; + } + } + + // Check that apps targeting lower API levels / running on older platforms succeed + assertEquals(req.length, + NetlinkSocket.sendMessage(fd, req, 0, req.length, TEST_TIMEOUT_MS)); + + int neighMessageCount = 0; + int doneMessageCount = 0; + + while (doneMessageCount == 0) { + ByteBuffer response = + NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, TEST_TIMEOUT_MS); + assertNotNull(response); + assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); + assertEquals(0, response.position()); + assertEquals(ByteOrder.nativeOrder(), response.order()); + + // Verify the messages at least appears minimally reasonable. + while (response.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(response, NETLINK_ROUTE); + assertNotNull(msg); + final StructNlMsgHdr hdr = msg.getHeader(); + assertNotNull(hdr); + + if (hdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + doneMessageCount++; + continue; + } + + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + assertTrue((hdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0); + assertEquals(TEST_SEQNO, hdr.nlmsg_seq); + assertEquals(localAddr.getPortId(), hdr.nlmsg_pid); + + neighMessageCount++; + } + } + + assertEquals(1, doneMessageCount); + // TODO: make sure this test passes sanely in airplane mode. + assertTrue(neighMessageCount > 0); + + IoUtils.closeQuietly(fd); + } + + @Test + public void testBasicWorkingGetAddrQuery() throws Exception { + final FileDescriptor fd = NetlinkSocket.forProto(NETLINK_ROUTE); + assertNotNull(fd); + + NetlinkSocket.connectToKernel(fd); + + final NetlinkSocketAddress localAddr = (NetlinkSocketAddress) Os.getsockname(fd); + assertNotNull(localAddr); + assertEquals(0, localAddr.getGroupsMask()); + assertTrue(0 != localAddr.getPortId()); + + final int testSeqno = 8; + final byte[] req = newGetAddrRequest(testSeqno); + assertNotNull(req); + + final long timeout = 500; + assertEquals(req.length, NetlinkSocket.sendMessage(fd, req, 0, req.length, timeout)); + + int addrMessageCount = 0; + + while (true) { + ByteBuffer response = NetlinkSocket.recvMessage(fd, DEFAULT_RECV_BUFSIZE, timeout); + assertNotNull(response); + assertTrue(StructNlMsgHdr.STRUCT_SIZE <= response.limit()); + assertEquals(0, response.position()); + assertEquals(ByteOrder.nativeOrder(), response.order()); + + final StructNlMsgHdr nlmsghdr = StructNlMsgHdr.parse(response); + assertNotNull(nlmsghdr); + + if (nlmsghdr.nlmsg_type == NetlinkConstants.NLMSG_DONE) { + break; + } + + assertEquals(NetlinkConstants.RTM_NEWADDR, nlmsghdr.nlmsg_type); + assertTrue((nlmsghdr.nlmsg_flags & StructNlMsgHdr.NLM_F_MULTI) != 0); + assertEquals(testSeqno, nlmsghdr.nlmsg_seq); + assertEquals(localAddr.getPortId(), nlmsghdr.nlmsg_pid); + addrMessageCount++; + + final IfaddrMsg ifaMsg = Struct.parse(IfaddrMsg.class, response); + assertTrue( + "Non-IP address family: " + ifaMsg.family, + ifaMsg.family == AF_INET || ifaMsg.family == AF_INET6); + } + + assertTrue(addrMessageCount > 0); + + IoUtils.closeQuietly(fd); + } + + /** A convenience method to create an RTM_GETADDR request message. */ + private static byte[] newGetAddrRequest(int seqNo) { + final int length = StructNlMsgHdr.STRUCT_SIZE + Struct.getSize(RtgenMsg.class); + final byte[] bytes = new byte[length]; + final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.order(ByteOrder.nativeOrder()); + + final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr(); + nlmsghdr.nlmsg_len = length; + nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETADDR; + nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + nlmsghdr.nlmsg_seq = seqNo; + nlmsghdr.pack(byteBuffer); + + final RtgenMsg rtgenMsg = new RtgenMsg(); + rtgenMsg.family = (byte) AF_UNSPEC; + rtgenMsg.writeToByteBuffer(byteBuffer); + + return bytes; + } + + /** From uapi/linux/rtnetlink.h */ + private static class RtgenMsg extends Struct { + @Field(order = 0, type = Type.U8) + public short family; + } + + /** + * From uapi/linux/ifaddr.h + * + * Public ensures visibility to Struct class + */ + public static class IfaddrMsg extends Struct { + @Field(order = 0, type = Type.U8) + public short family; + + @Field(order = 1, type = Type.U8) + public short prefixlen; + + @Field(order = 2, type = Type.U8) + public short flags; + + @Field(order = 3, type = Type.U8) + public short scope; + + @Field(order = 4, type = Type.U32) + public long index; + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java new file mode 100644 index 0000000000..4d8900cdbd --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkNeighborMessageTest.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static android.system.OsConstants.NETLINK_ROUTE; + +import static com.android.net.module.util.netlink.StructNdMsg.NUD_STALE; +import static com.android.testutils.NetlinkTestUtils.makeDelNeighMessage; +import static com.android.testutils.NetlinkTestUtils.makeNewNeighMessage; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.net.InetAddresses; +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class RtNetlinkNeighborMessageTest { + private static final String TAG = "RtNetlinkNeighborMessageTest"; + + public static final byte[] RTM_DELNEIGH = makeDelNeighMessage( + InetAddresses.parseNumericAddress("192.168.159.254"), NUD_STALE); + + public static final byte[] RTM_NEWNEIGH = makeNewNeighMessage( + InetAddresses.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), NUD_STALE); + + // An example of the full response from an RTM_GETNEIGH query. + private static final String RTM_GETNEIGH_RESPONSE_HEX = + // <-- struct nlmsghr -->|<-- struct ndmsg -->|<-- struct nlattr: NDA_DST -->|<-- NDA_LLADDR -->|<-- NDA_PROBES -->|<-- NDA_CACHEINFO -->| + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000001 0a00 0200 333300000001 0000 0800 0400 00000000 1400 0300 a2280000 32110000 32110000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff000001 0a00 0200 3333ff000001 0000 0800 0400 00000000 1400 0300 0d280000 9d100000 9d100000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0400 80 01 1400 0100 20010db800040ca00000000000000001 0a00 0200 84c9b26aed4b 0000 0800 0400 04000000 1400 0300 90100000 90100000 90080000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff47da19 0a00 0200 3333ff47da19 0000 0800 0400 00000000 1400 0300 a1280000 31110000 31110000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 912a0000 21130000 21130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 922a0000 22130000 22130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ff5c2a83 0a00 0200 3333ff5c2a83 0000 0800 0400 00000000 1400 0300 391c0000 c9040000 c9040000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 01000000 4000 00 02 1400 0100 00000000000000000000000000000000 0a00 0200 000000000000 0000 0800 0400 00000000 1400 0300 cd180200 5d010200 5d010200 08000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 352a0000 c5120000 c5120000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff020000000000000000000000000016 0a00 0200 333300000016 0000 0800 0400 00000000 1400 0300 982a0000 28130000 28130000 00000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 0800 80 01 1400 0100 fe8000000000000086c9b2fffe6aed4b 0a00 0200 84c9b26aed4b 0000 0800 0400 00000000 1400 0300 23000000 24000000 57000000 13000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 15000000 4000 00 05 1400 0100 ff0200000000000000000001ffeace3b 0a00 0200 3333ffeace3b 0000 0800 0400 00000000 1400 0300 992a0000 29130000 29130000 01000000" + + "58000000 1c00 0200 00000000 3e2b0000 0a 00 0000 14000000 4000 00 05 1400 0100 ff020000000000000000000000000002 0a00 0200 333300000002 0000 0800 0400 00000000 1400 0300 2e2a0000 be120000 be120000 00000000" + + "44000000 1c00 0200 00000000 3e2b0000 02 00 0000 18000000 4000 00 03 0800 0100 00000000 0400 0200 0800 0400 00000000 1400 0300 75280000 05110000 05110000 22000000"; + public static final byte[] RTM_GETNEIGH_RESPONSE = + HexEncoding.decode(RTM_GETNEIGH_RESPONSE_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testParseRtmDelNeigh() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_DELNEIGH); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(76, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_DELNEIGH, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); + assertNotNull(ndmsgHdr); + assertEquals((byte) OsConstants.AF_INET, ndmsgHdr.ndm_family); + assertEquals(21, ndmsgHdr.ndm_ifindex); + assertEquals(NUD_STALE, ndmsgHdr.ndm_state); + final InetAddress destination = neighMsg.getDestination(); + assertNotNull(destination); + assertEquals(InetAddress.parseNumericAddress("192.168.159.254"), destination); + } + + @Test + public void testParseRtmNewNeigh() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_NEWNEIGH); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(88, hdr.nlmsg_len); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertEquals(0, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(0, hdr.nlmsg_pid); + + final StructNdMsg ndmsgHdr = neighMsg.getNdHeader(); + assertNotNull(ndmsgHdr); + assertEquals((byte) OsConstants.AF_INET6, ndmsgHdr.ndm_family); + assertEquals(21, ndmsgHdr.ndm_ifindex); + assertEquals(NUD_STALE, ndmsgHdr.ndm_state); + final InetAddress destination = neighMsg.getDestination(); + assertNotNull(destination); + assertEquals(InetAddress.parseNumericAddress("fe80::86c9:b2ff:fe6a:ed4b"), destination); + } + + @Test + public void testParseRtmGetNeighResponse() { + final ByteBuffer byteBuffer = ByteBuffer.wrap(RTM_GETNEIGH_RESPONSE); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); // For testing. + + int messageCount = 0; + while (byteBuffer.remaining() > 0) { + final NetlinkMessage msg = NetlinkMessage.parse(byteBuffer, NETLINK_ROUTE); + assertNotNull(msg); + assertTrue(msg instanceof RtNetlinkNeighborMessage); + final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) msg; + + final StructNlMsgHdr hdr = neighMsg.getHeader(); + assertNotNull(hdr); + assertEquals(NetlinkConstants.RTM_NEWNEIGH, hdr.nlmsg_type); + assertEquals(StructNlMsgHdr.NLM_F_MULTI, hdr.nlmsg_flags); + assertEquals(0, hdr.nlmsg_seq); + assertEquals(11070, hdr.nlmsg_pid); + + final int probes = neighMsg.getProbes(); + assertTrue("Unexpected number of probes. Got " + probes + ", max=5", + probes < 5); + final int ndm_refcnt = neighMsg.getCacheInfo().ndm_refcnt; + assertTrue("nda_cacheinfo has unexpectedly high ndm_refcnt: " + ndm_refcnt, + ndm_refcnt < 0x100); + + messageCount++; + } + // TODO: add more detailed spot checks. + assertEquals(14, messageCount); + } + + @Test + public void testCreateRtmNewNeighMessage() { + final int seqNo = 2635; + final int ifIndex = 14; + final byte[] llAddr = + new byte[] { (byte) 1, (byte) 2, (byte) 3, (byte) 4, (byte) 5, (byte) 6 }; + + // Hexadecimal representation of our created packet. + final String expectedNewNeighHex = + // struct nlmsghdr + "30000000" + // length = 48 + "1c00" + // type = 28 (RTM_NEWNEIGH) + "0501" + // flags (NLM_F_REQUEST | NLM_F_ACK | NLM_F_REPLACE) + "4b0a0000" + // seqno + "00000000" + // pid (0 == kernel) + // struct ndmsg + "02" + // family + "00" + // pad1 + "0000" + // pad2 + "0e000000" + // interface index (14) + "0800" + // NUD state (0x08 == NUD_DELAY) + "00" + // flags + "00" + // type + // struct nlattr: NDA_DST + "0800" + // length = 8 + "0100" + // type (1 == NDA_DST, for neighbor messages) + "7f000001" + // IPv4 address (== 127.0.0.1) + // struct nlattr: NDA_LLADDR + "0a00" + // length = 10 + "0200" + // type (2 == NDA_LLADDR, for neighbor messages) + "010203040506" + // MAC Address (== 01:02:03:04:05:06) + "0000"; // padding, for 4 byte alignment + final byte[] expectedNewNeigh = + HexEncoding.decode(expectedNewNeighHex.toCharArray(), false); + + final byte[] bytes = RtNetlinkNeighborMessage.newNewNeighborMessage( + seqNo, Inet4Address.LOOPBACK, StructNdMsg.NUD_DELAY, ifIndex, llAddr); + if (!Arrays.equals(expectedNewNeigh, bytes)) { + assertEquals(expectedNewNeigh.length, bytes.length); + for (int i = 0; i < Math.min(expectedNewNeigh.length, bytes.length); i++) { + assertEquals(expectedNewNeigh[i], bytes[i]); + } + } + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java new file mode 100644 index 0000000000..57248eada6 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNdOptPref64Test.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static com.android.net.module.util.netlink.StructNdOptPref64.getScaledLifetimePlc; +import static com.android.net.module.util.netlink.StructNdOptPref64.plcToPrefixLength; +import static com.android.net.module.util.netlink.StructNdOptPref64.prefixLengthToPlc; +import static com.android.testutils.MiscAsserts.assertThrows; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import android.net.IpPrefix; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import libcore.util.HexEncoding; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.InetAddress; +import java.nio.ByteBuffer; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNdOptPref64Test { + + private static final String PREFIX1 = "64:ff9b::"; + private static final String PREFIX2 = "2001:db8:1:2:3:64::"; + + private static byte[] prefixBytes(String addrString) throws Exception { + InetAddress addr = InetAddress.getByName(addrString); + byte[] prefixBytes = new byte[12]; + System.arraycopy(addr.getAddress(), 0, prefixBytes, 0, 12); + return prefixBytes; + } + + private static IpPrefix prefix(String addrString, int prefixLength) throws Exception { + return new IpPrefix(InetAddress.getByName(addrString), prefixLength); + } + + private void assertPref64OptMatches(int lifetime, IpPrefix prefix, StructNdOptPref64 opt) { + assertEquals(StructNdOptPref64.TYPE, opt.type); + assertEquals(2, opt.length); + assertEquals(lifetime, opt.lifetime); + assertEquals(prefix, opt.prefix); + } + + private void assertToByteBufferMatches(StructNdOptPref64 opt, String expected) { + String actual = HexEncoding.encodeToString(opt.toByteBuffer().array()); + assertEquals(expected, actual); + } + + private ByteBuffer makeNdOptPref64(int lifetime, byte[] prefix, int prefixLengthCode) { + if (prefix.length != 12) throw new IllegalArgumentException("Prefix must be 12 bytes"); + + ByteBuffer buf = ByteBuffer.allocate(16) + .put((byte) StructNdOptPref64.TYPE) + .put((byte) StructNdOptPref64.LENGTH) + .putShort(getScaledLifetimePlc(lifetime, prefixLengthCode)) + .put(prefix, 0, 12); + + buf.flip(); + return buf; + } + + @Test + public void testParseCannedOption() throws Exception { + String hexBytes = "2602" // type=38, len=2 (16 bytes) + + "0088" // lifetime=136, PLC=0 (/96) + + "20010DB80003000400050006"; // 2001:db8:3:4:5:6/96 + byte[] rawBytes = HexEncoding.decode(hexBytes); + StructNdOptPref64 opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes)); + assertPref64OptMatches(136, prefix("2001:DB8:3:4:5:6::", 96), opt); + assertToByteBufferMatches(opt, hexBytes); + + hexBytes = "2602" // type=38, len=2 (16 bytes) + + "2752" // lifetime=10064, PLC=2 (/56) + + "0064FF9B0000000000000000"; // 64:ff9b::/56 + rawBytes = HexEncoding.decode(hexBytes); + opt = StructNdOptPref64.parse(ByteBuffer.wrap(rawBytes)); + assertPref64OptMatches(10064, prefix("64:FF9B::", 56), opt); + assertToByteBufferMatches(opt, hexBytes); + } + + @Test + public void testParsing() throws Exception { + // Valid. + ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 0); + StructNdOptPref64 opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 96), opt); + + // Valid, zero lifetime, /64. + buf = makeNdOptPref64(0, prefixBytes(PREFIX1), 1); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(0, prefix(PREFIX1, 64), opt); + + // Valid, low lifetime, /56. + buf = makeNdOptPref64(8, prefixBytes(PREFIX2), 2); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(8, prefix(PREFIX2, 56), opt); + assertEquals(new IpPrefix("2001:db8:1::/56"), opt.prefix); // Prefix is truncated. + + // Valid, maximum lifetime, /32. + buf = makeNdOptPref64(65528, prefixBytes(PREFIX2), 5); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(65528, prefix(PREFIX2, 32), opt); + assertEquals(new IpPrefix("2001:db8::/32"), opt.prefix); // Prefix is truncated. + + // Lifetime not divisible by 8. + buf = makeNdOptPref64(300, prefixBytes(PREFIX2), 0); + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(296, prefix(PREFIX2, 96), opt); + + // Invalid prefix length codes. + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 6); + assertNull(StructNdOptPref64.parse(buf)); + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 7); + assertNull(StructNdOptPref64.parse(buf)); + + // Truncated to varying lengths... + buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 3); + final int len = buf.limit(); + for (int i = 0; i < buf.limit() - 1; i++) { + buf.flip(); + buf.limit(i); + assertNull("Option truncated to " + i + " bytes, should have returned null", + StructNdOptPref64.parse(buf)); + } + buf.flip(); + buf.limit(len); + // ... but otherwise OK. + opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 48), opt); + } + + @Test + public void testToString() throws Exception { + ByteBuffer buf = makeNdOptPref64(600, prefixBytes(PREFIX1), 4); + StructNdOptPref64 opt = StructNdOptPref64.parse(buf); + assertPref64OptMatches(600, prefix(PREFIX1, 40), opt); + assertEquals("NdOptPref64(64:ff9b::/40, 600)", opt.toString()); + } + + private void assertInvalidPlc(int plc) { + assertThrows(IllegalArgumentException.class, () -> plcToPrefixLength(plc)); + } + + @Test + public void testPrefixLengthToPlc() { + for (int i = 0; i < 6; i++) { + assertEquals(i, prefixLengthToPlc(plcToPrefixLength(i))); + } + assertInvalidPlc(-1); + assertInvalidPlc(6); + assertInvalidPlc(7); + assertEquals(0, prefixLengthToPlc(96)); + } + + + private void assertInvalidParameters(IpPrefix prefix, int lifetime) { + assertThrows(IllegalArgumentException.class, () -> new StructNdOptPref64(prefix, lifetime)); + } + + @Test + public void testToByteBuffer() throws Exception { + final IpPrefix prefix1 = prefix(PREFIX1, 56); + final IpPrefix prefix2 = prefix(PREFIX2, 96); + + StructNdOptPref64 opt = new StructNdOptPref64(prefix1, 600); + assertToByteBufferMatches(opt, "2602025A0064FF9B0000000000000000"); + assertEquals(new IpPrefix("64:ff9b::/56"), opt.prefix); + assertEquals(600, opt.lifetime); + + opt = new StructNdOptPref64(prefix2, 65519); + assertToByteBufferMatches(opt, "2602FFE820010DB80001000200030064"); + assertEquals(new IpPrefix("2001:db8:1:2:3:64::/96"), opt.prefix); + assertEquals(65512, opt.lifetime); + + assertInvalidParameters(prefix1, 65535); + assertInvalidParameters(prefix2, -1); + assertInvalidParameters(prefix("1.2.3.4", 32), 600); + } +} diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java new file mode 100644 index 0000000000..b7f68c6236 --- /dev/null +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/StructNlMsgHdrTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.net.module.util.netlink; + +import static org.junit.Assert.fail; + +import android.system.OsConstants; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class StructNlMsgHdrTest { + + public static final short TEST_NLMSG_LEN = 16; + public static final short TEST_NLMSG_FLAGS = StructNlMsgHdr.NLM_F_REQUEST + | StructNlMsgHdr.NLM_F_MULTI | StructNlMsgHdr.NLM_F_ACK | StructNlMsgHdr.NLM_F_ECHO; + public static final short TEST_NLMSG_SEQ = 1234; + public static final short TEST_NLMSG_PID = 5678; + + // Checking the header string nlmsg_{len, ..} of the number can make sure that the checking + // number comes from the expected element. + // TODO: Verify more flags once StructNlMsgHdr can distinguish the flags which have the same + // value. For example, NLM_F_MATCH (0x200) and NLM_F_EXCL (0x200) can't be distinguished. + // See StructNlMsgHdrTest#stringForNlMsgFlags. + public static final String TEST_NLMSG_LEN_STR = "nlmsg_len{16}"; + public static final String TEST_NLMSG_FLAGS_STR = + "NLM_F_REQUEST|NLM_F_MULTI|NLM_F_ACK|NLM_F_ECHO"; + public static final String TEST_NLMSG_SEQ_STR = "nlmsg_seq{1234}"; + public static final String TEST_NLMSG_PID_STR = "nlmsg_pid{5678}"; + + private StructNlMsgHdr makeStructNlMsgHdr(short type) { + final StructNlMsgHdr struct = new StructNlMsgHdr(); + struct.nlmsg_len = TEST_NLMSG_LEN; + struct.nlmsg_type = type; + struct.nlmsg_flags = TEST_NLMSG_FLAGS; + struct.nlmsg_seq = TEST_NLMSG_SEQ; + struct.nlmsg_pid = TEST_NLMSG_PID; + return struct; + } + + private static void assertContains(String actualValue, String expectedSubstring) { + if (actualValue.contains(expectedSubstring)) return; + fail("\"" + actualValue + "\" does not contain \"" + expectedSubstring + "\""); + } + + @Test + public void testToString() { + StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR); + String s = struct.toString(); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20()}"); + + struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY); + s = struct.toString(); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20()}"); + } + + @Test + public void testToStringWithNetlinkFamily() { + StructNlMsgHdr struct = makeStructNlMsgHdr(NetlinkConstants.RTM_NEWADDR); + String s = struct.toString(OsConstants.NETLINK_ROUTE); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20(RTM_NEWADDR)}"); + + struct = makeStructNlMsgHdr(NetlinkConstants.SOCK_DIAG_BY_FAMILY); + s = struct.toString(OsConstants.NETLINK_INET_DIAG); + assertContains(s, TEST_NLMSG_LEN_STR); + assertContains(s, TEST_NLMSG_FLAGS_STR); + assertContains(s, TEST_NLMSG_SEQ_STR); + assertContains(s, TEST_NLMSG_PID_STR); + assertContains(s, "nlmsg_type{20(SOCK_DIAG_BY_FAMILY)}"); + } +} diff --git a/staticlibs/testutils/Android.bp b/staticlibs/testutils/Android.bp index 6d9414f8aa..89aa136214 100644 --- a/staticlibs/testutils/Android.bp +++ b/staticlibs/testutils/Android.bp @@ -32,6 +32,7 @@ java_library { "libnanohttpd", "net-tests-utils-host-device-common", "net-utils-device-common", + "net-utils-device-common-netlink", "modules-utils-build_system", ], } diff --git a/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt new file mode 100644 index 0000000000..ccfe710471 --- /dev/null +++ b/staticlibs/testutils/devicetests/com/android/testutils/NetlinkTestUtils.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:JvmName("NetlinkTestUtils") + +package com.android.testutils + +import com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH +import com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWNEIGH +import libcore.util.HexEncoding +import libcore.util.HexEncoding.encodeToString +import java.net.Inet6Address +import java.net.InetAddress + +/** + * Make a RTM_NEWNEIGH netlink message. + */ +fun makeNewNeighMessage( + neighAddr: InetAddress, + nudState: Short +) = makeNeighborMessage( + neighAddr = neighAddr, + type = RTM_NEWNEIGH, + nudState = nudState +) + +/** + * Make a RTM_DELNEIGH netlink message. + */ +fun makeDelNeighMessage( + neighAddr: InetAddress, + nudState: Short +) = makeNeighborMessage( + neighAddr = neighAddr, + type = RTM_DELNEIGH, + nudState = nudState +) + +private fun makeNeighborMessage( + neighAddr: InetAddress, + type: Short, + nudState: Short +) = HexEncoding.decode( + /* ktlint-disable indent */ + // -- struct nlmsghdr -- + // length = 88 or 76: + (if (neighAddr is Inet6Address) "58000000" else "4c000000") + + type.toLEHex() + // type + "0000" + // flags + "00000000" + // seqno + "00000000" + // pid (0 == kernel) + // struct ndmsg + // family (AF_INET6 or AF_INET) + (if (neighAddr is Inet6Address) "0a" else "02") + + "00" + // pad1 + "0000" + // pad2 + "15000000" + // interface index (21 == wlan0, on test device) + nudState.toLEHex() + // NUD state + "00" + // flags + "01" + // type + // -- struct nlattr: NDA_DST -- + // length = 20 or 8: + (if (neighAddr is Inet6Address) "1400" else "0800") + + "0100" + // type (1 == NDA_DST, for neighbor messages) + // IP address: + encodeToString(neighAddr.address) + + // -- struct nlattr: NDA_LLADDR -- + "0a00" + // length = 10 + "0200" + // type (2 == NDA_LLADDR, for neighbor messages) + "00005e000164" + // MAC Address (== 00:00:5e:00:01:64) + "0000" + // padding, for 4 byte alignment + // -- struct nlattr: NDA_PROBES -- + "0800" + // length = 8 + "0400" + // type (4 == NDA_PROBES, for neighbor messages) + "01000000" + // number of probes + // -- struct nlattr: NDA_CACHEINFO -- + "1400" + // length = 20 + "0300" + // type (3 == NDA_CACHEINFO, for neighbor messages) + "05190000" + // ndm_used, as "clock ticks ago" + "05190000" + // ndm_confirmed, as "clock ticks ago" + "190d0000" + // ndm_updated, as "clock ticks ago" + "00000000", // ndm_refcnt + false /* allowSingleChar */) + /* ktlint-enable indent */ + +/** + * Convert a [Short] to a little-endian hex string. + */ +private fun Short.toLEHex() = String.format("%04x", java.lang.Short.reverseBytes(this))