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
This commit is contained in:
Xiao Ma 2021-07-01 13:57:02 +00:00
commit 57b3442c65
34 changed files with 5388 additions and 5 deletions

View file

@ -34,7 +34,10 @@ package {
java_library {
name: "net-utils-device-common",
srcs: [
"device/com/android/net/module/util/**/*.java",
"device/com/android/net/module/util/DeviceConfigUtils.java",
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/HexDump.java",
"device/com/android/net/module/util/PacketReader.java",
// This library is used by system modules, for which the system health impact of Kotlin
// has not yet been evaluated. Annotations may need jarjar'ing.
// "src_devicecommon/**/*.kt",
@ -90,6 +93,37 @@ filegroup {
],
}
java_library {
name: "net-utils-device-common-netlink",
// TODO: Ipv6Utils and Struct stuff could be separated out of th netlink library into
// an individual Struct library, and remove the net-utils-framework-common lib dependency.
// But there is no need doing this at the moment.
srcs: [
"device/com/android/net/module/util/HexDump.java",
"device/com/android/net/module/util/Ipv6Utils.java",
"device/com/android/net/module/util/Struct.java",
"device/com/android/net/module/util/netlink/*.java",
"device/com/android/net/module/util/structs/*.java",
],
sdk_version: "system_current",
min_sdk_version: "29",
visibility: [
"//frameworks/libs/net/common/testutils:__subpackages__",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
],
static_libs: [
"net-utils-framework-common",
],
libs: [
"androidx.annotation_annotation",
],
apex_available: [
"com.android.tethering",
"//apex_available:platform",
],
}
java_library {
name: "net-utils-framework-common",
srcs: [

View file

@ -16,7 +16,7 @@
package com.android.net.module.util;
import android.annotation.Nullable;
import androidx.annotation.Nullable;
/**
* Hex utility functions.

View file

@ -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;

View file

@ -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) + "}"
+ "}";
}
}

View file

@ -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: &lt;linux_src&gt;/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 &lt;linux_src&gt;/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()) + "} "
+ "}";
}
}

View file

@ -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);
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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()) + "} "
+ "}";
}
}

View file

@ -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;
}
}
}

View file

@ -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);
}
}

View file

@ -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: &lt;linux_src&gt;/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()) + "} "
+ "}";
}
}

View file

@ -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 &lt;linux_src&gt;/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 + "}, "
+ "}";
}
}

View file

@ -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 &lt;linux_src&gt;/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")
+ "}";
}
}

View file

@ -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 &lt;linux_src&gt;/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}"
+ "}";
}
}

View file

@ -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: &lt;linux_src&gt;/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 + "} "
+ "}";
}
}

View file

@ -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);
}
}

View file

@ -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: &lt;linux_src&gt;/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 + "} "
+ "}";
}
}

View file

@ -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 &lt;linux_src&gt;/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) + "} "
+ "}";
}
}

View file

@ -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: &lt;linux_src&gt;/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) + "}, "
+ "}";
}
}

Some files were not shown because too many files have changed in this diff Show more