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;
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;
*/
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();
// 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;
}
}
- 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) {
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() {
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() {
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) {
mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
}
- private boolean started() {
- return mConfigInitialized && mControlInitialized;
- }
-
private boolean pushUpstreamParameters(String prevUpstream) {
final String iface = currentUpstreamInterface();
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;
+ }
}
}
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;
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;
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) {
--- /dev/null
+/*
+ * 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);
+ }
+}
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);
/**
- * A NetlinkMessage subclass for netlink error messages.
+ * A NetlinkMessage subclass for rtnetlink neighbor messages.
*
* see also: <linux_src>/include/uapi/linux/neighbour.h
*
--- /dev/null
+/*
+ * 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);
+ }
+}
*/
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.
}
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) {
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() {
}
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{ "
--- /dev/null
+/*
+ * 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);
+ }
+}