Merge "Support NAT keepalives" into oc-mr1-dev
am: 4fae111ca2
Change-Id: I82ac60e5ad79ec64a13df6ec56b5b51b223f8dde
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
117
services/net/java/android/net/netlink/ConntrackMessage.java
Normal file
117
services/net/java/android/net/netlink/ConntrackMessage.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
51
services/net/java/android/net/netlink/StructNfGenMsg.java
Normal file
51
services/net/java/android/net/netlink/StructNfGenMsg.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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{ "
|
||||
|
||||
131
tests/net/java/android/net/netlink/ConntrackMessageTest.java
Normal file
131
tests/net/java/android/net/netlink/ConntrackMessageTest.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user