Merge "[AF06] Fill file attributes metrics" into main
diff --git a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
index ea3af5e..651b643 100644
--- a/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
+++ b/service-t/src/com/android/server/connectivity/mdns/MdnsReplySender.java
@@ -25,6 +25,7 @@
import android.os.Looper;
import android.os.Message;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.net.module.util.SharedLog;
import com.android.server.connectivity.mdns.util.MdnsUtils;
@@ -57,15 +58,46 @@
@NonNull
private final SharedLog mSharedLog;
private final boolean mEnableDebugLog;
+ @NonNull
+ private final Dependencies mDependencies;
+
+ /**
+ * Dependencies of MdnsReplySender, for injection in tests.
+ */
+ @VisibleForTesting
+ public static class Dependencies {
+ /**
+ * @see Handler#sendMessageDelayed(Message, long)
+ */
+ public void sendMessageDelayed(@NonNull Handler handler, @NonNull Message message,
+ long delayMillis) {
+ handler.sendMessageDelayed(message, delayMillis);
+ }
+
+ /**
+ * @see Handler#removeMessages(int)
+ */
+ public void removeMessages(@NonNull Handler handler, int what) {
+ handler.removeMessages(what);
+ }
+ }
public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
@NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog,
boolean enableDebugLog) {
+ this(looper, socket, packetCreationBuffer, sharedLog, enableDebugLog, new Dependencies());
+ }
+
+ @VisibleForTesting
+ public MdnsReplySender(@NonNull Looper looper, @NonNull MdnsInterfaceSocket socket,
+ @NonNull byte[] packetCreationBuffer, @NonNull SharedLog sharedLog,
+ boolean enableDebugLog, @NonNull Dependencies dependencies) {
mHandler = new SendHandler(looper);
mSocket = socket;
mPacketCreationBuffer = packetCreationBuffer;
mSharedLog = sharedLog;
mEnableDebugLog = enableDebugLog;
+ mDependencies = dependencies;
}
/**
@@ -74,7 +106,8 @@
public void queueReply(@NonNull MdnsReplyInfo reply) {
ensureRunningOnHandlerThread(mHandler);
// TODO: implement response aggregation (RFC 6762 6.4)
- mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
+ mDependencies.sendMessageDelayed(
+ mHandler, mHandler.obtainMessage(MSG_SEND, reply), reply.sendDelayMs);
if (mEnableDebugLog) {
mSharedLog.v("Scheduling " + reply);
@@ -104,7 +137,7 @@
*/
public void cancelAll() {
ensureRunningOnHandlerThread(mHandler);
- mHandler.removeMessages(MSG_SEND);
+ mDependencies.removeMessages(mHandler, MSG_SEND);
}
private class SendHandler extends Handler {
diff --git a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
index 4f76577..dbd83d0 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/InetDiagMessage.java
@@ -27,6 +27,7 @@
import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DESTROY;
import static com.android.net.module.util.netlink.NetlinkConstants.SOCK_DIAG_BY_FAMILY;
+import static com.android.net.module.util.netlink.NetlinkConstants.SOCKDIAG_MSG_HEADER_SIZE;
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import static com.android.net.module.util.netlink.NetlinkConstants.stringForAddressFamily;
import static com.android.net.module.util.netlink.NetlinkConstants.stringForProtocol;
@@ -59,8 +60,11 @@
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -154,7 +158,8 @@
}
public StructInetDiagMsg inetDiagMsg;
-
+ // The netlink attributes.
+ public List<StructNlAttr> nlAttrs = new ArrayList<>();
@VisibleForTesting
public InetDiagMessage(@NonNull StructNlMsgHdr header) {
super(header);
@@ -172,6 +177,16 @@
if (msg.inetDiagMsg == null) {
return null;
}
+ final int payloadLength = header.nlmsg_len - SOCKDIAG_MSG_HEADER_SIZE;
+ final ByteBuffer payload = byteBuffer.slice();
+ while (payload.position() < payloadLength) {
+ final StructNlAttr attr = StructNlAttr.parse(payload);
+ // Stop parsing for truncated or malformed attribute
+ if (attr == null) return null;
+
+ msg.nlAttrs.add(attr);
+ }
+
return msg;
}
@@ -307,9 +322,8 @@
NetlinkUtils.receiveNetlinkAck(fd);
}
- private static void sendNetlinkDumpRequest(FileDescriptor fd, int proto, int states, int family)
- throws InterruptedIOException, ErrnoException {
- final byte[] dumpMsg = InetDiagMessage.inetDiagReqV2(
+ private static byte [] makeNetlinkDumpRequest(int proto, int states, int family) {
+ return InetDiagMessage.inetDiagReqV2(
proto,
null /* id */,
family,
@@ -318,51 +332,29 @@
0 /* pad */,
0 /* idiagExt */,
states);
- NetlinkUtils.sendMessage(fd, dumpMsg, 0, dumpMsg.length, IO_TIMEOUT_MS);
}
- private static int processNetlinkDumpAndDestroySockets(FileDescriptor dumpFd,
+ private static int processNetlinkDumpAndDestroySockets(byte[] dumpReq,
FileDescriptor destroyFd, int proto, Predicate<InetDiagMessage> filter)
- throws InterruptedIOException, ErrnoException {
- int destroyedSockets = 0;
-
- while (true) {
- final ByteBuffer buf = NetlinkUtils.recvMessage(
- dumpFd, DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
-
- while (buf.remaining() > 0) {
- final int position = buf.position();
- final NetlinkMessage nlMsg = NetlinkMessage.parse(buf, NETLINK_INET_DIAG);
- if (nlMsg == null) {
- // Move to the position where parse started for error log.
- buf.position(position);
- Log.e(TAG, "Failed to parse netlink message: " + hexify(buf));
- break;
- }
-
- if (nlMsg.getHeader().nlmsg_type == NLMSG_DONE) {
- return destroyedSockets;
- }
-
- if (!(nlMsg instanceof InetDiagMessage)) {
- Log.wtf(TAG, "Received unexpected netlink message: " + nlMsg);
- continue;
- }
-
- final InetDiagMessage diagMsg = (InetDiagMessage) nlMsg;
- if (filter.test(diagMsg)) {
- try {
- sendNetlinkDestroyRequest(destroyFd, proto, diagMsg);
- destroyedSockets++;
- } catch (InterruptedIOException | ErrnoException e) {
- if (!(e instanceof ErrnoException
- && ((ErrnoException) e).errno == ENOENT)) {
- Log.e(TAG, "Failed to destroy socket: diagMsg=" + diagMsg + ", " + e);
- }
+ throws SocketException, InterruptedIOException, ErrnoException {
+ AtomicInteger destroyedSockets = new AtomicInteger(0);
+ Consumer<InetDiagMessage> handleNlDumpMsg = (diagMsg) -> {
+ if (filter.test(diagMsg)) {
+ try {
+ sendNetlinkDestroyRequest(destroyFd, proto, diagMsg);
+ destroyedSockets.getAndIncrement();
+ } catch (InterruptedIOException | ErrnoException e) {
+ if (!(e instanceof ErrnoException
+ && ((ErrnoException) e).errno == ENOENT)) {
+ Log.e(TAG, "Failed to destroy socket: diagMsg=" + diagMsg + ", " + e);
}
}
}
- }
+ };
+
+ NetlinkUtils.<InetDiagMessage>getAndProcessNetlinkDumpMessages(dumpReq,
+ NETLINK_INET_DIAG, InetDiagMessage.class, handleNlDumpMsg);
+ return destroyedSockets.get();
}
/**
@@ -420,31 +412,28 @@
private static void destroySockets(int proto, int states, Predicate<InetDiagMessage> filter)
throws ErrnoException, SocketException, InterruptedIOException {
- FileDescriptor dumpFd = null;
FileDescriptor destroyFd = null;
try {
- dumpFd = NetlinkUtils.createNetLinkInetDiagSocket();
destroyFd = NetlinkUtils.createNetLinkInetDiagSocket();
- connectToKernel(dumpFd);
connectToKernel(destroyFd);
for (int family : List.of(AF_INET, AF_INET6)) {
+ byte[] req = makeNetlinkDumpRequest(proto, states, family);
+
try {
- sendNetlinkDumpRequest(dumpFd, proto, states, family);
- } catch (InterruptedIOException | ErrnoException e) {
- Log.e(TAG, "Failed to send netlink dump request: " + e);
- continue;
- }
- final int destroyedSockets = processNetlinkDumpAndDestroySockets(
- dumpFd, destroyFd, proto, filter);
- Log.d(TAG, "Destroyed " + destroyedSockets + " sockets"
+ final int destroyedSockets = processNetlinkDumpAndDestroySockets(
+ req, destroyFd, proto, filter);
+ Log.d(TAG, "Destroyed " + destroyedSockets + " sockets"
+ ", proto=" + stringForProtocol(proto)
+ ", family=" + stringForAddressFamily(family)
+ ", states=" + states);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ Log.e(TAG, "Failed to send netlink dump request or receive messages: " + e);
+ continue;
+ }
}
} finally {
- closeSocketQuietly(dumpFd);
closeSocketQuietly(destroyFd);
}
}
diff --git a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
index f6282fd..7c2be2c 100644
--- a/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
+++ b/staticlibs/device/com/android/net/module/util/netlink/NetlinkUtils.java
@@ -31,10 +31,10 @@
import static android.system.OsConstants.SO_SNDTIMEO;
import static com.android.net.module.util.netlink.NetlinkConstants.hexify;
import static com.android.net.module.util.netlink.NetlinkConstants.NLMSG_DONE;
+import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
-import android.net.ParseException;
import android.net.util.SocketUtils;
import android.system.ErrnoException;
import android.system.Os;
@@ -314,49 +314,20 @@
private NetlinkUtils() {}
- /**
- * Sends a netlink dump request and processes the returned dump messages
- *
- * @param <T> extends NetlinkMessage
- * @param dumpRequestMessage netlink dump request message to be sent
- * @param nlFamily netlink family
- * @param msgClass expected class of the netlink message
- * @param func function defined by caller to handle the dump messages
- * @throws SocketException when fails to create socket
- * @throws InterruptedIOException when fails to read the dumpFd
- * @throws ErrnoException when fails to send dump request
- * @throws ParseException when message can't be parsed
- */
- public static <T extends NetlinkMessage> void getAndProcessNetlinkDumpMessages(
- byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass,
+ private static <T extends NetlinkMessage> void getAndProcessNetlinkDumpMessagesWithFd(
+ FileDescriptor fd, byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass,
Consumer<T> func)
- throws SocketException, InterruptedIOException, ErrnoException, ParseException {
- // Create socket and send dump request
- final FileDescriptor fd;
- try {
- fd = netlinkSocketForProto(nlFamily);
- } catch (ErrnoException e) {
- Log.e(TAG, "Failed to create netlink socket " + e);
- throw e.rethrowAsSocketException();
- }
+ throws SocketException, InterruptedIOException, ErrnoException {
+ // connecToKernel throws ErrnoException and SocketException, should be handled by caller
+ connectToKernel(fd);
- try {
- connectToKernel(fd);
- } catch (ErrnoException | SocketException e) {
- Log.e(TAG, "Failed to connect netlink socket to kernel " + e);
- closeSocketQuietly(fd);
- return;
- }
-
- try {
- sendMessage(fd, dumpRequestMessage, 0, dumpRequestMessage.length, IO_TIMEOUT_MS);
- } catch (InterruptedIOException | ErrnoException e) {
- Log.e(TAG, "Failed to send dump request " + e);
- closeSocketQuietly(fd);
- throw e;
- }
+ // sendMessage throws InterruptedIOException and ErrnoException,
+ // should be handled by caller
+ sendMessage(fd, dumpRequestMessage, 0, dumpRequestMessage.length, IO_TIMEOUT_MS);
while (true) {
+ // recvMessage throws ErrnoException, InterruptedIOException
+ // should be handled by caller
final ByteBuffer buf = recvMessage(
fd, NetlinkUtils.DEFAULT_RECV_BUFSIZE, IO_TIMEOUT_MS);
@@ -367,17 +338,15 @@
// Move to the position where parse started for error log.
buf.position(position);
Log.e(TAG, "Failed to parse netlink message: " + hexify(buf));
- closeSocketQuietly(fd);
- throw new ParseException("Failed to parse netlink message");
+ break;
}
if (nlMsg.getHeader().nlmsg_type == NLMSG_DONE) {
- closeSocketQuietly(fd);
return;
}
if (!msgClass.isInstance(nlMsg)) {
- Log.e(TAG, "Received unexpected netlink message: " + nlMsg);
+ Log.wtf(TAG, "Received unexpected netlink message: " + nlMsg);
continue;
}
@@ -386,6 +355,91 @@
}
}
}
+ /**
+ * Sends a netlink dump request and processes the returned dump messages
+ *
+ * @param <T> extends NetlinkMessage
+ * @param dumpRequestMessage netlink dump request message to be sent
+ * @param nlFamily netlink family
+ * @param msgClass expected class of the netlink message
+ * @param func function defined by caller to handle the dump messages
+ * @throws SocketException when fails to connect socket to kernel
+ * @throws InterruptedIOException when fails to read the dumpFd
+ * @throws ErrnoException when fails to create dump fd, send dump request
+ * or receive messages
+ */
+ public static <T extends NetlinkMessage> void getAndProcessNetlinkDumpMessages(
+ byte[] dumpRequestMessage, int nlFamily, Class<T> msgClass,
+ Consumer<T> func)
+ throws SocketException, InterruptedIOException, ErrnoException {
+ // Create socket
+ final FileDescriptor fd = netlinkSocketForProto(nlFamily);
+ try {
+ getAndProcessNetlinkDumpMessagesWithFd(fd, dumpRequestMessage, nlFamily,
+ msgClass, func);
+ } finally {
+ closeSocketQuietly(fd);
+ }
+ }
+
+ /**
+ * Construct a RTM_GETROUTE message for dumping multicast IPv6 routes from kernel.
+ */
+ private static byte[] newIpv6MulticastRouteDumpRequest() {
+ final StructNlMsgHdr nlmsghdr = new StructNlMsgHdr();
+ nlmsghdr.nlmsg_type = NetlinkConstants.RTM_GETROUTE;
+ nlmsghdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+ final short shortZero = 0;
+
+ // family must be RTNL_FAMILY_IP6MR to dump IPv6 multicast routes.
+ // dstLen, srcLen, tos and scope must be zero in FIB dump request.
+ // protocol, flags must be 0, and type must be RTN_MULTICAST (if not 0) for multicast
+ // dump request.
+ // table or RTA_TABLE attributes can be used to dump a specific routing table.
+ // RTA_OIF attribute can be used to dump only routes containing given oif.
+ // Here no attributes are set so the kernel can return all multicast routes.
+ final StructRtMsg rtMsg =
+ new StructRtMsg(RTNL_FAMILY_IP6MR /* family */, shortZero /* dstLen */,
+ shortZero /* srcLen */, shortZero /* tos */, shortZero /* table */,
+ shortZero /* protocol */, shortZero /* scope */, shortZero /* type */,
+ 0L /* flags */);
+ final RtNetlinkRouteMessage msg =
+ new RtNetlinkRouteMessage(nlmsghdr, rtMsg);
+
+ final int spaceRequired = StructNlMsgHdr.STRUCT_SIZE + StructRtMsg.STRUCT_SIZE;
+ nlmsghdr.nlmsg_len = spaceRequired;
+ final byte[] bytes = new byte[NetlinkConstants.alignedLengthOf(spaceRequired)];
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
+ byteBuffer.order(ByteOrder.nativeOrder());
+ msg.pack(byteBuffer);
+ return bytes;
+ }
+
+ /**
+ * Get the list of IPv6 multicast route messages from kernel.
+ */
+ public static List<RtNetlinkRouteMessage> getIpv6MulticastRoutes() {
+ final byte[] dumpMsg = newIpv6MulticastRouteDumpRequest();
+ List<RtNetlinkRouteMessage> routes = new ArrayList<>();
+ Consumer<RtNetlinkRouteMessage> handleNlDumpMsg = (msg) -> {
+ if (msg.getRtmFamily() == RTNL_FAMILY_IP6MR) {
+ // Sent rtmFamily RTNL_FAMILY_IP6MR in dump request to make sure ipv6
+ // multicast routes are included in netlink reply messages, the kernel
+ // may also reply with other kind of routes, so we filter them out here.
+ routes.add(msg);
+ }
+ };
+ try {
+ NetlinkUtils.<RtNetlinkRouteMessage>getAndProcessNetlinkDumpMessages(
+ dumpMsg, NETLINK_ROUTE, RtNetlinkRouteMessage.class,
+ handleNlDumpMsg);
+ } catch (SocketException | InterruptedIOException | ErrnoException e) {
+ Log.e(TAG, "Failed to dump multicast routes");
+ return routes;
+ }
+
+ return routes;
+ }
private static void closeSocketQuietly(final FileDescriptor fd) {
try {
diff --git a/staticlibs/device/com/android/net/module/util/structs/StructMf6cctl.java b/staticlibs/device/com/android/net/module/util/structs/StructMf6cctl.java
new file mode 100644
index 0000000..24e0a97
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/StructMf6cctl.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2023 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.structs;
+
+import static android.system.OsConstants.AF_INET6;
+
+import com.android.net.module.util.Struct;
+import java.net.Inet6Address;
+import java.util.Set;
+
+/*
+ * Implements the mf6cctl structure which is used to add a multicast forwarding
+ * cache, see /usr/include/linux/mroute6.h
+ */
+public class StructMf6cctl extends Struct {
+ // struct sockaddr_in6 mf6cc_origin, added the fields directly as Struct
+ // doesn't support nested Structs
+ @Field(order = 0, type = Type.U16)
+ public final int originFamily; // AF_INET6
+ @Field(order = 1, type = Type.U16)
+ public final int originPort; // Transport layer port # of origin
+ @Field(order = 2, type = Type.U32)
+ public final long originFlowinfo; // IPv6 flow information
+ @Field(order = 3, type = Type.ByteArray, arraysize = 16)
+ public final byte[] originAddress; //the IPv6 address of origin
+ @Field(order = 4, type = Type.U32)
+ public final long originScopeId; // scope id, not used
+
+ // struct sockaddr_in6 mf6cc_mcastgrp
+ @Field(order = 5, type = Type.U16)
+ public final int groupFamily; // AF_INET6
+ @Field(order = 6, type = Type.U16)
+ public final int groupPort; // Transport layer port # of multicast group
+ @Field(order = 7, type = Type.U32)
+ public final long groupFlowinfo; // IPv6 flow information
+ @Field(order = 8, type = Type.ByteArray, arraysize = 16)
+ public final byte[] groupAddress; //the IPv6 address of multicast group
+ @Field(order = 9, type = Type.U32)
+ public final long groupScopeId; // scope id, not used
+
+ @Field(order = 10, type = Type.U16, padding = 2)
+ public final int mf6ccParent; // incoming interface
+ @Field(order = 11, type = Type.ByteArray, arraysize = 32)
+ public final byte[] mf6ccIfset; // outgoing interfaces
+
+ public StructMf6cctl(final Inet6Address origin, final Inet6Address group,
+ final int mf6ccParent, final Set<Integer> oifset) {
+ this(AF_INET6, 0, (long) 0, origin.getAddress(), (long) 0, AF_INET6,
+ 0, (long) 0, group.getAddress(), (long) 0, mf6ccParent,
+ getMf6ccIfsetBytes(oifset));
+ }
+
+ private StructMf6cctl(int originFamily, int originPort, long originFlowinfo,
+ byte[] originAddress, long originScopeId, int groupFamily, int groupPort,
+ long groupFlowinfo, byte[] groupAddress, long groupScopeId, int mf6ccParent,
+ byte[] mf6ccIfset) {
+ this.originFamily = originFamily;
+ this.originPort = originPort;
+ this.originFlowinfo = originFlowinfo;
+ this.originAddress = originAddress;
+ this.originScopeId = originScopeId;
+ this.groupFamily = groupFamily;
+ this.groupPort = groupPort;
+ this.groupFlowinfo = groupFlowinfo;
+ this.groupAddress = groupAddress;
+ this.groupScopeId = groupScopeId;
+ this.mf6ccParent = mf6ccParent;
+ this.mf6ccIfset = mf6ccIfset;
+ }
+
+ private static byte[] getMf6ccIfsetBytes(final Set<Integer> oifs)
+ throws IllegalArgumentException {
+ byte[] mf6ccIfset = new byte[32];
+ for (int oif : oifs) {
+ int idx = oif / 8;
+ if (idx >= 32) {
+ // invalid oif index, too big to fit in mf6ccIfset
+ throw new IllegalArgumentException("Invalid oif index" + oif);
+ }
+ int offset = oif % 8;
+ mf6ccIfset[idx] |= (byte) (1 << offset);
+ }
+ return mf6ccIfset;
+ }
+}
diff --git a/staticlibs/device/com/android/net/module/util/structs/StructMif6ctl.java b/staticlibs/device/com/android/net/module/util/structs/StructMif6ctl.java
new file mode 100644
index 0000000..626a170
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/StructMif6ctl.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2023 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.structs;
+
+import com.android.net.module.util.Struct;
+
+/*
+ * Implements the mif6ctl structure which is used to add a multicast routing
+ * interface, see /usr/include/linux/mroute6.h
+ */
+public class StructMif6ctl extends Struct {
+ @Field(order = 0, type = Type.U16)
+ public final int mif6cMifi; // Index of MIF
+ @Field(order = 1, type = Type.U8)
+ public final short mif6cFlags; // MIFF_ flags
+ @Field(order = 2, type = Type.U8)
+ public final short vifcThreshold; // ttl limit
+ @Field(order = 3, type = Type.U16)
+ public final int mif6cPifi; //the index of the physical IF
+ @Field(order = 4, type = Type.U32, padding = 2)
+ public final long vifcRateLimit; // Rate limiter values (NI)
+
+ public StructMif6ctl(final int mif6cMifi, final short mif6cFlags, final short vifcThreshold,
+ final int mif6cPifi, final long vifcRateLimit) {
+ this.mif6cMifi = mif6cMifi;
+ this.mif6cFlags = mif6cFlags;
+ this.vifcThreshold = vifcThreshold;
+ this.mif6cPifi = mif6cPifi;
+ this.vifcRateLimit = vifcRateLimit;
+ }
+}
+
diff --git a/staticlibs/device/com/android/net/module/util/structs/StructMrt6Msg.java b/staticlibs/device/com/android/net/module/util/structs/StructMrt6Msg.java
new file mode 100644
index 0000000..569e361
--- /dev/null
+++ b/staticlibs/device/com/android/net/module/util/structs/StructMrt6Msg.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 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.structs;
+
+import com.android.net.module.util.Struct;
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class StructMrt6Msg extends Struct {
+ public static final byte MRT6MSG_NOCACHE = 1;
+
+ @Field(order = 0, type = Type.S8)
+ public final byte mbz;
+ @Field(order = 1, type = Type.S8)
+ public final byte msgType; // message type
+ @Field(order = 2, type = Type.U16, padding = 4)
+ public final int mif; // mif received on
+ @Field(order = 3, type = Type.Ipv6Address)
+ public final Inet6Address src;
+ @Field(order = 4, type = Type.Ipv6Address)
+ public final Inet6Address dst;
+
+ public StructMrt6Msg(final byte mbz, final byte msgType, final int mif,
+ final Inet6Address source, final Inet6Address destination) {
+ this.mbz = mbz; // kernel should set it to 0
+ this.msgType = msgType;
+ this.mif = mif;
+ this.src = source;
+ this.dst = destination;
+ }
+
+ public static StructMrt6Msg parse(ByteBuffer byteBuffer) {
+ byteBuffer.order(ByteOrder.nativeOrder());
+ return Struct.parse(StructMrt6Msg.class, byteBuffer);
+ }
+}
+
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
index 65e99f8..b44e428 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/InetDiagSocketTest.java
@@ -32,6 +32,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -345,28 +346,28 @@
// Hexadecimal representation of InetDiagMessage
private static final String INET_DIAG_MSG_HEX1 =
// struct nlmsghdr
- "58000000" + // length = 88
- "1400" + // type = SOCK_DIAG_BY_FAMILY
- "0200" + // flags = NLM_F_MULTI
- "00000000" + // seqno
- "f5220000" + // pid
+ "58000000" // length = 88
+ + "1400" // type = SOCK_DIAG_BY_FAMILY
+ + "0200" // flags = NLM_F_MULTI
+ + "00000000" // seqno
+ + "f5220000" // pid
// struct inet_diag_msg
- "0a" + // family = AF_INET6
- "01" + // idiag_state = 1
- "02" + // idiag_timer = 2
- "ff" + // idiag_retrans = 255
+ + "0a" // family = AF_INET6
+ + "01" // idiag_state = 1
+ + "02" // idiag_timer = 2
+ + "ff" // idiag_retrans = 255
// inet_diag_sockid
- "a817" + // idiag_sport = 43031
- "960f" + // idiag_dport = 38415
- "20010db8000000000000000000000001" + // idiag_src = 2001:db8::1
- "20010db8000000000000000000000002" + // idiag_dst = 2001:db8::2
- "07000000" + // idiag_if = 7
- "5800000000000000" + // idiag_cookie = 88
- "04000000" + // idiag_expires = 4
- "05000000" + // idiag_rqueue = 5
- "06000000" + // idiag_wqueue = 6
- "a3270000" + // idiag_uid = 10147
- "a57e19f0"; // idiag_inode = 4028202661
+ + "a817" // idiag_sport = 43031
+ + "960f" // idiag_dport = 38415
+ + "20010db8000000000000000000000001" // idiag_src = 2001:db8::1
+ + "20010db8000000000000000000000002" // idiag_dst = 2001:db8::2
+ + "07000000" // idiag_if = 7
+ + "5800000000000000" // idiag_cookie = 88
+ + "04000000" // idiag_expires = 4
+ + "05000000" // idiag_rqueue = 5
+ + "06000000" // idiag_wqueue = 6
+ + "a3270000" // idiag_uid = 10147
+ + "a57e19f0"; // idiag_inode = 4028202661
private void assertInetDiagMsg1(final NetlinkMessage msg) {
assertNotNull(msg);
@@ -394,33 +395,45 @@
assertEquals(6, inetDiagMsg.inetDiagMsg.idiag_wqueue);
assertEquals(10147, inetDiagMsg.inetDiagMsg.idiag_uid);
assertEquals(4028202661L, inetDiagMsg.inetDiagMsg.idiag_inode);
+
+ // Verify the length of attribute list is 0 as expected since message doesn't
+ // take any attributes
+ assertEquals(0, inetDiagMsg.nlAttrs.size());
}
// Hexadecimal representation of InetDiagMessage
private static final String INET_DIAG_MSG_HEX2 =
// struct nlmsghdr
- "58000000" + // length = 88
- "1400" + // type = SOCK_DIAG_BY_FAMILY
- "0200" + // flags = NLM_F_MULTI
- "00000000" + // seqno
- "f5220000" + // pid
+ "6C000000" // length = 108
+ + "1400" // type = SOCK_DIAG_BY_FAMILY
+ + "0200" // flags = NLM_F_MULTI
+ + "00000000" // seqno
+ + "f5220000" // pid
// struct inet_diag_msg
- "0a" + // family = AF_INET6
- "02" + // idiag_state = 2
- "10" + // idiag_timer = 16
- "20" + // idiag_retrans = 32
+ + "0a" // family = AF_INET6
+ + "02" // idiag_state = 2
+ + "10" // idiag_timer = 16
+ + "20" // idiag_retrans = 32
// inet_diag_sockid
- "a845" + // idiag_sport = 43077
- "01bb" + // idiag_dport = 443
- "20010db8000000000000000000000003" + // idiag_src = 2001:db8::3
- "20010db8000000000000000000000004" + // idiag_dst = 2001:db8::4
- "08000000" + // idiag_if = 8
- "6300000000000000" + // idiag_cookie = 99
- "30000000" + // idiag_expires = 48
- "40000000" + // idiag_rqueue = 64
- "50000000" + // idiag_wqueue = 80
- "39300000" + // idiag_uid = 12345
- "851a0000"; // idiag_inode = 6789
+ + "a845" // idiag_sport = 43077
+ + "01bb" // idiag_dport = 443
+ + "20010db8000000000000000000000003" // idiag_src = 2001:db8::3
+ + "20010db8000000000000000000000004" // idiag_dst = 2001:db8::4
+ + "08000000" // idiag_if = 8
+ + "6300000000000000" // idiag_cookie = 99
+ + "30000000" // idiag_expires = 48
+ + "40000000" // idiag_rqueue = 64
+ + "50000000" // idiag_wqueue = 80
+ + "39300000" // idiag_uid = 12345
+ + "851a0000" // idiag_inode = 6789
+ + "0500" // len = 5
+ + "0800" // type = 8
+ + "00000000" // data
+ + "0800" // len = 8
+ + "0F00" // type = 15(INET_DIAG_MARK)
+ + "850A0C00" // data, socket mark=789125
+ + "0400" // len = 4
+ + "0200"; // type = 2
private void assertInetDiagMsg2(final NetlinkMessage msg) {
assertNotNull(msg);
@@ -448,6 +461,104 @@
assertEquals(80, inetDiagMsg.inetDiagMsg.idiag_wqueue);
assertEquals(12345, inetDiagMsg.inetDiagMsg.idiag_uid);
assertEquals(6789, inetDiagMsg.inetDiagMsg.idiag_inode);
+
+ // Verify the number of nlAttr and their content.
+ assertEquals(3, inetDiagMsg.nlAttrs.size());
+
+ assertEquals(5, inetDiagMsg.nlAttrs.get(0).nla_len);
+ assertEquals(8, inetDiagMsg.nlAttrs.get(0).nla_type);
+ assertArrayEquals(
+ HexEncoding.decode("00".toCharArray(), false),
+ inetDiagMsg.nlAttrs.get(0).nla_value);
+ assertEquals(8, inetDiagMsg.nlAttrs.get(1).nla_len);
+ assertEquals(15, inetDiagMsg.nlAttrs.get(1).nla_type);
+ assertArrayEquals(
+ HexEncoding.decode("850A0C00".toCharArray(), false),
+ inetDiagMsg.nlAttrs.get(1).nla_value);
+ assertEquals(4, inetDiagMsg.nlAttrs.get(2).nla_len);
+ assertEquals(2, inetDiagMsg.nlAttrs.get(2).nla_type);
+ assertNull(inetDiagMsg.nlAttrs.get(2).nla_value);
+ }
+
+ // Hexadecimal representation of InetDiagMessage
+ private static final String INET_DIAG_MSG_HEX_MALFORMED =
+ // struct nlmsghdr
+ "6E000000" // length = 110
+ + "1400" // type = SOCK_DIAG_BY_FAMILY
+ + "0200" // flags = NLM_F_MULTI
+ + "00000000" // seqno
+ + "f5220000" // pid
+ // struct inet_diag_msg
+ + "0a" // family = AF_INET6
+ + "02" // idiag_state = 2
+ + "10" // idiag_timer = 16
+ + "20" // idiag_retrans = 32
+ // inet_diag_sockid
+ + "a845" // idiag_sport = 43077
+ + "01bb" // idiag_dport = 443
+ + "20010db8000000000000000000000005" // idiag_src = 2001:db8::5
+ + "20010db8000000000000000000000006" // idiag_dst = 2001:db8::6
+ + "08000000" // idiag_if = 8
+ + "6300000000000000" // idiag_cookie = 99
+ + "30000000" // idiag_expires = 48
+ + "40000000" // idiag_rqueue = 64
+ + "50000000" // idiag_wqueue = 80
+ + "39300000" // idiag_uid = 12345
+ + "851a0000" // idiag_inode = 6789
+ + "0500" // len = 5
+ + "0800" // type = 8
+ + "00000000" // data
+ + "0800" // len = 8
+ + "0F00" // type = 15(INET_DIAG_MARK)
+ + "850A0C00" // data, socket mark=789125
+ + "0400" // len = 4
+ + "0200" // type = 2
+ + "0100" // len = 1, malformed value
+ + "0100"; // type = 1
+
+ @Test
+ public void testParseInetDiagResponseMalformedNlAttr() throws Exception {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(
+ HexEncoding.decode((INET_DIAG_MSG_HEX_MALFORMED).toCharArray(), false));
+ byteBuffer.order(ByteOrder.nativeOrder());
+ assertNull(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
+ }
+
+ // Hexadecimal representation of InetDiagMessage
+ private static final String INET_DIAG_MSG_HEX_TRUNCATED =
+ // struct nlmsghdr
+ "5E000000" // length = 96
+ + "1400" // type = SOCK_DIAG_BY_FAMILY
+ + "0200" // flags = NLM_F_MULTI
+ + "00000000" // seqno
+ + "f5220000" // pid
+ // struct inet_diag_msg
+ + "0a" // family = AF_INET6
+ + "02" // idiag_state = 2
+ + "10" // idiag_timer = 16
+ + "20" // idiag_retrans = 32
+ // inet_diag_sockid
+ + "a845" // idiag_sport = 43077
+ + "01bb" // idiag_dport = 443
+ + "20010db8000000000000000000000005" // idiag_src = 2001:db8::5
+ + "20010db8000000000000000000000006" // idiag_dst = 2001:db8::6
+ + "08000000" // idiag_if = 8
+ + "6300000000000000" // idiag_cookie = 99
+ + "30000000" // idiag_expires = 48
+ + "40000000" // idiag_rqueue = 64
+ + "50000000" // idiag_wqueue = 80
+ + "39300000" // idiag_uid = 12345
+ + "851a0000" // idiag_inode = 6789
+ + "0800" // len = 8
+ + "0100" // type = 1
+ + "000000"; // data, less than the expected length
+
+ @Test
+ public void testParseInetDiagResponseTruncatedNlAttr() throws Exception {
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(
+ HexEncoding.decode((INET_DIAG_MSG_HEX_TRUNCATED).toCharArray(), false));
+ byteBuffer.order(ByteOrder.nativeOrder());
+ assertNull(NetlinkMessage.parse(byteBuffer, NETLINK_INET_DIAG));
}
private static final byte[] INET_DIAG_MSG_BYTES =
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
index 17d4e81..0958f11 100644
--- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/NetlinkUtilsTest.java
@@ -21,7 +21,7 @@
import static android.system.OsConstants.AF_UNSPEC;
import static android.system.OsConstants.EACCES;
import static android.system.OsConstants.NETLINK_ROUTE;
-
+import static com.android.net.module.util.netlink.NetlinkConstants.RTNL_FAMILY_IP6MR;
import static com.android.net.module.util.netlink.NetlinkUtils.DEFAULT_RECV_BUFSIZE;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP;
import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST;
@@ -191,4 +191,17 @@
return bytes;
}
+
+ @Test
+ public void testGetIpv6MulticastRoutes_doesNotThrow() {
+ var multicastRoutes = NetlinkUtils.getIpv6MulticastRoutes();
+
+ for (var route : multicastRoutes) {
+ assertNotNull(route);
+ assertEquals("Route is not IP6MR: " + route,
+ RTNL_FAMILY_IP6MR, route.getRtmFamily());
+ assertNotNull("Route doesn't contain source: " + route, route.getSource());
+ assertNotNull("Route doesn't contain destination: " + route, route.getDestination());
+ }
+ }
}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMf6cctlTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMf6cctlTest.java
new file mode 100644
index 0000000..a83fc36
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMf6cctlTest.java
@@ -0,0 +1,102 @@
+package com.android.net.module.util.structs;
+
+import static android.system.OsConstants.AF_INET6;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import android.util.ArraySet;
+import androidx.test.runner.AndroidJUnit4;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StructMf6cctlTest {
+ private static final byte[] MSG_BYTES = new byte[] {
+ 10, 0, /* AF_INET6 */
+ 0, 0, /* originPort */
+ 0, 0, 0, 0, /* originFlowinfo */
+ 32, 1, 13, -72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* originAddress */
+ 0, 0, 0, 0, /* originScopeId */
+ 10, 0, /* AF_INET6 */
+ 0, 0, /* groupPort */
+ 0, 0, 0, 0, /* groupFlowinfo*/
+ -1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 52, /*groupAddress*/
+ 0, 0, 0, 0, /* groupScopeId*/
+ 1, 0, /* mf6ccParent */
+ 0, 0, /* padding */
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* mf6ccIfset */
+ };
+
+ private static final int OIF = 10;
+ private static final byte[] OIFSET_BYTES = new byte[] {
+ 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ };
+
+ private static final Inet6Address SOURCE =
+ (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
+ private static final Inet6Address DESTINATION =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+
+ @Test
+ public void testConstructor() {
+ final Set<Integer> oifset = new ArraySet<>();
+ oifset.add(OIF);
+
+ StructMf6cctl mf6cctl = new StructMf6cctl(SOURCE, DESTINATION,
+ 1 /* mf6ccParent */, oifset);
+
+ assertTrue(Arrays.equals(SOURCE.getAddress(), mf6cctl.originAddress));
+ assertTrue(Arrays.equals(DESTINATION.getAddress(), mf6cctl.groupAddress));
+ assertEquals(1, mf6cctl.mf6ccParent);
+ assertArrayEquals(OIFSET_BYTES, mf6cctl.mf6ccIfset);
+ }
+
+ @Test
+ public void testConstructor_tooBigOifIndex_throwsIllegalArgumentException()
+ throws UnknownHostException {
+ final Set<Integer> oifset = new ArraySet<>();
+ oifset.add(1000);
+
+ assertThrows(IllegalArgumentException.class,
+ () -> new StructMf6cctl(SOURCE, DESTINATION, 1, oifset));
+ }
+
+ @Test
+ public void testParseMf6cctl() {
+ final ByteBuffer buf = ByteBuffer.wrap(MSG_BYTES);
+ buf.order(ByteOrder.nativeOrder());
+ StructMf6cctl mf6cctl = StructMf6cctl.parse(StructMf6cctl.class, buf);
+
+ assertEquals(AF_INET6, mf6cctl.originFamily);
+ assertEquals(AF_INET6, mf6cctl.groupFamily);
+ assertArrayEquals(SOURCE.getAddress(), mf6cctl.originAddress);
+ assertArrayEquals(DESTINATION.getAddress(), mf6cctl.groupAddress);
+ assertEquals(1, mf6cctl.mf6ccParent);
+ assertArrayEquals("mf6ccIfset = " + Arrays.toString(mf6cctl.mf6ccIfset),
+ OIFSET_BYTES, mf6cctl.mf6ccIfset);
+ }
+
+ @Test
+ public void testWriteToBytes() {
+ final Set<Integer> oifset = new ArraySet<>();
+ oifset.add(OIF);
+
+ StructMf6cctl mf6cctl = new StructMf6cctl(SOURCE, DESTINATION,
+ 1 /* mf6ccParent */, oifset);
+ byte[] bytes = mf6cctl.writeToBytes();
+
+ assertArrayEquals("bytes = " + Arrays.toString(bytes), MSG_BYTES, bytes);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMif6ctlTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMif6ctlTest.java
new file mode 100644
index 0000000..75196e4
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMif6ctlTest.java
@@ -0,0 +1,70 @@
+package com.android.net.module.util.structs;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.util.ArraySet;
+import androidx.test.runner.AndroidJUnit4;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StructMif6ctlTest {
+ private static final byte[] MSG_BYTES = new byte[] {
+ 1, 0, /* mif6cMifi */
+ 0, /* mif6cFlags */
+ 1, /* vifcThreshold*/
+ 20, 0, /* mif6cPifi */
+ 0, 0, 0, 0, /* vifcRateLimit */
+ 0, 0 /* padding */
+ };
+
+ @Test
+ public void testConstructor() {
+ StructMif6ctl mif6ctl = new StructMif6ctl(10 /* mif6cMifi */,
+ (short) 11 /* mif6cFlags */,
+ (short) 12 /* vifcThreshold */,
+ 13 /* mif6cPifi */,
+ 14L /* vifcRateLimit */);
+
+ assertEquals(10, mif6ctl.mif6cMifi);
+ assertEquals(11, mif6ctl.mif6cFlags);
+ assertEquals(12, mif6ctl.vifcThreshold);
+ assertEquals(13, mif6ctl.mif6cPifi);
+ assertEquals(14, mif6ctl.vifcRateLimit);
+ }
+
+ @Test
+ public void testParseMif6ctl() {
+ final ByteBuffer buf = ByteBuffer.wrap(MSG_BYTES);
+ buf.order(ByteOrder.nativeOrder());
+ StructMif6ctl mif6ctl = StructMif6ctl.parse(StructMif6ctl.class, buf);
+
+ assertEquals(1, mif6ctl.mif6cMifi);
+ assertEquals(0, mif6ctl.mif6cFlags);
+ assertEquals(1, mif6ctl.vifcThreshold);
+ assertEquals(20, mif6ctl.mif6cPifi);
+ assertEquals(0, mif6ctl.vifcRateLimit);
+ }
+
+ @Test
+ public void testWriteToBytes() {
+ StructMif6ctl mif6ctl = new StructMif6ctl(1 /* mif6cMifi */,
+ (short) 0 /* mif6cFlags */,
+ (short) 1 /* vifcThreshold */,
+ 20 /* mif6cPifi */,
+ (long) 0 /* vifcRateLimit */);
+
+ byte[] bytes = mif6ctl.writeToBytes();
+
+ assertArrayEquals("bytes = " + Arrays.toString(bytes), MSG_BYTES, bytes);
+ }
+}
diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMrt6MsgTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMrt6MsgTest.java
new file mode 100644
index 0000000..f1b75a0
--- /dev/null
+++ b/staticlibs/tests/unit/src/com/android/net/module/util/structs/StructMrt6MsgTest.java
@@ -0,0 +1,58 @@
+package com.android.net.module.util.structs;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.InetAddresses;
+import androidx.test.runner.AndroidJUnit4;
+import com.android.net.module.util.Struct;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class StructMrt6MsgTest {
+
+ private static final byte[] MSG_BYTES = new byte[] {
+ 0, /* mbz = 0 */
+ 1, /* message type = MRT6MSG_NOCACHE */
+ 1, 0, /* mif u16 = 1 */
+ 0, 0, 0, 0, /* padding */
+ 32, 1, 13, -72, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, /* source=2001:db8::1 */
+ -1, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 52, /* destination=ff05::1234 */
+ };
+
+ private static final Inet6Address SOURCE =
+ (Inet6Address) InetAddresses.parseNumericAddress("2001:db8::1");
+ private static final Inet6Address GROUP =
+ (Inet6Address) InetAddresses.parseNumericAddress("ff05::1234");
+
+ @Test
+ public void testParseMrt6Msg() {
+ final ByteBuffer buf = ByteBuffer.wrap(MSG_BYTES);
+ StructMrt6Msg mrt6Msg = StructMrt6Msg.parse(buf);
+
+ assertEquals(1, mrt6Msg.mif);
+ assertEquals(StructMrt6Msg.MRT6MSG_NOCACHE, mrt6Msg.msgType);
+ assertEquals(SOURCE, mrt6Msg.src);
+ assertEquals(GROUP, mrt6Msg.dst);
+ }
+
+ @Test
+ public void testWriteToBytes() {
+ StructMrt6Msg msg = new StructMrt6Msg((byte) 0 /* mbz must be 0 */,
+ StructMrt6Msg.MRT6MSG_NOCACHE,
+ 1 /* mif */,
+ SOURCE,
+ GROUP);
+ byte[] bytes = msg.writeToBytes();
+
+ assertArrayEquals(MSG_BYTES, bytes);
+ }
+}
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index e55ba63..a0aafc6 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -43,6 +43,8 @@
test_suites: [
"cts",
"general-tests",
+ "mcts-tethering",
+ "mts-tethering",
"sts"
],
data: [
diff --git a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
index 10a0982..015b75a 100644
--- a/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
+++ b/tests/unit/java/com/android/server/connectivity/AutomaticOnOffKeepaliveTrackerTest.java
@@ -142,81 +142,81 @@
// Hexadecimal representation of a SOCK_DIAG response with tcp info.
private static final String SOCK_DIAG_TCP_INET_HEX =
// struct nlmsghdr.
- "14010000" + // length = 276
- "1400" + // type = SOCK_DIAG_BY_FAMILY
- "0301" + // flags = NLM_F_REQUEST | NLM_F_DUMP
- "00000000" + // seqno
- "00000000" + // pid (0 == kernel)
+ "14010000" // length = 276
+ + "1400" // type = SOCK_DIAG_BY_FAMILY
+ + "0301" // flags = NLM_F_REQUEST | NLM_F_DUMP
+ + "00000000" // seqno
+ + "00000000" // pid (0 == kernel)
// struct inet_diag_req_v2
- "02" + // family = AF_INET
- "06" + // state
- "00" + // timer
- "00" + // retrans
+ + "02" // family = AF_INET
+ + "06" // state
+ + "00" // timer
+ + "00" // retrans
// inet_diag_sockid
- "DEA5" + // idiag_sport = 42462
- "71B9" + // idiag_dport = 47473
- "0a006402000000000000000000000000" + // idiag_src = 10.0.100.2
- "08080808000000000000000000000000" + // idiag_dst = 8.8.8.8
- "00000000" + // idiag_if
- "34ED000076270000" + // idiag_cookie = 43387759684916
- "00000000" + // idiag_expires
- "00000000" + // idiag_rqueue
- "00000000" + // idiag_wqueue
- "00000000" + // idiag_uid
- "00000000" + // idiag_inode
+ + "DEA5" // idiag_sport = 42462
+ + "71B9" // idiag_dport = 47473
+ + "0a006402000000000000000000000000" // idiag_src = 10.0.100.2
+ + "08080808000000000000000000000000" // idiag_dst = 8.8.8.8
+ + "00000000" // idiag_if
+ + "34ED000076270000" // idiag_cookie = 43387759684916
+ + "00000000" // idiag_expires
+ + "00000000" // idiag_rqueue
+ + "00000000" // idiag_wqueue
+ + "00000000" // idiag_uid
+ + "00000000" // idiag_inode
// rtattr
- "0500" + // len = 5
- "0800" + // type = 8
- "00000000" + // data
- "0800" + // len = 8
- "0F00" + // type = 15(INET_DIAG_MARK)
- "850A0C00" + // data, socket mark=789125
- "AC00" + // len = 172
- "0200" + // type = 2(INET_DIAG_INFO)
+ + "0500" // len = 5
+ + "0800" // type = 8
+ + "00000000" // data
+ + "0800" // len = 8
+ + "0F00" // type = 15(INET_DIAG_MARK)
+ + "850A0C00" // data, socket mark=789125
+ + "AC00" // len = 172
+ + "0200" // type = 2(INET_DIAG_INFO)
// tcp_info
- "01" + // state = TCP_ESTABLISHED
- "00" + // ca_state = TCP_CA_OPEN
- "05" + // retransmits = 5
- "00" + // probes = 0
- "00" + // backoff = 0
- "07" + // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
- "88" + // wscale = 8
- "00" + // delivery_rate_app_limited = 0
- "4A911B00" + // rto = 1806666
- "00000000" + // ato = 0
- "2E050000" + // sndMss = 1326
- "18020000" + // rcvMss = 536
- "00000000" + // unsacked = 0
- "00000000" + // acked = 0
- "00000000" + // lost = 0
- "00000000" + // retrans = 0
- "00000000" + // fackets = 0
- "BB000000" + // lastDataSent = 187
- "00000000" + // lastAckSent = 0
- "BB000000" + // lastDataRecv = 187
- "BB000000" + // lastDataAckRecv = 187
- "DC050000" + // pmtu = 1500
- "30560100" + // rcvSsthresh = 87600
- "3E2C0900" + // rttt = 601150
- "1F960400" + // rttvar = 300575
- "78050000" + // sndSsthresh = 1400
- "0A000000" + // sndCwnd = 10
- "A8050000" + // advmss = 1448
- "03000000" + // reordering = 3
- "00000000" + // rcvrtt = 0
- "30560100" + // rcvspace = 87600
- "00000000" + // totalRetrans = 0
- "53AC000000000000" + // pacingRate = 44115
- "FFFFFFFFFFFFFFFF" + // maxPacingRate = 18446744073709551615
- "0100000000000000" + // bytesAcked = 1
- "0000000000000000" + // bytesReceived = 0
- "0A000000" + // SegsOut = 10
- "00000000" + // SegsIn = 0
- "00000000" + // NotSentBytes = 0
- "3E2C0900" + // minRtt = 601150
- "00000000" + // DataSegsIn = 0
- "00000000" + // DataSegsOut = 0
- "0000000000000000"; // deliverRate = 0
+ + "01" // state = TCP_ESTABLISHED
+ + "00" // ca_state = TCP_CA_OPEN
+ + "05" // retransmits = 5
+ + "00" // probes = 0
+ + "00" // backoff = 0
+ + "07" // option = TCPI_OPT_WSCALE|TCPI_OPT_SACK|TCPI_OPT_TIMESTAMPS
+ + "88" // wscale = 8
+ + "00" // delivery_rate_app_limited = 0
+ + "4A911B00" // rto = 1806666
+ + "00000000" // ato = 0
+ + "2E050000" // sndMss = 1326
+ + "18020000" // rcvMss = 536
+ + "00000000" // unsacked = 0
+ + "00000000" // acked = 0
+ + "00000000" // lost = 0
+ + "00000000" // retrans = 0
+ + "00000000" // fackets = 0
+ + "BB000000" // lastDataSent = 187
+ + "00000000" // lastAckSent = 0
+ + "BB000000" // lastDataRecv = 187
+ + "BB000000" // lastDataAckRecv = 187
+ + "DC050000" // pmtu = 1500
+ + "30560100" // rcvSsthresh = 87600
+ + "3E2C0900" // rttt = 601150
+ + "1F960400" // rttvar = 300575
+ + "78050000" // sndSsthresh = 1400
+ + "0A000000" // sndCwnd = 10
+ + "A8050000" // advmss = 1448
+ + "03000000" // reordering = 3
+ + "00000000" // rcvrtt = 0
+ + "30560100" // rcvspace = 87600
+ + "00000000" // totalRetrans = 0
+ + "53AC000000000000" // pacingRate = 44115
+ + "FFFFFFFFFFFFFFFF" // maxPacingRate = 18446744073709551615
+ + "0100000000000000" // bytesAcked = 1
+ + "0000000000000000" // bytesReceived = 0
+ + "0A000000" // SegsOut = 10
+ + "00000000" // SegsIn = 0
+ + "00000000" // NotSentBytes = 0
+ + "3E2C0900" // minRtt = 601150
+ + "00000000" // DataSegsIn = 0
+ + "00000000" // DataSegsOut = 0
+ + "0000000000000000"; // deliverRate = 0
private static final String SOCK_DIAG_NO_TCP_INET_HEX =
// struct nlmsghdr
"14000000" // length = 20
diff --git a/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt b/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt
new file mode 100644
index 0000000..9e2933f
--- /dev/null
+++ b/tests/unit/java/com/android/server/connectivity/mdns/MdnsReplySenderTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 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.server.connectivity.mdns
+
+import android.net.InetAddresses
+import android.net.LinkAddress
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Message
+import com.android.net.module.util.SharedLog
+import com.android.server.connectivity.mdns.MdnsConstants.IPV4_SOCKET_ADDR
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo
+import com.android.testutils.DevSdkIgnoreRunner
+import java.net.InetSocketAddress
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.timeout
+import org.mockito.Mockito.verify
+
+private const val TEST_PORT = 12345
+private const val DEFAULT_TIMEOUT_MS = 2000L
+private const val LONG_TTL = 4_500_000L
+private const val SHORT_TTL = 120_000L
+
+@RunWith(DevSdkIgnoreRunner::class)
+@IgnoreUpTo(Build.VERSION_CODES.S_V2)
+class MdnsReplySenderTest {
+ private val serviceName = arrayOf("MyTestService", "_testservice", "_tcp", "local")
+ private val serviceType = arrayOf("_testservice", "_tcp", "local")
+ private val hostname = arrayOf("Android_000102030405060708090A0B0C0D0E0F", "local")
+ private val hostAddresses = listOf(
+ LinkAddress(InetAddresses.parseNumericAddress("192.0.2.111"), 24),
+ LinkAddress(InetAddresses.parseNumericAddress("2001:db8::111"), 64),
+ LinkAddress(InetAddresses.parseNumericAddress("2001:db8::222"), 64))
+ private val answers = listOf(
+ MdnsPointerRecord(serviceType, 0L /* receiptTimeMillis */, false /* cacheFlush */,
+ LONG_TTL, serviceName))
+ private val additionalAnswers = listOf(
+ MdnsTextRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */, LONG_TTL,
+ listOf() /* entries */),
+ MdnsServiceRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ SHORT_TTL, 0 /* servicePriority */, 0 /* serviceWeight */, TEST_PORT, hostname),
+ MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ SHORT_TTL, hostAddresses[0].address),
+ MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ SHORT_TTL, hostAddresses[1].address),
+ MdnsInetAddressRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */,
+ SHORT_TTL, hostAddresses[2].address),
+ MdnsNsecRecord(serviceName, 0L /* receiptTimeMillis */, true /* cacheFlush */, LONG_TTL,
+ serviceName /* nextDomain */,
+ intArrayOf(MdnsRecord.TYPE_TXT, MdnsRecord.TYPE_SRV)),
+ MdnsNsecRecord(hostname, 0L /* receiptTimeMillis */, true /* cacheFlush */, SHORT_TTL,
+ hostname /* nextDomain */, intArrayOf(MdnsRecord.TYPE_A, MdnsRecord.TYPE_AAAA)))
+ private val thread = HandlerThread(MdnsReplySenderTest::class.simpleName)
+ private val socket = mock(MdnsInterfaceSocket::class.java)
+ private val buffer = ByteArray(1500)
+ private val sharedLog = SharedLog(MdnsReplySenderTest::class.simpleName)
+ private val deps = mock(MdnsReplySender.Dependencies::class.java)
+ private val handler by lazy { Handler(thread.looper) }
+ private val replySender by lazy {
+ MdnsReplySender(thread.looper, socket, buffer, sharedLog, false /* enableDebugLog */, deps)
+ }
+
+ @Before
+ fun setUp() {
+ thread.start()
+ doReturn(true).`when`(socket).hasJoinedIpv4()
+ doReturn(true).`when`(socket).hasJoinedIpv6()
+ }
+
+ @After
+ fun tearDown() {
+ thread.quitSafely()
+ thread.join()
+ }
+
+ private fun <T> runningOnHandlerAndReturn(functor: (() -> T)): T {
+ val future = CompletableFuture<T>()
+ handler.post {
+ future.complete(functor())
+ }
+ return future.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS)
+ }
+
+ private fun sendNow(packet: MdnsPacket, destination: InetSocketAddress):
+ Unit = runningOnHandlerAndReturn { replySender.sendNow(packet, destination) }
+
+ private fun queueReply(reply: MdnsReplyInfo):
+ Unit = runningOnHandlerAndReturn { replySender.queueReply(reply) }
+
+ @Test
+ fun testSendNow() {
+ val packet = MdnsPacket(0x8400,
+ listOf() /* questions */,
+ answers,
+ listOf() /* authorityRecords */,
+ additionalAnswers)
+ sendNow(packet, IPV4_SOCKET_ADDR)
+ verify(socket).send(argThat{ it.socketAddress.equals(IPV4_SOCKET_ADDR) })
+ }
+
+ @Test
+ fun testQueueReply() {
+ val reply = MdnsReplyInfo(answers, additionalAnswers, 20L /* sendDelayMs */,
+ IPV4_SOCKET_ADDR)
+ val handlerCaptor = ArgumentCaptor.forClass(Handler::class.java)
+ val messageCaptor = ArgumentCaptor.forClass(Message::class.java)
+ queueReply(reply)
+ verify(deps).sendMessageDelayed(handlerCaptor.capture(), messageCaptor.capture(), eq(20L))
+
+ val realHandler = handlerCaptor.value
+ val delayMessage = messageCaptor.value
+ realHandler.sendMessage(delayMessage)
+ verify(socket, timeout(DEFAULT_TIMEOUT_MS)).send(argThat{
+ it.socketAddress.equals(IPV4_SOCKET_ADDR)
+ })
+ }
+}
diff --git a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
index 89dcd39..a9da8d6 100644
--- a/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
+++ b/thread/framework/java/android/net/thread/IThreadNetworkController.aidl
@@ -38,6 +38,8 @@
void scheduleMigration(in PendingOperationalDataset pendingOpDataset, in IOperationReceiver receiver);
void leave(in IOperationReceiver receiver);
+ void setTestNetworkAsUpstream(in String testNetworkInterfaceName, in IOperationReceiver receiver);
+
int getThreadVersion();
void createRandomizedDataset(String networkName, IActiveOperationalDatasetReceiver receiver);
}
diff --git a/thread/framework/java/android/net/thread/ThreadNetworkController.java b/thread/framework/java/android/net/thread/ThreadNetworkController.java
index 34b0b06..b5699a9 100644
--- a/thread/framework/java/android/net/thread/ThreadNetworkController.java
+++ b/thread/framework/java/android/net/thread/ThreadNetworkController.java
@@ -31,6 +31,7 @@
import android.os.RemoteException;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -499,6 +500,31 @@
}
}
+ /**
+ * Sets to use a specified test network as the upstream.
+ *
+ * @param testNetworkInterfaceName The name of the test network interface. When it's null,
+ * forbids using test network as an upstream.
+ * @param executor the executor to execute {@code receiver}
+ * @param receiver the receiver to receive result of this operation
+ * @hide
+ */
+ @VisibleForTesting
+ @RequiresPermission("android.permission.THREAD_NETWORK_PRIVILEGED")
+ public void setTestNetworkAsUpstream(
+ @Nullable String testNetworkInterfaceName,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OutcomeReceiver<Void, ThreadNetworkException> receiver) {
+ requireNonNull(executor, "executor cannot be null");
+ requireNonNull(receiver, "receiver cannot be null");
+ try {
+ mControllerService.setTestNetworkAsUpstream(
+ testNetworkInterfaceName, new OperationReceiverProxy(executor, receiver));
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private static <T> void propagateError(
Executor executor,
OutcomeReceiver<T, ThreadNetworkException> receiver,
diff --git a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
index 60c97bf..1d8cd73 100644
--- a/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
+++ b/thread/service/java/com/android/server/thread/ThreadNetworkControllerService.java
@@ -53,14 +53,15 @@
import android.Manifest.permission;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.LocalNetworkConfig;
-import android.net.MulticastRoutingConfig;
import android.net.LocalNetworkInfo;
+import android.net.MulticastRoutingConfig;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
@@ -69,6 +70,7 @@
import android.net.NetworkRequest;
import android.net.NetworkScore;
import android.net.RouteInfo;
+import android.net.TestNetworkSpecifier;
import android.net.thread.ActiveOperationalDataset;
import android.net.thread.ActiveOperationalDataset.SecurityPolicy;
import android.net.thread.IActiveOperationalDatasetReceiver;
@@ -91,12 +93,12 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.ServiceManagerWrapper;
+import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import com.android.server.thread.openthread.IOtDaemon;
import com.android.server.thread.openthread.IOtDaemonCallback;
import com.android.server.thread.openthread.IOtStatusReceiver;
import com.android.server.thread.openthread.Ipv6AddressInfo;
import com.android.server.thread.openthread.OtDaemonState;
-import com.android.server.thread.openthread.BorderRouterConfigurationParcel;
import java.io.IOException;
import java.net.Inet6Address;
@@ -147,9 +149,10 @@
private MulticastRoutingConfig mUpstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
private MulticastRoutingConfig mDownstreamMulticastRoutingConfig = CONFIG_FORWARD_NONE;
private Network mUpstreamNetwork;
- private final NetworkRequest mUpstreamNetworkRequest;
+ private NetworkRequest mUpstreamNetworkRequest;
+ private UpstreamNetworkCallback mUpstreamNetworkCallback;
+ private TestNetworkSpecifier mUpstreamTestNetworkSpecifier;
private final HashMap<Network, String> mNetworkToInterface;
- private final LocalNetworkConfig mLocalNetworkConfig;
private BorderRouterConfigurationParcel mBorderRouterConfig;
@@ -168,16 +171,7 @@
mOtDaemonSupplier = otDaemonSupplier;
mConnectivityManager = connectivityManager;
mTunIfController = tunIfController;
- mUpstreamNetworkRequest =
- new NetworkRequest.Builder()
- .clearCapabilities()
- .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
- .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
- .build();
- mLocalNetworkConfig =
- new LocalNetworkConfig.Builder()
- .setUpstreamSelector(mUpstreamNetworkRequest)
- .build();
+ mUpstreamNetworkRequest = newUpstreamNetworkRequest();
mNetworkToInterface = new HashMap<Network, String>();
mBorderRouterConfig = new BorderRouterConfigurationParcel();
}
@@ -237,6 +231,60 @@
LinkAddress.LIFETIME_PERMANENT /* expirationTime */);
}
+ private NetworkRequest newUpstreamNetworkRequest() {
+ NetworkRequest.Builder builder = new NetworkRequest.Builder().clearCapabilities();
+
+ if (mUpstreamTestNetworkSpecifier != null) {
+ return builder.addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .setNetworkSpecifier(mUpstreamTestNetworkSpecifier)
+ .build();
+ }
+ return builder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ }
+
+ private LocalNetworkConfig newLocalNetworkConfig() {
+ return new LocalNetworkConfig.Builder()
+ .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
+ .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
+ .setUpstreamSelector(mUpstreamNetworkRequest)
+ .build();
+ }
+
+ @Override
+ public void setTestNetworkAsUpstream(
+ @Nullable String testNetworkInterfaceName, @NonNull IOperationReceiver receiver) {
+ enforceAllCallingPermissionsGranted(PERMISSION_THREAD_NETWORK_PRIVILEGED);
+
+ Log.i(TAG, "setTestNetworkAsUpstream: " + testNetworkInterfaceName);
+ mHandler.post(() -> setTestNetworkAsUpstreamInternal(testNetworkInterfaceName, receiver));
+ }
+
+ private void setTestNetworkAsUpstreamInternal(
+ @Nullable String testNetworkInterfaceName, @NonNull IOperationReceiver receiver) {
+ checkOnHandlerThread();
+
+ TestNetworkSpecifier testNetworkSpecifier = null;
+ if (testNetworkInterfaceName != null) {
+ testNetworkSpecifier = new TestNetworkSpecifier(testNetworkInterfaceName);
+ }
+
+ if (!Objects.equals(mUpstreamTestNetworkSpecifier, testNetworkSpecifier)) {
+ cancelRequestUpstreamNetwork();
+ mUpstreamTestNetworkSpecifier = testNetworkSpecifier;
+ mUpstreamNetworkRequest = newUpstreamNetworkRequest();
+ requestUpstreamNetwork();
+ sendLocalNetworkConfig();
+ }
+ try {
+ receiver.onSuccess();
+ } catch (RemoteException ignored) {
+ // do nothing if the client is dead
+ }
+ }
+
private void initializeOtDaemon() {
try {
getOtDaemon();
@@ -289,45 +337,63 @@
}
private void requestUpstreamNetwork() {
+ if (mUpstreamNetworkCallback != null) {
+ throw new AssertionError("The upstream network request is already there.");
+ }
+ mUpstreamNetworkCallback = new UpstreamNetworkCallback();
mConnectivityManager.registerNetworkCallback(
- mUpstreamNetworkRequest,
- new ConnectivityManager.NetworkCallback() {
- @Override
- public void onAvailable(@NonNull Network network) {
- Log.i(TAG, "onAvailable: " + network);
- }
+ mUpstreamNetworkRequest, mUpstreamNetworkCallback, mHandler);
+ }
- @Override
- public void onLost(@NonNull Network network) {
- Log.i(TAG, "onLost: " + network);
- }
+ private void cancelRequestUpstreamNetwork() {
+ if (mUpstreamNetworkCallback == null) {
+ throw new AssertionError("The upstream network request null.");
+ }
+ mNetworkToInterface.clear();
+ mConnectivityManager.unregisterNetworkCallback(mUpstreamNetworkCallback);
+ mUpstreamNetworkCallback = null;
+ }
- @Override
- public void onLinkPropertiesChanged(
- @NonNull Network network, @NonNull LinkProperties linkProperties) {
- Log.i(
- TAG,
- String.format(
- "onLinkPropertiesChanged: {network: %s, interface: %s}",
- network, linkProperties.getInterfaceName()));
- mNetworkToInterface.put(network, linkProperties.getInterfaceName());
- if (network.equals(mUpstreamNetwork)) {
- enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
- }
- }
- },
- mHandler);
+ private final class UpstreamNetworkCallback extends ConnectivityManager.NetworkCallback {
+ @Override
+ public void onAvailable(@NonNull Network network) {
+ checkOnHandlerThread();
+ Log.i(TAG, "onAvailable: " + network);
+ }
+
+ @Override
+ public void onLost(@NonNull Network network) {
+ checkOnHandlerThread();
+ Log.i(TAG, "onLost: " + network);
+ }
+
+ @Override
+ public void onLinkPropertiesChanged(
+ @NonNull Network network, @NonNull LinkProperties linkProperties) {
+ checkOnHandlerThread();
+ Log.i(
+ TAG,
+ String.format(
+ "onLinkPropertiesChanged: {network: %s, interface: %s}",
+ network, linkProperties.getInterfaceName()));
+ mNetworkToInterface.put(network, linkProperties.getInterfaceName());
+ if (network.equals(mUpstreamNetwork)) {
+ enableBorderRouting(mNetworkToInterface.get(mUpstreamNetwork));
+ }
+ }
}
private final class ThreadNetworkCallback extends ConnectivityManager.NetworkCallback {
@Override
public void onAvailable(@NonNull Network network) {
+ checkOnHandlerThread();
Log.i(TAG, "onAvailable: Thread network Available");
}
@Override
public void onLocalNetworkInfoChanged(
@NonNull Network network, @NonNull LocalNetworkInfo localNetworkInfo) {
+ checkOnHandlerThread();
Log.i(TAG, "onLocalNetworkInfoChanged: " + localNetworkInfo);
if (localNetworkInfo.getUpstreamNetwork() == null) {
mUpstreamNetwork = null;
@@ -370,7 +436,7 @@
TAG,
netCaps,
mLinkProperties,
- mLocalNetworkConfig,
+ newLocalNetworkConfig(),
score,
new NetworkAgentConfig.Builder().build(),
mNetworkProvider) {};
@@ -754,20 +820,9 @@
if (mNetworkAgent == null) {
return;
}
- final LocalNetworkConfig.Builder configBuilder = new LocalNetworkConfig.Builder();
- LocalNetworkConfig localNetworkConfig =
- configBuilder
- .setUpstreamMulticastRoutingConfig(mUpstreamMulticastRoutingConfig)
- .setDownstreamMulticastRoutingConfig(mDownstreamMulticastRoutingConfig)
- .setUpstreamSelector(mUpstreamNetworkRequest)
- .build();
+ final LocalNetworkConfig localNetworkConfig = newLocalNetworkConfig();
mNetworkAgent.sendLocalNetworkConfig(localNetworkConfig);
- Log.d(
- TAG,
- "Sent localNetworkConfig with upstreamConfig "
- + mUpstreamMulticastRoutingConfig
- + " downstreamConfig"
- + mDownstreamMulticastRoutingConfig);
+ Log.d(TAG, "Sent localNetworkConfig: " + localNetworkConfig);
}
private void handleMulticastForwardingStateChanged(boolean isEnabled) {
@@ -800,8 +855,8 @@
MulticastRoutingConfig newDownstreamConfig;
MulticastRoutingConfig.Builder builder;
- if (mDownstreamMulticastRoutingConfig.getForwardingMode() !=
- MulticastRoutingConfig.FORWARD_SELECTED) {
+ if (mDownstreamMulticastRoutingConfig.getForwardingMode()
+ != MulticastRoutingConfig.FORWARD_SELECTED) {
Log.e(
TAG,
"Ignore multicast listening address updates when downstream multicast "
@@ -809,8 +864,8 @@
// Don't update the address set if downstream multicast forwarding is disabled.
return;
}
- if (isAdded ==
- mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) {
+ if (isAdded
+ == mDownstreamMulticastRoutingConfig.getListeningAddresses().contains(address)) {
return;
}
diff --git a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
index 2f120b2..75eb043 100644
--- a/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
+++ b/thread/tests/unit/src/android/net/thread/ThreadNetworkControllerTest.java
@@ -28,11 +28,6 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
-import android.net.thread.IActiveOperationalDatasetReceiver;
-import android.net.thread.IOperationReceiver;
-import android.net.thread.IOperationalDatasetCallback;
-import android.net.thread.IStateCallback;
-import android.net.thread.IThreadNetworkController;
import android.net.thread.ThreadNetworkController.OperationalDatasetCallback;
import android.net.thread.ThreadNetworkController.StateCallback;
import android.os.Binder;
@@ -111,6 +106,11 @@
return (IOperationReceiver) invocation.getArguments()[1];
}
+ private static IOperationReceiver getSetTestNetworkAsUpstreamReceiver(
+ InvocationOnMock invocation) {
+ return (IOperationReceiver) invocation.getArguments()[1];
+ }
+
private static IActiveOperationalDatasetReceiver getCreateDatasetReceiver(
InvocationOnMock invocation) {
return (IActiveOperationalDatasetReceiver) invocation.getArguments()[1];
@@ -359,4 +359,27 @@
assertThat(errorCallbackUid.get()).isNotEqualTo(SYSTEM_UID);
assertThat(errorCallbackUid.get()).isEqualTo(Process.myUid());
}
+
+ @Test
+ public void setTestNetworkAsUpstream_callbackIsInvokedWithCallingAppIdentity()
+ throws Exception {
+ setBinderUid(SYSTEM_UID);
+
+ AtomicInteger callbackUid = new AtomicInteger(0);
+
+ doAnswer(
+ invoke -> {
+ getSetTestNetworkAsUpstreamReceiver(invoke).onSuccess();
+ return null;
+ })
+ .when(mMockService)
+ .setTestNetworkAsUpstream(anyString(), any(IOperationReceiver.class));
+ mController.setTestNetworkAsUpstream(
+ null, Runnable::run, v -> callbackUid.set(Binder.getCallingUid()));
+ mController.setTestNetworkAsUpstream(
+ new String("test0"), Runnable::run, v -> callbackUid.set(Binder.getCallingUid()));
+
+ assertThat(callbackUid.get()).isNotEqualTo(SYSTEM_UID);
+ assertThat(callbackUid.get()).isEqualTo(Process.myUid());
+ }
}