Add mf6cctl struct definition.
This is used to configure multicast routes.
Test: atest NetworkStaticLibTests:com.android.net.moduletests.util.structs.StructMf6cctlTest
Change-Id: I57b36e6678262f48781b70ab77d49b920c7ddca2
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/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);
+ }
+}