blob: 5823dc49cb97a3354d64264af4cc49aee40f1959 [file] [log] [blame]
/*
* 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 android.net.util;
import static android.Manifest.permission.NETWORK_SETTINGS;
import static android.system.OsConstants.AF_PACKET;
import static android.system.OsConstants.SOCK_NONBLOCK;
import static android.system.OsConstants.SOCK_RAW;
import static com.android.net.module.util.NetworkStackConstants.ETHER_ADDR_LEN;
import static com.android.net.module.util.NetworkStackConstants.ETHER_DST_ADDR_OFFSET;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_LENGTH;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_OFFSET;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.net.TetheringManager;
import android.system.Os;
import androidx.annotation.NonNull;
import com.android.internal.util.HexDump;
import java.io.FileDescriptor;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class RawSocketUtils {
// For isTetheredInterface, a quick Tethering event callback is expected
// since there's no start/stop Tethering involved. This timeout allows
// system messages to be handled, preventing flaky test results.
private static final int TETHERING_EVENT_CALLBACK_TIMEOUT_MS = 3000;
/**
* Send a raw packet represents in Hex format to the downstream interface.
* <p>
* Note that the target interface is limited to tethering downstream
* for security considerations.
*/
@RequiresPermission(NETWORK_SETTINGS)
public static void sendRawPacketDownStream(@NonNull Context context, @NonNull String ifaceName,
@NonNull String packetInHex) throws Exception {
// 1. Verify Tethering Downstream Interface.
enforceTetheredInterface(context, ifaceName);
// 2. Hex to Byte Array Conversion
final byte[] packetData = HexDump.hexStringToByteArray(packetInHex);
final byte[] destMac = Arrays.copyOfRange(packetData, ETHER_DST_ADDR_OFFSET,
ETHER_DST_ADDR_OFFSET + ETHER_ADDR_LEN);
final byte[] etherTypeBytes = Arrays.copyOfRange(packetData, ETHER_TYPE_OFFSET,
ETHER_TYPE_OFFSET + ETHER_TYPE_LENGTH);
final int etherType = ((etherTypeBytes[0] & 0xFF) << 8) | (etherTypeBytes[1] & 0xFF);
// 3. Obtain Network Interface
final NetworkInterface iface = NetworkInterface.getByName(ifaceName);
if (iface == null) {
throw new IllegalArgumentException("Invalid network interface: " + ifaceName);
}
// 4. Construct and Send Packet.
final SocketAddress addr = SocketUtils.makePacketSocketAddress(
etherType,
iface.getIndex(),
destMac
);
final FileDescriptor sock = Os.socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, 0);
try {
Os.sendto(sock, packetData, 0, packetData.length, 0, addr);
} finally {
SocketUtils.closeSocket(sock);
}
}
@RequiresPermission(NETWORK_SETTINGS)
public static void enforceTetheredInterface(@NonNull Context context,
@NonNull String interfaceName)
throws ExecutionException, InterruptedException, TimeoutException, SecurityException {
final TetheringManager tm = context.getSystemService(TetheringManager.class);
final CompletableFuture<List<String>> tetheredInterfaces = new CompletableFuture<>();
final TetheringManager.TetheringEventCallback callback =
new TetheringManager.TetheringEventCallback() {
@Override
public void onTetheredInterfacesChanged(@NonNull List<String> interfaces) {
tetheredInterfaces.complete(interfaces);
}
};
tm.registerTetheringEventCallback(c -> c.run() /* executor */, callback);
final List<String> tetheredIfaces = tetheredInterfaces.get(
TETHERING_EVENT_CALLBACK_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!tetheredIfaces.contains(interfaceName)) {
throw new SecurityException("Only tethered interfaces " + tetheredIfaces
+ " are expected, but got " + interfaceName);
}
}
}