Merge "Move static utils to the static library"

This commit is contained in:
Chalard Jean 2021-10-28 01:55:12 +00:00 • committed by Gerrit Code Review
commit 68bb44645b
12 changed files with 1689 additions and 2 deletions

View file

@ -38,6 +38,7 @@ java_library {
"device/com/android/net/module/util/FdEventsReader.java",
"device/com/android/net/module/util/HexDump.java",
"device/com/android/net/module/util/PacketReader.java",
"device/com/android/net/module/util/SharedLog.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",
@ -95,7 +96,7 @@ filegroup {
java_library {
name: "net-utils-device-common-netlink",
// TODO: Ipv6Utils and Struct stuff could be separated out of th netlink library into
// TODO: Ipv6Utils and Struct stuff could be separated out of the 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: [
@ -124,6 +125,36 @@ java_library {
],
}
java_library {
// TODO : this target should probably be folded into net-utils-device-common
name: "net-utils-device-common-ip",
srcs: [
"device/com/android/net/module/util/ip/*.java",
],
sdk_version: "module_current",
min_sdk_version: "29",
visibility: [
"//frameworks/libs/net/common/tests:__subpackages__",
"//frameworks/libs/net/common/testutils:__subpackages__",
"//packages/modules/Connectivity:__subpackages__",
"//packages/modules/NetworkStack:__subpackages__",
],
libs: [
"framework-annotations-lib",
"framework-connectivity",
],
static_libs: [
"net-utils-device-common",
"net-utils-device-common-netlink",
"net-utils-framework-common",
"netd-client",
],
apex_available: [
"com.android.tethering",
"//apex_available:platform",
],
}
java_library {
name: "net-utils-framework-common",
srcs: [
@ -205,7 +236,11 @@ filegroup {
filegroup {
name: "net-utils-framework-wifi-common-srcs",
srcs: [
"framework/com/android/net/module/util/**/*.java",
"framework/com/android/net/module/util/DnsSdTxtRecord.java",
"framework/com/android/net/module/util/Inet4AddressUtils.java",
"framework/com/android/net/module/util/InetAddressUtils.java",
"framework/com/android/net/module/util/MacAddressUtils.java",
"framework/com/android/net/module/util/NetUtils.java",
],
path: "framework",
visibility: [

View file

@ -0,0 +1,232 @@
/*
* 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;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.StringJoiner;
/**
* Class to centralize logging functionality for tethering.
*
* All access to class methods other than dump() must be on the same thread.
*
* @hide
*/
public class SharedLog {
private static final int DEFAULT_MAX_RECORDS = 500;
private static final String COMPONENT_DELIMITER = ".";
private enum Category {
NONE,
ERROR,
MARK,
WARN,
}
private final LocalLog mLocalLog;
// The tag to use for output to the system log. This is not output to the
// LocalLog because that would be redundant.
private final String mTag;
// The component (or subcomponent) of a system that is sharing this log.
// This can grow in depth if components call forSubComponent() to obtain
// their SharedLog instance. The tag is not included in the component for
// brevity.
private final String mComponent;
public SharedLog(String tag) {
this(DEFAULT_MAX_RECORDS, tag);
}
public SharedLog(int maxRecords, String tag) {
this(new LocalLog(maxRecords), tag, tag);
}
private SharedLog(LocalLog localLog, String tag, String component) {
mLocalLog = localLog;
mTag = tag;
mComponent = component;
}
public String getTag() {
return mTag;
}
/**
* Create a SharedLog based on this log with an additional component prefix on each logged line.
*/
public SharedLog forSubComponent(String component) {
if (!isRootLogInstance()) {
component = mComponent + COMPONENT_DELIMITER + component;
}
return new SharedLog(mLocalLog, mTag, component);
}
/**
* Dump the contents of this log.
*
* <p>This method may be called on any thread.
*/
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
mLocalLog.dump(writer);
}
//////
// Methods that both log an entry and emit it to the system log.
//////
/**
* Log an error due to an exception. This does not include the exception stacktrace.
*
* <p>The log entry will be also added to the system log.
* @see #e(String, Throwable)
*/
public void e(Exception e) {
Log.e(mTag, record(Category.ERROR, e.toString()));
}
/**
* Log an error message.
*
* <p>The log entry will be also added to the system log.
*/
public void e(String msg) {
Log.e(mTag, record(Category.ERROR, msg));
}
/**
* Log an error due to an exception, with the exception stacktrace if provided.
*
* <p>The error and exception message appear in the shared log, but the stacktrace is only
* logged in general log output (logcat). The log entry will be also added to the system log.
*/
public void e(@NonNull String msg, @Nullable Throwable exception) {
if (exception == null) {
e(msg);
return;
}
Log.e(mTag, record(Category.ERROR, msg + ": " + exception.getMessage()), exception);
}
/**
* Log an informational message.
*
* <p>The log entry will be also added to the system log.
*/
public void i(String msg) {
Log.i(mTag, record(Category.NONE, msg));
}
/**
* Log a warning message.
*
* <p>The log entry will be also added to the system log.
*/
public void w(String msg) {
Log.w(mTag, record(Category.WARN, msg));
}
//////
// Methods that only log an entry (and do NOT emit to the system log).
//////
/**
* Log a general message to be only included in the in-memory log.
*
* <p>The log entry will *not* be added to the system log.
*/
public void log(String msg) {
record(Category.NONE, msg);
}
/**
* Log a general, formatted message to be only included in the in-memory log.
*
* <p>The log entry will *not* be added to the system log.
* @see String#format(String, Object...)
*/
public void logf(String fmt, Object... args) {
log(String.format(fmt, args));
}
/**
* Log a message with MARK level.
*
* <p>The log entry will *not* be added to the system log.
*/
public void mark(String msg) {
record(Category.MARK, msg);
}
private String record(Category category, String msg) {
final String entry = logLine(category, msg);
mLocalLog.append(entry);
return entry;
}
private String logLine(Category category, String msg) {
final StringJoiner sj = new StringJoiner(" ");
if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
if (category != Category.NONE) sj.add(category.toString());
return sj.add(msg).toString();
}
// Check whether this SharedLog instance is nominally the top level in
// a potential hierarchy of shared logs (the root of a tree),
// or is a subcomponent within the hierarchy.
private boolean isRootLogInstance() {
return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
}
private static final class LocalLog {
private final Deque<String> mLog;
private final int mMaxLines;
LocalLog(int maxLines) {
mMaxLines = Math.max(0, maxLines);
mLog = new ArrayDeque<>(mMaxLines);
}
synchronized void append(String logLine) {
if (mMaxLines <= 0) return;
while (mLog.size() >= mMaxLines) {
mLog.remove();
}
mLog.add(LocalDateTime.now() + " - " + logLine);
}
/**
* Dumps the content of local log to print writer with each log entry
*
* @param pw printer writer to write into
*/
synchronized void dump(PrintWriter pw) {
for (final String s : mLog) {
pw.println(s);
}
}
}
}

View file

@ -0,0 +1,203 @@
/*
* 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.ip;
import static com.android.net.module.util.netlink.ConntrackMessage.DYING_MASK;
import static com.android.net.module.util.netlink.ConntrackMessage.ESTABLISHED_MASK;
import android.annotation.NonNull;
import android.os.Handler;
import android.system.OsConstants;
import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.ConntrackMessage;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkMessage;
import java.util.Objects;
/**
* ConntrackMonitor.
*
* Monitors the netfilter conntrack notifications and presents to callers
* ConntrackEvents describing each event.
*
* @hide
*/
public class ConntrackMonitor extends NetlinkMonitor {
private static final String TAG = ConntrackMonitor.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean VDBG = false;
// Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
public static final int NF_NETLINK_CONNTRACK_NEW = 1;
public static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
public static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
// The socket receive buffer size in bytes. If too many conntrack messages are sent too
// quickly, the conntrack messages can overflow the socket receive buffer. This can happen
// if too many connections are disconnected by losing network and so on. Use a large-enough
// buffer to avoid the error ENOBUFS while listening to the conntrack messages.
private static final int SOCKET_RECV_BUFSIZE = 6 * 1024 * 1024;
/**
* A class for describing parsed netfilter conntrack events.
*/
public static class ConntrackEvent {
/**
* Conntrack event type.
*/
public final short msgType;
/**
* Original direction conntrack tuple.
*/
public final ConntrackMessage.Tuple tupleOrig;
/**
* Reply direction conntrack tuple.
*/
public final ConntrackMessage.Tuple tupleReply;
/**
* Connection status. A bitmask of ip_conntrack_status enum flags.
*/
public final int status;
/**
* Conntrack timeout.
*/
public final int timeoutSec;
public ConntrackEvent(ConntrackMessage msg) {
this.msgType = msg.getHeader().nlmsg_type;
this.tupleOrig = msg.tupleOrig;
this.tupleReply = msg.tupleReply;
this.status = msg.status;
this.timeoutSec = msg.timeoutSec;
}
@VisibleForTesting
public ConntrackEvent(short msgType, ConntrackMessage.Tuple tupleOrig,
ConntrackMessage.Tuple tupleReply, int status, int timeoutSec) {
this.msgType = msgType;
this.tupleOrig = tupleOrig;
this.tupleReply = tupleReply;
this.status = status;
this.timeoutSec = timeoutSec;
}
@Override
@VisibleForTesting
public boolean equals(Object o) {
if (!(o instanceof ConntrackEvent)) return false;
ConntrackEvent that = (ConntrackEvent) o;
return this.msgType == that.msgType
&& Objects.equals(this.tupleOrig, that.tupleOrig)
&& Objects.equals(this.tupleReply, that.tupleReply)
&& this.status == that.status
&& this.timeoutSec == that.timeoutSec;
}
@Override
public int hashCode() {
return Objects.hash(msgType, tupleOrig, tupleReply, status, timeoutSec);
}
@Override
public String toString() {
return "ConntrackEvent{"
+ "msg_type{"
+ NetlinkConstants.stringForNlMsgType(msgType, OsConstants.NETLINK_NETFILTER)
+ "}, "
+ "tuple_orig{" + tupleOrig + "}, "
+ "tuple_reply{" + tupleReply + "}, "
+ "status{"
+ status + "(" + ConntrackMessage.stringForIpConntrackStatus(status) + ")"
+ "}, "
+ "timeout_sec{" + Integer.toUnsignedLong(timeoutSec) + "}"
+ "}";
}
/**
* Check the established NAT session conntrack message.
*
* @param msg the conntrack message to check.
* @return true if an established NAT message, false if not.
*/
public static boolean isEstablishedNatSession(@NonNull ConntrackMessage msg) {
if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_NEW) return false;
if (msg.tupleOrig == null) return false;
if (msg.tupleReply == null) return false;
if (msg.timeoutSec == 0) return false;
if ((msg.status & ESTABLISHED_MASK) != ESTABLISHED_MASK) return false;
return true;
}
/**
* Check the dying NAT session conntrack message.
* Note that IPCTNL_MSG_CT_DELETE event has no CTA_TIMEOUT attribute.
*
* @param msg the conntrack message to check.
* @return true if a dying NAT message, false if not.
*/
public static boolean isDyingNatSession(@NonNull ConntrackMessage msg) {
if (msg.getMessageType() != NetlinkConstants.IPCTNL_MSG_CT_DELETE) return false;
if (msg.tupleOrig == null) return false;
if (msg.tupleReply == null) return false;
if (msg.timeoutSec != 0) return false;
if ((msg.status & DYING_MASK) != DYING_MASK) return false;
return true;
}
}
/**
* A callback to caller for conntrack event.
*/
public interface ConntrackEventConsumer {
/**
* Every conntrack event received on the netlink socket is passed in
* here.
*/
void accept(@NonNull ConntrackEvent event);
}
private final ConntrackEventConsumer mConsumer;
public ConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
@NonNull ConntrackEventConsumer cb) {
super(h, log, TAG, OsConstants.NETLINK_NETFILTER, NF_NETLINK_CONNTRACK_NEW
| NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY, SOCKET_RECV_BUFSIZE);
mConsumer = cb;
}
@Override
public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
if (!(nlMsg instanceof ConntrackMessage)) {
mLog.e("non-conntrack msg: " + nlMsg);
return;
}
final ConntrackMessage conntrackMsg = (ConntrackMessage) nlMsg;
if (!(ConntrackEvent.isEstablishedNatSession(conntrackMsg)
|| ConntrackEvent.isDyingNatSession(conntrackMsg))) {
return;
}
mConsumer.accept(new ConntrackEvent(conntrackMsg));
}
}

View file

@ -0,0 +1,211 @@
/*
* 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.ip;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
import android.net.INetd;
import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.system.OsConstants;
import com.android.net.module.util.SharedLog;
import java.net.Inet4Address;
import java.net.InetAddress;
/**
* Encapsulates the multiple IP configuration operations performed on an interface.
*
* TODO: refactor/eliminate the redundant ways to set and clear addresses.
*
* @hide
*/
public class InterfaceController {
private static final boolean DBG = false;
private final String mIfName;
private final INetd mNetd;
private final SharedLog mLog;
public InterfaceController(String ifname, INetd netd, SharedLog log) {
mIfName = ifname;
mNetd = netd;
mLog = log;
}
/**
* Set the IPv4 address and also optionally bring the interface up or down.
*/
public boolean setInterfaceConfiguration(final LinkAddress ipv4Addr,
final Boolean setIfaceUp) {
if (!(ipv4Addr.getAddress() instanceof Inet4Address)) {
throw new IllegalArgumentException("Invalid or mismatched Inet4Address");
}
// Note: currently netd only support INetd#IF_STATE_UP and #IF_STATE_DOWN.
// Other flags would be ignored.
final InterfaceConfigurationParcel ifConfig = new InterfaceConfigurationParcel();
ifConfig.ifName = mIfName;
ifConfig.ipv4Addr = ipv4Addr.getAddress().getHostAddress();
ifConfig.prefixLength = ipv4Addr.getPrefixLength();
// Netd ignores hwaddr in interfaceSetCfg.
ifConfig.hwAddr = "";
if (setIfaceUp == null) {
// Empty array means no change.
ifConfig.flags = new String[0];
} else {
// Netd ignores any flag that's not IF_STATE_UP or IF_STATE_DOWN in interfaceSetCfg.
ifConfig.flags = setIfaceUp.booleanValue()
? new String[] {IF_STATE_UP} : new String[] {IF_STATE_DOWN};
}
try {
mNetd.interfaceSetCfg(ifConfig);
} catch (RemoteException | ServiceSpecificException e) {
logError("Setting IPv4 address to %s/%d failed: %s",
ifConfig.ipv4Addr, ifConfig.prefixLength, e);
return false;
}
return true;
}
/**
* Set the IPv4 address of the interface.
*/
public boolean setIPv4Address(final LinkAddress address) {
return setInterfaceConfiguration(address, null);
}
/**
* Clear the IPv4Address of the interface.
*/
public boolean clearIPv4Address() {
return setIPv4Address(new LinkAddress("0.0.0.0/0"));
}
private boolean setEnableIPv6(boolean enabled) {
try {
mNetd.interfaceSetEnableIPv6(mIfName, enabled);
} catch (RemoteException | ServiceSpecificException e) {
logError("%s IPv6 failed: %s", (enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
/**
* Enable IPv6 on the interface.
*/
public boolean enableIPv6() {
return setEnableIPv6(true);
}
/**
* Disable IPv6 on the interface.
*/
public boolean disableIPv6() {
return setEnableIPv6(false);
}
/**
* Enable or disable IPv6 privacy extensions on the interface.
* @param enabled Whether the extensions should be enabled.
*/
public boolean setIPv6PrivacyExtensions(boolean enabled) {
try {
mNetd.interfaceSetIPv6PrivacyExtensions(mIfName, enabled);
} catch (RemoteException | ServiceSpecificException e) {
logError("error %s IPv6 privacy extensions: %s",
(enabled ? "enabling" : "disabling"), e);
return false;
}
return true;
}
/**
* Set IPv6 address generation mode on the interface.
*
* <p>IPv6 should be disabled before changing the mode.
*/
public boolean setIPv6AddrGenModeIfSupported(int mode) {
try {
mNetd.setIPv6AddrGenMode(mIfName, mode);
} catch (RemoteException e) {
logError("Unable to set IPv6 addrgen mode: %s", e);
return false;
} catch (ServiceSpecificException e) {
if (e.errorCode != OsConstants.EOPNOTSUPP) {
logError("Unable to set IPv6 addrgen mode: %s", e);
return false;
}
}
return true;
}
/**
* Add an address to the interface.
*/
public boolean addAddress(LinkAddress addr) {
return addAddress(addr.getAddress(), addr.getPrefixLength());
}
/**
* Add an address to the interface.
*/
public boolean addAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceAddAddress(mIfName, ip.getHostAddress(), prefixLen);
} catch (ServiceSpecificException | RemoteException e) {
logError("failed to add %s/%d: %s", ip, prefixLen, e);
return false;
}
return true;
}
/**
* Remove an address from the interface.
*/
public boolean removeAddress(InetAddress ip, int prefixLen) {
try {
mNetd.interfaceDelAddress(mIfName, ip.getHostAddress(), prefixLen);
} catch (ServiceSpecificException | RemoteException e) {
logError("failed to remove %s/%d: %s", ip, prefixLen, e);
return false;
}
return true;
}
/**
* Remove all addresses from the interface.
*/
public boolean clearAllAddresses() {
try {
mNetd.interfaceClearAddrs(mIfName);
} catch (Exception e) {
logError("Failed to clear addresses: %s", e);
return false;
}
return true;
}
private void logError(String fmt, Object... args) {
mLog.e(String.format(fmt, args));
}
}

View file

@ -0,0 +1,189 @@
/*
* 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.ip;
import static android.system.OsConstants.NETLINK_ROUTE;
import static com.android.net.module.util.netlink.NetlinkConstants.RTM_DELNEIGH;
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import static com.android.net.module.util.netlink.NetlinkConstants.stringForNlMsgType;
import android.net.MacAddress;
import android.os.Handler;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.util.Log;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.NetlinkMessage;
import com.android.net.module.util.netlink.NetlinkSocket;
import com.android.net.module.util.netlink.RtNetlinkNeighborMessage;
import com.android.net.module.util.netlink.StructNdMsg;
import java.net.InetAddress;
import java.util.StringJoiner;
/**
* IpNeighborMonitor.
*
* Monitors the kernel rtnetlink neighbor notifications and presents to callers
* NeighborEvents describing each event. Callers can provide a consumer instance
* to both filter (e.g. by interface index and IP address) and handle the
* generated NeighborEvents.
*
* @hide
*/
public class IpNeighborMonitor extends NetlinkMonitor {
private static final String TAG = IpNeighborMonitor.class.getSimpleName();
private static final boolean DBG = false;
private static final boolean VDBG = false;
/**
* Make the kernel perform neighbor reachability detection (IPv4 ARP or IPv6 ND)
* for the given IP address on the specified interface index.
*
* @return 0 if the request was successfully passed to the kernel; otherwise return
* a non-zero error code.
*/
public static int startKernelNeighborProbe(int ifIndex, InetAddress ip) {
final String msgSnippet = "probing ip=" + ip.getHostAddress() + "%" + ifIndex;
if (DBG) Log.d(TAG, msgSnippet);
final byte[] msg = RtNetlinkNeighborMessage.newNewNeighborMessage(
1, ip, StructNdMsg.NUD_PROBE, ifIndex, null);
try {
NetlinkSocket.sendOneShotKernelMessage(NETLINK_ROUTE, msg);
} catch (ErrnoException e) {
Log.e(TAG, "Error " + msgSnippet + ": " + e);
return -e.errno;
}
return 0;
}
/**
* An event about a neighbor.
*/
public static class NeighborEvent {
final long elapsedMs;
final short msgType;
final int ifindex;
final InetAddress ip;
final short nudState;
final MacAddress macAddr;
public NeighborEvent(long elapsedMs, short msgType, int ifindex, InetAddress ip,
short nudState, MacAddress macAddr) {
this.elapsedMs = elapsedMs;
this.msgType = msgType;
this.ifindex = ifindex;
this.ip = ip;
this.nudState = nudState;
this.macAddr = macAddr;
}
boolean isConnected() {
return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateConnected(nudState);
}
boolean isValid() {
return (msgType != RTM_DELNEIGH) && StructNdMsg.isNudStateValid(nudState);
}
@Override
public String toString() {
final StringJoiner j = new StringJoiner(",", "NeighborEvent{", "}");
return j.add("@" + elapsedMs)
.add(stringForNlMsgType(msgType, NETLINK_ROUTE))
.add("if=" + ifindex)
.add(ip.getHostAddress())
.add(StructNdMsg.stringForNudState(nudState))
.add("[" + macAddr + "]")
.toString();
}
}
/**
* A client that consumes NeighborEvent instances.
* Implement this to listen to neighbor events.
*/
public interface NeighborEventConsumer {
// Every neighbor event received on the netlink socket is passed in
// here. Subclasses should filter for events of interest.
/**
* Consume a neighbor event
* @param event the event
*/
void accept(NeighborEvent event);
}
private final NeighborEventConsumer mConsumer;
public IpNeighborMonitor(Handler h, SharedLog log, NeighborEventConsumer cb) {
super(h, log, TAG, NETLINK_ROUTE, OsConstants.RTMGRP_NEIGH);
mConsumer = (cb != null) ? cb : (event) -> { /* discard */ };
}
@Override
public void processNetlinkMessage(NetlinkMessage nlMsg, final long whenMs) {
if (!(nlMsg instanceof RtNetlinkNeighborMessage)) {
mLog.e("non-rtnetlink neighbor msg: " + nlMsg);
return;
}
final RtNetlinkNeighborMessage neighMsg = (RtNetlinkNeighborMessage) nlMsg;
final short msgType = neighMsg.getHeader().nlmsg_type;
final StructNdMsg ndMsg = neighMsg.getNdHeader();
if (ndMsg == null) {
mLog.e("RtNetlinkNeighborMessage without ND message header!");
return;
}
final int ifindex = ndMsg.ndm_ifindex;
final InetAddress destination = neighMsg.getDestination();
final short nudState =
(msgType == RTM_DELNEIGH)
? StructNdMsg.NUD_NONE
: ndMsg.ndm_state;
final NeighborEvent event = new NeighborEvent(
whenMs, msgType, ifindex, destination, nudState,
getMacAddress(neighMsg.getLinkLayerAddress()));
if (VDBG) {
Log.d(TAG, neighMsg.toString());
}
if (DBG) {
Log.d(TAG, event.toString());
}
mConsumer.accept(event);
}
private static MacAddress getMacAddress(byte[] linkLayerAddress) {
if (linkLayerAddress != null) {
try {
return MacAddress.fromBytes(linkLayerAddress);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Failed to parse link-layer address: " + hexify(linkLayerAddress));
}
}
return null;
}
}

View file

@ -0,0 +1,166 @@
/*
* 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.ip;
import static android.net.util.SocketUtils.makeNetlinkSocketAddress;
import static android.system.OsConstants.AF_NETLINK;
import static android.system.OsConstants.SOCK_DGRAM;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOL_SOCKET;
import static android.system.OsConstants.SO_RCVBUF;
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import android.annotation.NonNull;
import android.net.util.SocketUtils;
import android.os.Handler;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import com.android.net.module.util.PacketReader;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.netlink.NetlinkErrorMessage;
import com.android.net.module.util.netlink.NetlinkMessage;
import com.android.net.module.util.netlink.NetlinkSocket;
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* A simple base class to listen for netlink broadcasts.
*
* Opens a netlink socket of the given family and binds to the specified groups. Polls the socket
* from the event loop of the passed-in {@link Handler}, and calls the subclass-defined
* {@link #processNetlinkMessage} method on the handler thread for each netlink message that
* arrives. Currently ignores all netlink errors.
* @hide
*/
public class NetlinkMonitor extends PacketReader {
protected final SharedLog mLog;
protected final String mTag;
private final int mFamily;
private final int mBindGroups;
private final int mSockRcvbufSize;
private static final boolean DBG = false;
// Default socket receive buffer size. This means the specific buffer size is not set.
private static final int DEFAULT_SOCKET_RECV_BUFSIZE = -1;
/**
* Constructs a new {@code NetlinkMonitor} instance.
*
* @param h The Handler on which to poll for messages and on which to call
* {@link #processNetlinkMessage}.
* @param log A SharedLog to log to.
* @param tag The log tag to use for log messages.
* @param family the Netlink socket family to, e.g., {@code NETLINK_ROUTE}.
* @param bindGroups the netlink groups to bind to.
* @param sockRcvbufSize the specific socket receive buffer size in bytes. -1 means that don't
* set the specific socket receive buffer size in #createFd and use the default value in
* /proc/sys/net/core/rmem_default file. See SO_RCVBUF in man-pages/socket.
*/
public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
int family, int bindGroups, int sockRcvbufSize) {
super(h, NetlinkSocket.DEFAULT_RECV_BUFSIZE);
mLog = log.forSubComponent(tag);
mTag = tag;
mFamily = family;
mBindGroups = bindGroups;
mSockRcvbufSize = sockRcvbufSize;
}
public NetlinkMonitor(@NonNull Handler h, @NonNull SharedLog log, @NonNull String tag,
int family, int bindGroups) {
this(h, log, tag, family, bindGroups, DEFAULT_SOCKET_RECV_BUFSIZE);
}
@Override
protected FileDescriptor createFd() {
FileDescriptor fd = null;
try {
fd = Os.socket(AF_NETLINK, SOCK_DGRAM | SOCK_NONBLOCK, mFamily);
if (mSockRcvbufSize != DEFAULT_SOCKET_RECV_BUFSIZE) {
Os.setsockoptInt(fd, SOL_SOCKET, SO_RCVBUF, mSockRcvbufSize);
}
Os.bind(fd, makeNetlinkSocketAddress(0, mBindGroups));
NetlinkSocket.connectToKernel(fd);
if (DBG) {
final SocketAddress nlAddr = Os.getsockname(fd);
Log.d(mTag, "bound to sockaddr_nl{" + nlAddr.toString() + "}");
}
} catch (ErrnoException | SocketException e) {
logError("Failed to create rtnetlink socket", e);
closeSocketQuietly(fd);
return null;
}
return fd;
}
@Override
protected void handlePacket(byte[] recvbuf, int length) {
final long whenMs = SystemClock.elapsedRealtime();
final ByteBuffer byteBuffer = ByteBuffer.wrap(recvbuf, 0, length);
byteBuffer.order(ByteOrder.nativeOrder());
while (byteBuffer.remaining() > 0) {
try {
final int position = byteBuffer.position();
final NetlinkMessage nlMsg = NetlinkMessage.parse(byteBuffer, mFamily);
if (nlMsg == null || nlMsg.getHeader() == null) {
byteBuffer.position(position);
mLog.e("unparsable netlink msg: " + hexify(byteBuffer));
break;
}
if (nlMsg instanceof NetlinkErrorMessage) {
mLog.e("netlink error: " + nlMsg);
continue;
}
processNetlinkMessage(nlMsg, whenMs);
} catch (Exception e) {
mLog.e("Error handling netlink message", e);
}
}
}
// TODO: move NetworkStackUtils to frameworks/libs/net for NetworkStackUtils#closeSocketQuietly.
private void closeSocketQuietly(FileDescriptor fd) {
try {
SocketUtils.closeSocket(fd);
} catch (IOException ignored) {
}
}
/**
* Processes one netlink message. Must be overridden by subclasses.
* @param nlMsg the message to process.
* @param whenMs the timestamp, as measured by {@link SystemClock#elapsedRealtime}, when the
* message was received.
*/
protected void processNetlinkMessage(NetlinkMessage nlMsg, long whenMs) { }
}

View file

@ -0,0 +1,105 @@
/*
* 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;
import android.net.MacAddress;
import android.text.TextUtils;
import java.net.NetworkInterface;
import java.net.SocketException;
/**
* Encapsulate the interface parameters common to IpClient/IpServer components.
*
* Basically all java.net.NetworkInterface methods throw Exceptions. IpClient
* and IpServer (sub)components need most or all of this information at some
* point during their lifecycles, so pass only this simplified object around
* which can be created once when IpClient/IpServer are told to start.
*
* @hide
*/
public class InterfaceParams {
public final String name;
public final int index;
public final boolean hasMacAddress;
public final MacAddress macAddr;
public final int defaultMtu;
// TODO: move the below to NetworkStackConstants when this class is moved to the NetworkStack.
private static final int ETHER_MTU = 1500;
private static final int IPV6_MIN_MTU = 1280;
/**
* Return InterfaceParams corresponding with an interface name
* @param name the interface name
*/
public static InterfaceParams getByName(String name) {
final NetworkInterface netif = getNetworkInterfaceByName(name);
if (netif == null) return null;
// Not all interfaces have MAC addresses, e.g. rmnet_data0.
final MacAddress macAddr = getMacAddress(netif);
try {
return new InterfaceParams(name, netif.getIndex(), macAddr, netif.getMTU());
} catch (IllegalArgumentException | SocketException e) {
return null;
}
}
public InterfaceParams(String name, int index, MacAddress macAddr) {
this(name, index, macAddr, ETHER_MTU);
}
public InterfaceParams(String name, int index, MacAddress macAddr, int defaultMtu) {
if (TextUtils.isEmpty(name)) {
throw new IllegalArgumentException("impossible interface name");
}
if (index <= 0) throw new IllegalArgumentException("invalid interface index");
this.name = name;
this.index = index;
this.hasMacAddress = (macAddr != null);
this.macAddr = hasMacAddress ? macAddr : MacAddress.fromBytes(new byte[] {
0x02, 0x00, 0x00, 0x00, 0x00, 0x00 });
this.defaultMtu = (defaultMtu > IPV6_MIN_MTU) ? defaultMtu : IPV6_MIN_MTU;
}
@Override
public String toString() {
return String.format("%s/%d/%s/%d", name, index, macAddr, defaultMtu);
}
private static NetworkInterface getNetworkInterfaceByName(String name) {
try {
return NetworkInterface.getByName(name);
} catch (NullPointerException | SocketException e) {
return null;
}
}
private static MacAddress getMacAddress(NetworkInterface netif) {
try {
return MacAddress.fromBytes(netif.getHardwareAddress());
} catch (IllegalArgumentException | NullPointerException | SocketException e) {
return null;
}
}
}

View file

@ -0,0 +1,23 @@
/*
* 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;
/** @hide */
// RouteUtils is now empty, because some new methods will be added to it soon and it is less
// expensive to keep it empty than to remove it now and add it again later.
public class RouteUtils {
}

View file

@ -13,10 +13,12 @@ android_library {
defaults: ["framework-connectivity-test-defaults"],
static_libs: [
"net-utils-framework-common",
"net-utils-device-common-ip",
"androidx.test.rules",
"mockito-target-extended-minus-junit4",
"net-utils-device-common",
"net-tests-utils",
"netd-client",
],
libs: [
"android.test.runner",

View file

@ -0,0 +1,95 @@
/*
* 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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class SharedLogTest {
private static final String TIMESTAMP_PATTERN = "\\d{2}:\\d{2}:\\d{2}";
private static final String TIMESTAMP = "HH:MM:SS";
@Test
public void testBasicOperation() {
final SharedLog logTop = new SharedLog("top");
logTop.mark("first post!");
final SharedLog logLevel2a = logTop.forSubComponent("twoA");
final SharedLog logLevel2b = logTop.forSubComponent("twoB");
logLevel2b.e("2b or not 2b");
logLevel2b.e("No exception", null);
logLevel2b.e("Wait, here's one", new Exception("Test"));
logLevel2a.w("second post?");
final SharedLog logLevel3 = logLevel2a.forSubComponent("three");
logTop.log("still logging");
logLevel3.log("3 >> 2");
logLevel2a.mark("ok: last post");
final String[] expected = {
" - MARK first post!",
" - [twoB] ERROR 2b or not 2b",
" - [twoB] ERROR No exception",
// No stacktrace in shared log, only in logcat
" - [twoB] ERROR Wait, here's one: Test",
" - [twoA] WARN second post?",
" - still logging",
" - [twoA.three] 3 >> 2",
" - [twoA] MARK ok: last post",
};
// Verify the logs are all there and in the correct order.
verifyLogLines(expected, logTop);
// In fact, because they all share the same underlying LocalLog,
// every subcomponent SharedLog's dump() is identical.
verifyLogLines(expected, logLevel2a);
verifyLogLines(expected, logLevel2b);
verifyLogLines(expected, logLevel3);
}
private static void verifyLogLines(String[] expected, SharedLog log) {
final ByteArrayOutputStream ostream = new ByteArrayOutputStream();
final PrintWriter pw = new PrintWriter(ostream, true);
log.dump(null, pw, null);
final String dumpOutput = ostream.toString();
assertTrue(dumpOutput != null);
assertTrue(!"".equals(dumpOutput));
final String[] lines = dumpOutput.split("\n");
assertEquals(expected.length, lines.length);
for (int i = 0; i < expected.length; i++) {
String got = lines[i];
String want = expected[i];
assertTrue(String.format("'%s' did not contain '%s'", got, want), got.endsWith(want));
assertTrue(String.format("'%s' did not contain a %s timestamp", got, TIMESTAMP),
got.replaceFirst(TIMESTAMP_PATTERN, TIMESTAMP).contains(TIMESTAMP));
}
}
}

View file

@ -0,0 +1,336 @@
/*
* 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.ip;
import static android.system.OsConstants.AF_UNIX;
import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.SOCK_DGRAM;
import static com.android.net.module.util.netlink.ConntrackMessage.Tuple;
import static com.android.net.module.util.netlink.ConntrackMessage.TupleIpv4;
import static com.android.net.module.util.netlink.ConntrackMessage.TupleProto;
import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_DELETE;
import static com.android.net.module.util.netlink.NetlinkConstants.IPCTNL_MSG_CT_NEW;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import android.net.InetAddresses;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.system.ErrnoException;
import android.system.Os;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.SharedLog;
import com.android.net.module.util.ip.ConntrackMonitor.ConntrackEvent;
import com.android.net.module.util.netlink.NetlinkConstants;
import com.android.net.module.util.netlink.NetlinkSocket;
import libcore.util.HexEncoding;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.FileDescriptor;
import java.io.InterruptedIOException;
import java.net.Inet4Address;
/**
* Tests for ConntrackMonitor.
*/
@RunWith(AndroidJUnit4.class)
@SmallTest
public class ConntrackMonitorTest {
private static final long TIMEOUT_MS = 10_000L;
@Mock private SharedLog mLog;
@Mock private ConntrackMonitor.ConntrackEventConsumer mConsumer;
private final HandlerThread mHandlerThread = new HandlerThread(
ConntrackMonitorTest.class.getSimpleName());
// Late init since the handler thread has been started.
private Handler mHandler;
private TestConntrackMonitor mConntrackMonitor;
// A version of [ConntrackMonitor] that reads packets from the socket pair, and instead
// allows the test to write test packets to the socket pair via [sendMessage].
private class TestConntrackMonitor extends ConntrackMonitor {
TestConntrackMonitor(@NonNull Handler h, @NonNull SharedLog log,
@NonNull ConntrackEventConsumer cb) {
super(h, log, cb);
mReadFd = new FileDescriptor();
mWriteFd = new FileDescriptor();
try {
Os.socketpair(AF_UNIX, SOCK_DGRAM, 0, mWriteFd, mReadFd);
} catch (ErrnoException e) {
fail("Could not create socket pair: " + e);
}
}
@Override
protected FileDescriptor createFd() {
return mReadFd;
}
private void sendMessage(byte[] msg) {
mHandler.post(() -> {
try {
NetlinkSocket.sendMessage(mWriteFd, msg, 0 /* offset */, msg.length,
TIMEOUT_MS);
} catch (ErrnoException | InterruptedIOException e) {
fail("Unable to send netfilter message: " + e);
}
});
}
private final FileDescriptor mReadFd;
private final FileDescriptor mWriteFd;
}
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
// ConntrackMonitor needs to be started from the handler thread.
final ConditionVariable initDone = new ConditionVariable();
mHandler.post(() -> {
TestConntrackMonitor m = new TestConntrackMonitor(mHandler, mLog, mConsumer);
m.start();
mConntrackMonitor = m;
initDone.open();
});
if (!initDone.block(TIMEOUT_MS)) {
fail("... init monitor timed-out after " + TIMEOUT_MS + "ms");
}
}
@After
public void tearDown() throws Exception {
mHandlerThread.quitSafely();
}
public static final String CT_V4NEW_TCP_HEX =
// CHECKSTYLE:OFF IndentationCheck
// struct nlmsghdr
"8C000000" + // length = 140
"0001" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_NEW (0)
"0006" + // flags = NLM_F_CREATE (1 << 10) | NLM_F_EXCL (1 << 9)
"00000000" + // seqno = 0
"00000000" + // pid = 0
// struct nfgenmsg
"02" + // nfgen_family = AF_INET
"00" + // version = NFNETLINK_V0
"1234" + // res_id = 0x1234 (big endian)
// 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 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
// struct nlattr
"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=443 (big endian)
// struct nlattr
"3400" + // nla_len = 52
"0280" + // nla_type = nested CTA_TUPLE_REPLY
// struct nlattr
"1400" + // nla_len = 20
"0180" + // nla_type = nested CTA_TUPLE_IP
"0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
"0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
// struct nlattr
"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 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=443 (big endian)
"0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
// struct nlattr
"0800" + // nla_len = 8
"0300" + // nla_type = CTA_STATUS
"0000019e" + // nla_value = 0b110011110 (big endian)
// IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
// IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
// IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8)
// struct nlattr
"0800" + // nla_len = 8
"0700" + // nla_type = CTA_TIMEOUT
"00000078"; // nla_value = 120 (big endian)
// CHECKSTYLE:ON IndentationCheck
public static final byte[] CT_V4NEW_TCP_BYTES =
HexEncoding.decode(CT_V4NEW_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
@NonNull
private ConntrackEvent makeTestConntrackEvent(short msgType, int status, int timeoutSec) {
final Inet4Address privateIp =
(Inet4Address) InetAddresses.parseNumericAddress("192.168.80.12");
final Inet4Address remoteIp =
(Inet4Address) InetAddresses.parseNumericAddress("140.112.8.116");
final Inet4Address publicIp =
(Inet4Address) InetAddresses.parseNumericAddress("100.81.179.1");
return new ConntrackEvent(
(short) (NetlinkConstants.NFNL_SUBSYS_CTNETLINK << 8 | msgType),
new Tuple(new TupleIpv4(privateIp, remoteIp),
new TupleProto((byte) IPPROTO_TCP, (short) 62449, (short) 443)),
new Tuple(new TupleIpv4(remoteIp, publicIp),
new TupleProto((byte) IPPROTO_TCP, (short) 443, (short) 62449)),
status,
timeoutSec);
}
@Test
public void testConntrackEventNew() throws Exception {
final ConntrackEvent expectedEvent = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
0x19e /* status */, 120 /* timeoutSec */);
mConntrackMonitor.sendMessage(CT_V4NEW_TCP_BYTES);
verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
}
@Test
public void testConntrackEventEquals() {
final ConntrackEvent event1 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
5678 /* timeoutSec*/);
final ConntrackEvent event2 = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
5678 /* timeoutSec*/);
assertEquals(event1, event2);
}
@Test
public void testConntrackEventNotEquals() {
final ConntrackEvent e = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW, 1234 /* status */,
5678 /* timeoutSec*/);
final ConntrackEvent typeNotEqual = new ConntrackEvent((short) (e.msgType + 1) /* diff */,
e.tupleOrig, e.tupleReply, e.status, e.timeoutSec);
assertNotEquals(e, typeNotEqual);
final ConntrackEvent tupleOrigNotEqual = new ConntrackEvent(e.msgType,
null /* diff */, e.tupleReply, e.status, e.timeoutSec);
assertNotEquals(e, tupleOrigNotEqual);
final ConntrackEvent tupleReplyNotEqual = new ConntrackEvent(e.msgType,
e.tupleOrig, null /* diff */, e.status, e.timeoutSec);
assertNotEquals(e, tupleReplyNotEqual);
final ConntrackEvent statusNotEqual = new ConntrackEvent(e.msgType,
e.tupleOrig, e.tupleReply, e.status + 1 /* diff */, e.timeoutSec);
assertNotEquals(e, statusNotEqual);
final ConntrackEvent timeoutSecNotEqual = new ConntrackEvent(e.msgType,
e.tupleOrig, e.tupleReply, e.status, e.timeoutSec + 1 /* diff */);
assertNotEquals(e, timeoutSecNotEqual);
}
@Test
public void testToString() {
final ConntrackEvent event = makeTestConntrackEvent(IPCTNL_MSG_CT_NEW,
0x198 /* status */, 120 /* timeoutSec */);
final String expected = ""
+ "ConntrackEvent{"
+ "msg_type{IPCTNL_MSG_CT_NEW}, "
+ "tuple_orig{Tuple{IPPROTO_TCP: 192.168.80.12:62449 -> 140.112.8.116:443}}, "
+ "tuple_reply{Tuple{IPPROTO_TCP: 140.112.8.116:443 -> 100.81.179.1:62449}}, "
+ "status{408(IPS_CONFIRMED|IPS_SRC_NAT|IPS_SRC_NAT_DONE|IPS_DST_NAT_DONE)}, "
+ "timeout_sec{120}}";
assertEquals(expected, event.toString());
}
public static final String CT_V4DELETE_TCP_HEX =
// CHECKSTYLE:OFF IndentationCheck
// struct nlmsghdr
"84000000" + // length = 132
"0201" + // type = NFNL_SUBSYS_CTNETLINK (1) << 8 | IPCTNL_MSG_CT_DELETE (2)
"0000" + // flags = 0
"00000000" + // seqno = 0
"00000000" + // pid = 0
// struct nfgenmsg
"02" + // nfgen_family = AF_INET
"00" + // version = NFNETLINK_V0
"1234" + // res_id = 0x1234 (big endian)
// 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 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
// struct nlattr
"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)
// struct nlattr
"3400" + // nla_len = 52
"0280" + // nla_type = nested CTA_TUPLE_REPLY
// struct nlattr
"1400" + // nla_len = 20
"0180" + // nla_type = nested CTA_TUPLE_IP
"0800 0100 8C700874" + // nla_type=CTA_IP_V4_SRC, ip=140.112.8.116
"0800 0200 6451B301" + // nla_type=CTA_IP_V4_DST, ip=100.81.179.1
// struct nlattr
"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 01BB 0000" + // nla_type=CTA_PROTO_SRC_PORT, port=433 (big endian)
"0600 0300 F3F1 0000" + // nla_type=CTA_PROTO_DST_PORT, port=62449 (big endian)
// struct nlattr
"0800" + // nla_len = 8
"0300" + // nla_type = CTA_STATUS
"0000039E"; // nla_value = 0b1110011110 (big endian)
// IPS_SEEN_REPLY (1 << 1) | IPS_ASSURED (1 << 2) |
// IPS_CONFIRMED (1 << 3) | IPS_SRC_NAT (1 << 4) |
// IPS_SRC_NAT_DONE (1 << 7) | IPS_DST_NAT_DONE (1 << 8) |
// IPS_DYING (1 << 9)
// CHECKSTYLE:ON IndentationCheck
public static final byte[] CT_V4DELETE_TCP_BYTES =
HexEncoding.decode(CT_V4DELETE_TCP_HEX.replaceAll(" ", "").toCharArray(), false);
@Test
public void testConntrackEventDelete() throws Exception {
final ConntrackEvent expectedEvent =
makeTestConntrackEvent(IPCTNL_MSG_CT_DELETE, 0x39e /* status */,
0 /* timeoutSec (absent) */);
mConntrackMonitor.sendMessage(CT_V4DELETE_TCP_BYTES);
verify(mConsumer, timeout(TIMEOUT_MS)).accept(eq(expectedEvent));
}
}

View file

@ -0,0 +1,90 @@
/*
* 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.ip;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.net.INetd;
import android.net.InetAddresses;
import android.net.InterfaceConfigurationParcel;
import android.net.LinkAddress;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.net.module.util.SharedLog;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class InterfaceControllerTest {
private static final String TEST_IFACE = "testif";
private static final String TEST_IPV4_ADDR = "192.168.123.28";
private static final int TEST_PREFIXLENGTH = 31;
@Mock private INetd mNetd;
@Mock private SharedLog mLog;
@Captor private ArgumentCaptor<InterfaceConfigurationParcel> mConfigCaptor;
private InterfaceController mController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mController = new InterfaceController(TEST_IFACE, mNetd, mLog);
doNothing().when(mNetd).interfaceSetCfg(mConfigCaptor.capture());
}
@Test
public void testSetIPv4Address() throws Exception {
mController.setIPv4Address(
new LinkAddress(InetAddresses.parseNumericAddress(TEST_IPV4_ADDR),
TEST_PREFIXLENGTH));
verify(mNetd, times(1)).interfaceSetCfg(any());
final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
assertEquals(TEST_IFACE, parcel.ifName);
assertEquals(TEST_IPV4_ADDR, parcel.ipv4Addr);
assertEquals(TEST_PREFIXLENGTH, parcel.prefixLength);
assertEquals("", parcel.hwAddr);
assertArrayEquals(new String[0], parcel.flags);
}
@Test
public void testClearIPv4Address() throws Exception {
mController.clearIPv4Address();
verify(mNetd, times(1)).interfaceSetCfg(any());
final InterfaceConfigurationParcel parcel = mConfigCaptor.getValue();
assertEquals(TEST_IFACE, parcel.ifName);
assertEquals("0.0.0.0", parcel.ipv4Addr);
assertEquals(0, parcel.prefixLength);
assertEquals("", parcel.hwAddr);
assertArrayEquals(new String[0], parcel.flags);
}
}