diff options
author | Xiao Ma <xiaom@google.com> | 2021-07-01 13:57:02 +0000 |
---|---|---|
committer | Xiao Ma <xiaom@google.com> | 2021-07-29 03:52:09 +0000 |
commit | a5abbe652945803803a0fe1985a7cb9712392302 (patch) | |
tree | 66172ef37e240fb91aa7c93a525b155688f46eea /common/device/com/android/net/module/util | |
parent | 5d39cb6ea74958c60b72e81ea5052e8a82928177 (diff) | |
download | net-a5abbe652945803803a0fe1985a7cb9712392302.tar.gz |
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
Diffstat (limited to 'common/device/com/android/net/module/util')
22 files changed, 3329 insertions, 4 deletions
diff --git a/common/device/com/android/net/module/util/HexDump.java b/common/device/com/android/net/module/util/HexDump.java index 6d36487b..a27c0a3c 100644 --- a/common/device/com/android/net/module/util/HexDump.java +++ b/common/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/common/device/com/android/net/module/util/Struct.java b/common/device/com/android/net/module/util/Struct.java index d95c3037..b43e2c45 100644 --- a/common/device/com/android/net/module/util/Struct.java +++ b/common/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/common/device/com/android/net/module/util/netlink/ConntrackMessage.java b/common/device/com/android/net/module/util/netlink/ConntrackMessage.java new file mode 100644 index 00000000..1763c04b --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/InetDiagMessage.java b/common/device/com/android/net/module/util/netlink/InetDiagMessage.java new file mode 100644 index 00000000..7b200e75 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NdOption.java b/common/device/com/android/net/module/util/netlink/NdOption.java new file mode 100644 index 00000000..50a34966 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NduseroptMessage.java b/common/device/com/android/net/module/util/netlink/NduseroptMessage.java new file mode 100644 index 00000000..4e3b9f2d --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NetlinkConstants.java b/common/device/com/android/net/module/util/netlink/NetlinkConstants.java new file mode 100644 index 00000000..cf9d2c54 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java b/common/device/com/android/net/module/util/netlink/NetlinkErrorMessage.java new file mode 100644 index 00000000..d9fb09e6 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NetlinkMessage.java b/common/device/com/android/net/module/util/netlink/NetlinkMessage.java new file mode 100644 index 00000000..f425384f --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/NetlinkSocket.java b/common/device/com/android/net/module/util/netlink/NetlinkSocket.java new file mode 100644 index 00000000..ec326dd7 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java b/common/device/com/android/net/module/util/netlink/RtNetlinkNeighborMessage.java new file mode 100644 index 00000000..a75ef8dd --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java b/common/device/com/android/net/module/util/netlink/StructInetDiagMsg.java new file mode 100644 index 00000000..205656e3 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java b/common/device/com/android/net/module/util/netlink/StructInetDiagReqV2.java new file mode 100644 index 00000000..6eef8653 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java b/common/device/com/android/net/module/util/netlink/StructInetDiagSockId.java new file mode 100644 index 00000000..95d60e53 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNdMsg.java b/common/device/com/android/net/module/util/netlink/StructNdMsg.java new file mode 100644 index 00000000..53ce8991 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java b/common/device/com/android/net/module/util/netlink/StructNdOptPref64.java new file mode 100644 index 00000000..bde69831 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java b/common/device/com/android/net/module/util/netlink/StructNdaCacheInfo.java new file mode 100644 index 00000000..79d5ff4a --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNfGenMsg.java b/common/device/com/android/net/module/util/netlink/StructNfGenMsg.java new file mode 100644 index 00000000..2de5490b --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNlAttr.java b/common/device/com/android/net/module/util/netlink/StructNlAttr.java new file mode 100644 index 00000000..80f00579 --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNlMsgErr.java b/common/device/com/android/net/module/util/netlink/StructNlMsgErr.java new file mode 100644 index 00000000..b6620f3b --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/common/device/com/android/net/module/util/netlink/StructNlMsgHdr.java new file mode 100644 index 00000000..ddf1562f --- /dev/null +++ b/common/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/common/device/com/android/net/module/util/structs/PrefixInformationOption.java b/common/device/com/android/net/module/util/structs/PrefixInformationOption.java index 1bdee295..49d7654d 100644 --- a/common/device/com/android/net/module/util/structs/PrefixInformationOption.java +++ b/common/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; |