diff options
9 files changed, 584 insertions, 64 deletions
diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java index 788867f9137e..5eafe5f9f64f 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadController.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadController.java @@ -30,6 +30,10 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.NetworkStats; import android.net.RouteInfo; +import android.net.netlink.ConntrackMessage; +import android.net.netlink.NetlinkConstants; +import android.net.netlink.NetlinkSocket; +import android.net.util.IpUtils; import android.net.util.SharedLog; import android.os.Handler; import android.os.Looper; @@ -37,10 +41,12 @@ import android.os.INetworkManagementService; import android.os.RemoteException; import android.os.SystemClock; import android.provider.Settings; +import android.system.ErrnoException; +import android.system.OsConstants; import android.text.TextUtils; -import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats; import java.net.Inet4Address; import java.net.Inet6Address; @@ -63,6 +69,7 @@ import java.util.concurrent.TimeUnit; */ public class OffloadController { private static final String TAG = OffloadController.class.getSimpleName(); + private static final boolean DBG = false; private static final String ANYIP = "0.0.0.0"; private static final ForwardedStats EMPTY_STATS = new ForwardedStats(); @@ -96,6 +103,9 @@ public class OffloadController { // includes upstream interfaces that have a quota set. private HashMap<String, Long> mInterfaceQuotas = new HashMap<>(); + private int mNatUpdateCallbacksReceived; + private int mNatUpdateNetlinkErrors; + public OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) { mHandler = h; @@ -115,12 +125,12 @@ public class OffloadController { } } - public void start() { - if (started()) return; + public boolean start() { + if (started()) return true; if (isOffloadDisabled()) { mLog.i("tethering offload disabled"); - return; + return false; } if (!mConfigInitialized) { @@ -128,11 +138,14 @@ public class OffloadController { if (!mConfigInitialized) { mLog.i("tethering offload config not supported"); stop(); - return; + return false; } } mControlInitialized = mHwInterface.initOffloadControl( + // OffloadHardwareInterface guarantees that these callback + // methods are called on the handler passed to it, which is the + // same as mHandler, as coordinated by the setup in Tethering. new OffloadHardwareInterface.ControlCallback() { @Override public void onStarted() { @@ -203,15 +216,20 @@ public class OffloadController { String srcAddr, int srcPort, String dstAddr, int dstPort) { if (!started()) return; - mLog.log(String.format("NAT timeout update: %s (%s,%s) -> (%s,%s)", - proto, srcAddr, srcPort, dstAddr, dstPort)); + updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort); } }); - if (!mControlInitialized) { + + final boolean isStarted = started(); + if (!isStarted) { mLog.i("tethering offload control not supported"); stop(); + } else { + mLog.log("tethering offload started"); + mNatUpdateCallbacksReceived = 0; + mNatUpdateNetlinkErrors = 0; } - mLog.log("tethering offload started"); + return isStarted; } public void stop() { @@ -227,6 +245,10 @@ public class OffloadController { if (wasStarted) mLog.log("tethering offload stopped"); } + private boolean started() { + return mConfigInitialized && mControlInitialized; + } + private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub { @Override public NetworkStats getTetherStats(int how) { @@ -402,10 +424,6 @@ public class OffloadController { mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0); } - private boolean started() { - return mConfigInitialized && mControlInitialized; - } - private boolean pushUpstreamParameters(String prevUpstream) { final String iface = currentUpstreamInterface(); @@ -516,10 +534,113 @@ public class OffloadController { pw.println("Offload disabled"); return; } - pw.println("Offload HALs " + (started() ? "started" : "not started")); + final boolean isStarted = started(); + pw.println("Offload HALs " + (isStarted ? "started" : "not started")); LinkProperties lp = mUpstreamLinkProperties; String upstream = (lp != null) ? lp.getInterfaceName() : null; pw.println("Current upstream: " + upstream); pw.println("Exempt prefixes: " + mLastLocalPrefixStrs); + pw.println("NAT timeout update callbacks received during the " + + (isStarted ? "current" : "last") + + " offload session: " + + mNatUpdateCallbacksReceived); + pw.println("NAT timeout update netlink errors during the " + + (isStarted ? "current" : "last") + + " offload session: " + + mNatUpdateNetlinkErrors); + } + + private void updateNatTimeout( + int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) { + final String protoName = protoNameFor(proto); + if (protoName == null) { + mLog.e("Unknown NAT update callback protocol: " + proto); + return; + } + + final Inet4Address src = parseIPv4Address(srcAddr); + if (src == null) { + mLog.e("Failed to parse IPv4 address: " + srcAddr); + return; + } + + if (!IpUtils.isValidUdpOrTcpPort(srcPort)) { + mLog.e("Invalid src port: " + srcPort); + return; + } + + final Inet4Address dst = parseIPv4Address(dstAddr); + if (dst == null) { + mLog.e("Failed to parse IPv4 address: " + dstAddr); + return; + } + + if (!IpUtils.isValidUdpOrTcpPort(dstPort)) { + mLog.e("Invalid dst port: " + dstPort); + return; + } + + mNatUpdateCallbacksReceived++; + if (DBG) { + mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)", + protoName, srcAddr, srcPort, dstAddr, dstPort)); + } + + final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto); + final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest( + proto, src, srcPort, dst, dstPort, timeoutSec); + + try { + NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg); + } catch (ErrnoException e) { + mNatUpdateNetlinkErrors++; + mLog.e("Error updating NAT conntrack entry: " + e + + ", msg: " + NetlinkConstants.hexify(msg)); + mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived); + mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors); + } + } + + private static Inet4Address parseIPv4Address(String addrString) { + try { + final InetAddress ip = InetAddress.parseNumericAddress(addrString); + // TODO: Consider other sanitization steps here, including perhaps: + // not eql to 0.0.0.0 + // not within 169.254.0.0/16 + // not within ::ffff:0.0.0.0/96 + // not within ::/96 + // et cetera. + if (ip instanceof Inet4Address) { + return (Inet4Address) ip; + } + } catch (IllegalArgumentException iae) {} + return null; + } + + private static String protoNameFor(int proto) { + // OsConstants values are not constant expressions; no switch statement. + if (proto == OsConstants.IPPROTO_UDP) { + return "UDP"; + } else if (proto == OsConstants.IPPROTO_TCP) { + return "TCP"; + } + return null; + } + + private static int connectionTimeoutUpdateSecondsFor(int proto) { + // TODO: Replace this with more thoughtful work, perhaps reading from + // and maybe writing to any required + // + // /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_* + // /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream} + // + // entries. TBD. + if (proto == OsConstants.IPPROTO_TCP) { + // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established + return 432000; + } else { + // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream + return 180; + } } } diff --git a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java index 865a98902d0b..553fd8c453a0 100644 --- a/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java +++ b/services/core/java/com/android/server/connectivity/tethering/OffloadHardwareInterface.java @@ -21,10 +21,12 @@ import static com.android.internal.util.BitUtils.uint16; import android.hardware.tetheroffload.control.V1_0.IOffloadControl; import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback; import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate; +import android.hardware.tetheroffload.control.V1_0.NetworkProtocol; import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent; import android.os.Handler; import android.os.RemoteException; import android.net.util.SharedLog; +import android.system.OsConstants; import java.util.ArrayList; @@ -327,13 +329,24 @@ public class OffloadHardwareInterface { public void updateTimeout(NatTimeoutUpdate params) { handler.post(() -> { controlCb.onNatTimeoutUpdate( - params.proto, + networkProtocolToOsConstant(params.proto), params.src.addr, uint16(params.src.port), params.dst.addr, uint16(params.dst.port)); }); } } + private static int networkProtocolToOsConstant(int proto) { + switch (proto) { + case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP; + case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP; + default: + // The caller checks this value and will log an error. Just make + // sure it won't collide with valid OsContants.IPPROTO_* values. + return -Math.abs(proto); + } + } + private static class CbResults { boolean success; String errMsg; diff --git a/services/net/java/android/net/ip/IpReachabilityMonitor.java b/services/net/java/android/net/ip/IpReachabilityMonitor.java index e833f6a03bce..714b35a03396 100644 --- a/services/net/java/android/net/ip/IpReachabilityMonitor.java +++ b/services/net/java/android/net/ip/IpReachabilityMonitor.java @@ -205,44 +205,14 @@ public class IpReachabilityMonitor { final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage( 1, ip, StructNdMsg.NUD_PROBE, ifIndex, null); - int errno = -OsConstants.EPROTO; - try (NetlinkSocket nlSocket = new NetlinkSocket(OsConstants.NETLINK_ROUTE)) { - final long IO_TIMEOUT = 300L; - nlSocket.connectToKernel(); - nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT); - final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT); - // recvMessage() guaranteed to not return null if it did not throw. - final NetlinkMessage response = NetlinkMessage.parse(bytes); - if (response != null && response instanceof NetlinkErrorMessage && - (((NetlinkErrorMessage) response).getNlMsgError() != null)) { - 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, "Error " + msgSnippet + ", errmsg=" + response.toString()); - } - } else { - String errmsg; - if (response == null) { - bytes.position(0); - errmsg = "raw bytes: " + NetlinkConstants.hexify(bytes); - } else { - errmsg = response.toString(); - } - Log.e(TAG, "Error " + msgSnippet + ", errmsg=" + errmsg); - } + try { + NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_ROUTE, msg); } catch (ErrnoException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -e.errno; - } catch (InterruptedIOException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -OsConstants.ETIMEDOUT; - } catch (SocketException e) { - Log.e(TAG, "Error " + msgSnippet, e); - errno = -OsConstants.EIO; + Log.e(TAG, "Error " + msgSnippet + ": " + e); + return -e.errno; } - return errno; + + return 0; } public IpReachabilityMonitor(Context context, String ifName, SharedLog log, Callback callback) { diff --git a/services/net/java/android/net/netlink/ConntrackMessage.java b/services/net/java/android/net/netlink/ConntrackMessage.java new file mode 100644 index 000000000000..605c46b3b4e0 --- /dev/null +++ b/services/net/java/android/net/netlink/ConntrackMessage.java @@ -0,0 +1,117 @@ +/* + * 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 android.net.netlink; + +import static android.net.netlink.NetlinkConstants.alignedLengthOf; +import static android.net.netlink.StructNlAttr.makeNestedType; +import static android.net.netlink.StructNlAttr.NLA_HEADERLEN; +import static android.net.netlink.StructNlMsgHdr.NLM_F_ACK; +import static android.net.netlink.StructNlMsgHdr.NLM_F_DUMP; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REPLACE; +import static android.net.netlink.StructNlMsgHdr.NLM_F_REQUEST; +import static android.net.util.NetworkConstants.IPV4_ADDR_LEN; +import static java.nio.ByteOrder.BIG_ENDIAN; + +import android.system.OsConstants; +import android.util.Log; +import libcore.io.SizeOf; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + + +/** + * 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; + + public static final short NFNL_SUBSYS_CTNETLINK = 1; + public static final short IPCTNL_MSG_CT_NEW = 0; + + // enum ctattr_type + public static final short CTA_TUPLE_ORIG = 1; + public static final short CTA_TUPLE_REPLY = 2; + 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; + + 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 = (NFNL_SUBSYS_CTNETLINK << 8) | 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; + } + + protected StructNfGenMsg mNfGenMsg; + + private ConntrackMessage() { + super(new StructNlMsgHdr()); + mNfGenMsg = new StructNfGenMsg((byte) OsConstants.AF_INET); + } + + public void pack(ByteBuffer byteBuffer) { + mHeader.pack(byteBuffer); + mNfGenMsg.pack(byteBuffer); + } +} diff --git a/services/net/java/android/net/netlink/NetlinkSocket.java b/services/net/java/android/net/netlink/NetlinkSocket.java index 657d48c15250..a9e0cd996fbd 100644 --- a/services/net/java/android/net/netlink/NetlinkSocket.java +++ b/services/net/java/android/net/netlink/NetlinkSocket.java @@ -51,6 +51,47 @@ public class NetlinkSocket implements Closeable { private long mLastRecvTimeoutMs; private long mLastSendTimeoutMs; + public static void sendOneShotKernelMessage(int nlProto, byte[] msg) throws ErrnoException { + final String errPrefix = "Error in NetlinkSocket.sendOneShotKernelMessage"; + + try (NetlinkSocket nlSocket = new NetlinkSocket(nlProto)) { + final long IO_TIMEOUT = 300L; + nlSocket.connectToKernel(); + nlSocket.sendMessage(msg, 0, msg.length, IO_TIMEOUT); + final ByteBuffer bytes = nlSocket.recvMessage(IO_TIMEOUT); + // recvMessage() guaranteed to not return null if it did not throw. + final NetlinkMessage response = NetlinkMessage.parse(bytes); + 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, OsConstants.EPROTO); + } + } catch (InterruptedIOException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, OsConstants.ETIMEDOUT, e); + } catch (SocketException e) { + Log.e(TAG, errPrefix, e); + throw new ErrnoException(errPrefix, OsConstants.EIO, e); + } + } + public NetlinkSocket(int nlProto) throws ErrnoException { mDescriptor = Os.socket( OsConstants.AF_NETLINK, OsConstants.SOCK_DGRAM, nlProto); diff --git a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java index 02df1313c43f..e784fbb5e0dc 100644 --- a/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java +++ b/services/net/java/android/net/netlink/RtNetlinkNeighborMessage.java @@ -36,7 +36,7 @@ import java.nio.ByteOrder; /** - * A NetlinkMessage subclass for netlink error messages. + * A NetlinkMessage subclass for rtnetlink neighbor messages. * * see also: <linux_src>/include/uapi/linux/neighbour.h * diff --git a/services/net/java/android/net/netlink/StructNfGenMsg.java b/services/net/java/android/net/netlink/StructNfGenMsg.java new file mode 100644 index 000000000000..99695e23b248 --- /dev/null +++ b/services/net/java/android/net/netlink/StructNfGenMsg.java @@ -0,0 +1,51 @@ +/* + * 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 android.net.netlink; + +import libcore.io.SizeOf; + +import java.nio.ByteBuffer; + + +/** + * struct nfgenmsg + * + * see <linux_src>/include/uapi/linux/netfilter/nfnetlink.h + * + * @hide + */ +public class StructNfGenMsg { + public static final int STRUCT_SIZE = 2 + SizeOf.SHORT; + + public static final int NFNETLINK_V0 = 0; + + final public byte nfgen_family; + final public byte version; + final public short res_id; // N.B.: this is big endian in the kernel + + public StructNfGenMsg(byte family) { + nfgen_family = family; + version = (byte) NFNETLINK_V0; + res_id = (short) 0; + } + + public void pack(ByteBuffer byteBuffer) { + byteBuffer.put(nfgen_family); + byteBuffer.put(version); + byteBuffer.putShort(res_id); + } +} diff --git a/services/net/java/android/net/netlink/StructNlAttr.java b/services/net/java/android/net/netlink/StructNlAttr.java index 597a6aa1c9eb..811bdbbe821a 100644 --- a/services/net/java/android/net/netlink/StructNlAttr.java +++ b/services/net/java/android/net/netlink/StructNlAttr.java @@ -34,7 +34,12 @@ import java.nio.ByteBuffer; */ public class StructNlAttr { // Already aligned. - public static final int NLA_HEADERLEN = 4; + public static final int NLA_HEADERLEN = 4; + public static final int NLA_F_NESTED = (1 << 15); + + public static short makeNestedType(short type) { + return (short) (type | NLA_F_NESTED); + } // Return a (length, type) object only, without consuming any bytes in // |byteBuffer| and without copying or interpreting any value bytes. @@ -46,10 +51,17 @@ public class StructNlAttr { } final int baseOffset = byteBuffer.position(); - final StructNlAttr struct = new StructNlAttr(); - struct.nla_len = byteBuffer.getShort(); - struct.nla_type = byteBuffer.getShort(); - struct.mByteOrder = byteBuffer.order(); + // Assume the byte order of the buffer is the expected byte order of the value. + final StructNlAttr struct = new StructNlAttr(byteBuffer.order()); + // The byte order of nla_len and nla_type is always native. + 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) { @@ -78,13 +90,65 @@ public class StructNlAttr { return struct; } - public short nla_len; + public short nla_len = (short) NLA_HEADERLEN; public short nla_type; public byte[] nla_value; - public ByteOrder mByteOrder; - public StructNlAttr() { - mByteOrder = ByteOrder.nativeOrder(); + // The byte order used to read/write the value member. Netlink length and + // type members are always read/written in native order. + private ByteOrder mByteOrder = ByteOrder.nativeOrder(); + + public StructNlAttr() {} + + public StructNlAttr(ByteOrder byteOrder) { + mByteOrder = byteOrder; + } + + 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) { + this(order); + nla_type = type; + setValue(new byte[SizeOf.SHORT]); + getValueAsByteBuffer().putShort(value); + } + + public StructNlAttr(short type, int value) { + this(type, value, ByteOrder.nativeOrder()); + } + + public StructNlAttr(short type, int value, ByteOrder order) { + this(order); + nla_type = type; + setValue(new byte[SizeOf.INT]); + getValueAsByteBuffer().putInt(value); + } + + 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); + } } public int getAlignedLength() { @@ -117,13 +181,25 @@ public class StructNlAttr { } public void pack(ByteBuffer byteBuffer) { + final ByteOrder originalOrder = byteBuffer.order(); final int originalPosition = byteBuffer.position(); - byteBuffer.putShort(nla_len); - byteBuffer.putShort(nla_type); - byteBuffer.put(nla_value); + + 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{ " diff --git a/tests/net/java/android/net/netlink/ConntrackMessageTest.java b/tests/net/java/android/net/netlink/ConntrackMessageTest.java new file mode 100644 index 000000000000..3aab9426bc3f --- /dev/null +++ b/tests/net/java/android/net/netlink/ConntrackMessageTest.java @@ -0,0 +1,131 @@ +/* + * 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 android.net.netlink; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assume.assumeTrue; + +import android.system.OsConstants; +import libcore.util.HexEncoding; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import org.junit.runner.RunWith; +import org.junit.Test; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; + + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class ConntrackMessageTest { + private static final boolean USING_LE = (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN); + + // Example 1: TCP (192.168.43.209, 44333) -> (23.211.13.26, 443) + public static final String CT_V4UPDATE_TCP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 C0A82BD1" + // nla_type=CTA_IP_V4_SRC, ip=192.168.43.209 + "0800 0200 17D30D1A" + // nla_type=CTA_IP_V4_DST, ip=23.211.13.26 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 06 000000" + // nla_type=CTA_PROTO_NUM, proto=6 + "0600 0200 AD2D 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=44333 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "00069780"; // nla_value = 432000 (big endian) + public static final byte[] CT_V4UPDATE_TCP_BYTES = + HexEncoding.decode(CT_V4UPDATE_TCP_HEX.replaceAll(" ", "").toCharArray(), false); + + // Example 2: UDP (100.96.167.146, 37069) -> (216.58.197.10, 443) + public static final String CT_V4UPDATE_UDP_HEX = + // struct nlmsghdr + "50000000" + // length = 80 + "0001" + // type = (1 << 8) | 0 + "0501" + // flags + "01000000" + // seqno = 1 + "00000000" + // pid = 0 + // struct nfgenmsg + "02" + // nfgen_family = AF_INET + "00" + // version = NFNETLINK_V0 + "0000" + // res_id + // struct nlattr + "3400" + // nla_len = 52 + "0180" + // nla_type = nested CTA_TUPLE_ORIG + // struct nlattr + "1400" + // nla_len = 20 + "0180" + // nla_type = nested CTA_TUPLE_IP + "0800 0100 6460A792" + // nla_type=CTA_IP_V4_SRC, ip=100.96.167.146 + "0800 0200 D83AC50A" + // nla_type=CTA_IP_V4_DST, ip=216.58.197.10 + // struct nlattr + "1C00" + // nla_len = 28 + "0280" + // nla_type = nested CTA_TUPLE_PROTO + "0500 0100 11 000000" + // nla_type=CTA_PROTO_NUM, proto=17 + "0600 0200 90CD 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=37069 (big endian) + "0600 0300 01BB 0000" + // nla_type=CTA_PROTO_DST_PORT, port=443 (big endian) + // struct nlattr + "0800" + // nla_len = 8 + "0700" + // nla_type = CTA_TIMEOUT + "000000B4"; // nla_value = 180 (big endian) + public static final byte[] CT_V4UPDATE_UDP_BYTES = + HexEncoding.decode(CT_V4UPDATE_UDP_HEX.replaceAll(" ", "").toCharArray(), false); + + @Test + public void testConntrackIPv4TcpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] tcp = ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_TCP, + (Inet4Address) InetAddress.getByName("192.168.43.209"), 44333, + (Inet4Address) InetAddress.getByName("23.211.13.26"), 443, + 432000); + assertArrayEquals(CT_V4UPDATE_TCP_BYTES, tcp); + } + + @Test + public void testConntrackIPv4UdpTimeoutUpdate() throws Exception { + assumeTrue(USING_LE); + + final byte[] udp = ConntrackMessage.newIPv4TimeoutUpdateRequest( + OsConstants.IPPROTO_UDP, + (Inet4Address) InetAddress.getByName("100.96.167.146"), 37069, + (Inet4Address) InetAddress.getByName("216.58.197.10"), 443, + 180); + assertArrayEquals(CT_V4UPDATE_UDP_BYTES, udp); + } +} |