diff --git a/staticlibs/device/com/android/net/module/util/netlink/OsAccess.java b/staticlibs/device/com/android/net/module/util/netlink/OsAccess.java new file mode 100644 index 0000000000..7591d5cdbf --- /dev/null +++ b/staticlibs/device/com/android/net/module/util/netlink/OsAccess.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 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.netlink; + +import android.system.Os; + +import androidx.annotation.NonNull; + +/** + * This class wraps the static methods of {@link android.system.Os} for mocking and testing. + */ +public class OsAccess { + /** + * Constant indicating that the {@code if_nametoindex()} function could not find the network + * interface index corresponding to the given interface name. + */ + public static int INVALID_INTERFACE_INDEX = 0; + + /** Wraps {@link Os#if_nametoindex(String)}. */ + public int if_nametoindex(@NonNull String name) { + return Os.if_nametoindex(name); + } +} diff --git a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java index 72c770a471..887a7010cf 100644 --- a/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java +++ b/staticlibs/device/com/android/net/module/util/netlink/RtNetlinkLinkMessage.java @@ -16,7 +16,12 @@ package com.android.net.module.util.netlink; +import static android.system.OsConstants.AF_UNSPEC; + import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN; +import static com.android.net.module.util.netlink.NetlinkConstants.IFF_UP; +import static com.android.net.module.util.netlink.NetlinkConstants.RTM_NEWLINK; +import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST_ACK; import android.net.MacAddress; import android.system.OsConstants; @@ -218,6 +223,35 @@ public class RtNetlinkLinkMessage extends NetlinkMessage { return length; } + /** + * Create a link message to set the operational state (up or down) of a network interface. + * + * @param interfaceName The network interface name. + * @param sequenceNumber The sequence number to use for the Netlink message. + * @param isUp {@code true} to set the interface up, {@code false} to set it down. + * @return A `RtNetlinkLinkMessage` instance configured to set the link state. + */ + @Nullable + public static RtNetlinkLinkMessage createSetLinkStateMessage(@NonNull String interfaceName, + int sequenceNumber, boolean isUp) { + return createSetLinkStateMessage(interfaceName, sequenceNumber, isUp, new OsAccess()); + } + + @VisibleForTesting + @Nullable + protected static RtNetlinkLinkMessage createSetLinkStateMessage(@NonNull String interfaceName, + int sequenceNumber, boolean isUp, OsAccess osAccess) { + final int interfaceIndex = osAccess.if_nametoindex(interfaceName); + if (interfaceIndex == OsAccess.INVALID_INTERFACE_INDEX) { + return null; + } + + return RtNetlinkLinkMessage.build( + new StructNlMsgHdr(0, RTM_NEWLINK, NLM_F_REQUEST_ACK, sequenceNumber), + new StructIfinfoMsg((short) AF_UNSPEC, (short) 0, interfaceIndex, + isUp ? IFF_UP : 0, IFF_UP), DEFAULT_MTU, null, null); + } + @Override public String toString() { return "RtNetlinkLinkMessage{ " diff --git a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java index 527236695c..7cc95de409 100644 --- a/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java +++ b/staticlibs/device/com/android/net/module/util/netlink/StructNlMsgHdr.java @@ -32,10 +32,11 @@ public class StructNlMsgHdr { // Already aligned. public static final int STRUCT_SIZE = 16; - public static final short NLM_F_REQUEST = 0x0001; - public static final short NLM_F_MULTI = 0x0002; - public static final short NLM_F_ACK = 0x0004; - public static final short NLM_F_ECHO = 0x0008; + public static final short NLM_F_REQUEST = 0x0001; + public static final short NLM_F_MULTI = 0x0002; + public static final short NLM_F_ACK = 0x0004; + public static final short NLM_F_ECHO = 0x0008; + public static final short NLM_F_REQUEST_ACK = NLM_F_REQUEST | NLM_F_ACK; // Flags for a GET request. public static final short NLM_F_ROOT = 0x0100; public static final short NLM_F_MATCH = 0x0200; diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java index afe220f524..b9f366181b 100644 --- a/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java +++ b/staticlibs/tests/unit/src/com/android/net/module/util/netlink/RtNetlinkLinkMessageTest.java @@ -24,24 +24,29 @@ 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.mockito.Mockito.when; +import android.annotation.SuppressLint; import android.net.MacAddress; import android.system.OsConstants; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; import com.android.net.module.util.HexDump; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import java.nio.ByteBuffer; import java.nio.ByteOrder; -@RunWith(AndroidJUnit4.class) +@RunWith(MockitoJUnitRunner.class) @SmallTest public class RtNetlinkLinkMessageTest { + @Mock + private OsAccess mOsAccess; // An example of the full RTM_NEWLINK message. private static final String RTM_NEWLINK_HEX = @@ -185,6 +190,57 @@ public class RtNetlinkLinkMessageTest { assertTrue(linkMsg.getInterfaceName().equals("wlan0")); } + @Test + public void testCreateSetLinkUpMessage() { + final String expectedHexBytes = + "20000000100005006824000000000000" // struct nlmsghdr + + "00000000080000000100000001000000"; // struct ifinfomsg + final String interfaceName = "wlan0"; + final int interfaceIndex = 8; + final int sequenceNumber = 0x2468; + final boolean isUp = true; + + when(mOsAccess.if_nametoindex(interfaceName)).thenReturn(interfaceIndex); + + final RtNetlinkLinkMessage msg = RtNetlinkLinkMessage.createSetLinkStateMessage( + interfaceName, sequenceNumber, isUp, mOsAccess); + assertNotNull(msg); + final byte[] bytes = msg.pack(ByteOrder.LITTLE_ENDIAN); // For testing. + assertEquals(expectedHexBytes, HexDump.toHexString(bytes)); + } + + @Test + public void testCreateSetLinkDownMessage() { + final String expectedHexBytes = + "20000000100005006824000000000000" // struct nlmsghdr + + "00000000080000000000000001000000"; // struct ifinfomsg + final String interfaceName = "wlan0"; + final int interfaceIndex = 8; + final int sequenceNumber = 0x2468; + final boolean isUp = false; + + when(mOsAccess.if_nametoindex(interfaceName)).thenReturn(interfaceIndex); + + final RtNetlinkLinkMessage msg = RtNetlinkLinkMessage.createSetLinkStateMessage( + interfaceName, sequenceNumber, isUp, mOsAccess); + assertNotNull(msg); + final byte[] bytes = msg.pack(ByteOrder.LITTLE_ENDIAN); // For testing. + assertEquals(expectedHexBytes, HexDump.toHexString(bytes)); + } + + @Test + public void testCreateSetLinkStateMessage_InvalidInterface() { + final String interfaceName = "wlan0"; + final int sequenceNumber = 0x2468; + final boolean isUp = false; + + when(mOsAccess.if_nametoindex(interfaceName)).thenReturn(OsAccess.INVALID_INTERFACE_INDEX); + + final RtNetlinkLinkMessage msg = RtNetlinkLinkMessage.createSetLinkStateMessage( + interfaceName, sequenceNumber, isUp, mOsAccess); + assertNull(msg); + } + @Test public void testToString() { final ByteBuffer byteBuffer = toByteBuffer(RTM_NEWLINK_HEX);