/* * Copyright (C) 2025 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.net; import static android.net.L2capNetworkSpecifier.HEADER_COMPRESSION_6LOWPAN; import android.annotation.Nullable; import android.bluetooth.BluetoothSocket; import android.content.Context; import android.net.L2capNetworkSpecifier; import android.net.LinkProperties; import android.net.NetworkAgent; import android.net.NetworkAgentConfig; import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkScore; import android.net.ip.IIpClient; import android.net.ip.IpClientCallbacks; import android.net.ip.IpClientManager; import android.net.ip.IpClientUtil; import android.net.shared.ProvisioningConfiguration; import android.os.ConditionVariable; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.util.Log; import com.android.server.L2capNetworkProvider; public class L2capNetwork { private static final NetworkScore NETWORK_SCORE = new NetworkScore.Builder().build(); private final String mLogTag; private final Handler mHandler; private final L2capPacketForwarder mForwarder; private final NetworkCapabilities mNetworkCapabilities; private final NetworkAgent mNetworkAgent; /** IpClient wrapper to handle IPv6 link-local provisioning for L2CAP tun. * * Note that the IpClient does not need to be stopped. */ public static class L2capIpClient extends IpClientCallbacks { private final String mLogTag; private final ConditionVariable mOnIpClientCreatedCv = new ConditionVariable(false); private final ConditionVariable mOnProvisioningSuccessCv = new ConditionVariable(false); @Nullable private IpClientManager mIpClient; @Nullable private volatile LinkProperties mLinkProperties; public L2capIpClient(String logTag, Context context, String ifname) { mLogTag = logTag; IpClientUtil.makeIpClient(context, ifname, this); } @Override public void onIpClientCreated(IIpClient ipClient) { mIpClient = new IpClientManager(ipClient, mLogTag); mOnIpClientCreatedCv.open(); } @Override public void onProvisioningSuccess(LinkProperties lp) { Log.d(mLogTag, "Successfully provisioned l2cap tun: " + lp); mLinkProperties = lp; mOnProvisioningSuccessCv.open(); } @Override public void onProvisioningFailure(LinkProperties lp) { Log.i(mLogTag, "Failed to provision l2cap tun: " + lp); mLinkProperties = null; mOnProvisioningSuccessCv.open(); } /** * Starts IPv6 link-local provisioning. * * @return LinkProperties on success, null on failure. */ @Nullable public LinkProperties start() { mOnIpClientCreatedCv.block(); // mIpClient guaranteed non-null. final ProvisioningConfiguration config = new ProvisioningConfiguration.Builder() .withoutIPv4() .withIpv6LinkLocalOnly() .withRandomMacAddress() // addr_gen_mode EUI64 -> random on tun. .build(); mIpClient.startProvisioning(config); // "Provisioning" is guaranteed to succeed as link-local only mode does not actually // require any provisioning. mOnProvisioningSuccessCv.block(); return mLinkProperties; } } public interface ICallback { /** Called when an error is encountered */ void onError(L2capNetwork network); /** Called when CS triggers NetworkAgent#onNetworkUnwanted */ void onNetworkUnwanted(L2capNetwork network); } public L2capNetwork(String logTag, Handler handler, Context context, NetworkProvider provider, BluetoothSocket socket, ParcelFileDescriptor tunFd, NetworkCapabilities nc, LinkProperties lp, L2capNetworkProvider.Dependencies deps, ICallback cb) { mLogTag = logTag; mHandler = handler; mNetworkCapabilities = nc; final L2capNetworkSpecifier spec = (L2capNetworkSpecifier) nc.getNetworkSpecifier(); final boolean compressHeaders = spec.getHeaderCompression() == HEADER_COMPRESSION_6LOWPAN; mForwarder = deps.createL2capPacketForwarder(handler, tunFd, socket, compressHeaders, () -> { // TODO: add a check that this callback is invoked on the handler thread. cb.onError(L2capNetwork.this); }); final NetworkAgentConfig config = new NetworkAgentConfig.Builder().build(); mNetworkAgent = new NetworkAgent(context, mHandler.getLooper(), mLogTag, nc, lp, NETWORK_SCORE, config, provider) { @Override public void onNetworkUnwanted() { Log.i(mLogTag, "Network is unwanted"); // TODO: add a check that this callback is invoked on the handler thread. cb.onNetworkUnwanted(L2capNetwork.this); } }; mNetworkAgent.register(); mNetworkAgent.markConnected(); } /** Create an L2capNetwork or return null on failure. */ @Nullable public static L2capNetwork create(Handler handler, Context context, NetworkProvider provider, String ifname, BluetoothSocket socket, ParcelFileDescriptor tunFd, NetworkCapabilities nc, L2capNetworkProvider.Dependencies deps, ICallback cb) { // TODO: add a check that this function is invoked on the handler thread. final String logTag = String.format("L2capNetwork[%s]", ifname); // L2capIpClient#start() blocks until provisioning either succeeds (and returns // LinkProperties) or fails (and returns null). // Note that since L2capNetwork is using IPv6 link-local provisioning the most likely // (only?) failure mode is due to the interface disappearing. final LinkProperties lp = deps.createL2capIpClient(logTag, context, ifname).start(); if (lp == null) return null; return new L2capNetwork( logTag, handler, context, provider, socket, tunFd, nc, lp, deps, cb); } /** Get the NetworkCapabilities used for this Network */ public NetworkCapabilities getNetworkCapabilities() { return mNetworkCapabilities; } /** Tear down the network and associated resources */ public void tearDown() { mNetworkAgent.unregister(); mForwarder.tearDown(); } }