1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.net; 18 19 import static com.android.server.net.HeaderCompressionUtils.compress6lowpan; 20 import static com.android.server.net.HeaderCompressionUtils.decompress6lowpan; 21 22 import android.bluetooth.BluetoothSocket; 23 import android.os.Handler; 24 import android.os.ParcelFileDescriptor; 25 import android.system.ErrnoException; 26 import android.system.Os; 27 import android.system.OsConstants; 28 import android.util.Log; 29 30 import com.android.internal.annotations.VisibleForTesting; 31 32 import java.io.IOException; 33 import java.io.InputStream; 34 import java.io.OutputStream; 35 import java.nio.BufferUnderflowException; 36 37 /** 38 * Forwards packets from a BluetoothSocket of type L2CAP to a tun fd and vice versa. 39 * 40 * The forwarding logic operates on raw IP packets and there are no ethernet headers. 41 * Therefore, L3 MTU = L2 MTU. 42 */ 43 public class L2capPacketForwarder { 44 private static final String TAG = "L2capPacketForwarder"; 45 46 // DCT specifies an MTU of 1500. 47 // TODO: Set /proc/sys/net/ipv6/conf/${iface}/mtu to 1280 and the link MTU to 1528 to accept 48 // slightly larger packets on ingress (i.e. packets passing through a NAT64 gateway). 49 // MTU determines the value of the read buffers, so use the larger of the two. 50 @VisibleForTesting 51 public static final int MTU = 1528; 52 private final Handler mHandler; 53 private final IReadWriteFd mTunFd; 54 private final IReadWriteFd mL2capFd; 55 private final L2capThread mIngressThread; 56 private final L2capThread mEgressThread; 57 private final ICallback mCallback; 58 59 public interface ICallback { 60 /** Called when an error is encountered; should tear down forwarding. */ onError()61 void onError(); 62 } 63 64 private interface IReadWriteFd { 65 /** 66 * Read up to len bytes into bytes[off] and return bytes actually read. 67 * 68 * bytes[] must be of size >= off + len. 69 */ read(byte[] bytes, int off, int len)70 int read(byte[] bytes, int off, int len) throws IOException; 71 /** 72 * Write len bytes starting from bytes[off] 73 * 74 * bytes[] must be of size >= off + len. 75 */ write(byte[] bytes, int off, int len)76 void write(byte[] bytes, int off, int len) throws IOException; 77 /** Disallow further receptions, shutdown(fd, SHUT_RD) */ shutdownRead()78 void shutdownRead(); 79 /** Disallow further transmissions, shutdown(fd, SHUT_WR) */ shutdownWrite()80 void shutdownWrite(); 81 /** Close the fd */ close()82 void close(); 83 } 84 85 @VisibleForTesting 86 public static class BluetoothSocketWrapper implements IReadWriteFd { 87 private final BluetoothSocket mSocket; 88 private final InputStream mInputStream; 89 private final OutputStream mOutputStream; 90 BluetoothSocketWrapper(BluetoothSocket socket)91 public BluetoothSocketWrapper(BluetoothSocket socket) { 92 // TODO: assert that MTU can fit within Bluetooth L2CAP SDU (maximum size of an L2CAP 93 // packet). The L2CAP SDU is 65535 by default, but can be less when using hardware 94 // offload. 95 mSocket = socket; 96 try { 97 mInputStream = socket.getInputStream(); 98 mOutputStream = socket.getOutputStream(); 99 } catch (IOException e) { 100 // Per the API docs, this should not actually be possible. 101 Log.wtf(TAG, "Failed to get Input/OutputStream", e); 102 // Fail hard. 103 throw new IllegalStateException("Failed to get Input/OutputStream"); 104 } 105 } 106 107 /** Read from the BluetoothSocket. */ 108 @Override read(byte[] bytes, int off, int len)109 public int read(byte[] bytes, int off, int len) throws IOException { 110 // Note: EINTR is handled internally and automatically triggers a retry loop. 111 int bytesRead = mInputStream.read(bytes, off, len); 112 if (bytesRead < 0 || bytesRead > MTU) { 113 // Don't try to recover, just trigger network teardown. This might indicate a bug in 114 // the Bluetooth stack. 115 throw new IOException("Packet exceeds MTU or reached EOF. Read: " + bytesRead); 116 } 117 return bytesRead; 118 } 119 120 /** Write to the BluetoothSocket. */ 121 @Override write(byte[] bytes, int off, int len)122 public void write(byte[] bytes, int off, int len) throws IOException { 123 // Note: EINTR is handled internally and automatically triggers a retry loop. 124 mOutputStream.write(bytes, off, len); 125 } 126 127 @Override shutdownRead()128 public void shutdownRead() { 129 // BluetoothSocket does not expose methods to shutdown read / write individually; 130 // however, BluetoothSocket#close() shuts down both read and write and is safe to be 131 // called multiple times from any thread. 132 try { 133 mSocket.close(); 134 } catch (IOException e) { 135 Log.w(TAG, "shutdownRead: Failed to close BluetoothSocket", e); 136 } 137 } 138 139 @Override shutdownWrite()140 public void shutdownWrite() { 141 // BluetoothSocket does not expose methods to shutdown read / write individually; 142 // however, BluetoothSocket#close() shuts down both read and write and is safe to be 143 // called multiple times from any thread. 144 try { 145 mSocket.close(); 146 } catch (IOException e) { 147 Log.w(TAG, "shutdownWrite: Failed to close BluetoothSocket", e); 148 } 149 } 150 151 @Override close()152 public void close() { 153 // BluetoothSocket#close() is safe to be called multiple times. 154 try { 155 mSocket.close(); 156 } catch (IOException e) { 157 Log.w(TAG, "close: Failed to close BluetoothSocket", e); 158 } 159 } 160 } 161 162 @VisibleForTesting 163 public static class FdWrapper implements IReadWriteFd { 164 private final ParcelFileDescriptor mFd; 165 FdWrapper(ParcelFileDescriptor fd)166 public FdWrapper(ParcelFileDescriptor fd) { 167 mFd = fd; 168 } 169 170 @Override read(byte[] bytes, int off, int len)171 public int read(byte[] bytes, int off, int len) throws IOException { 172 try { 173 // Note: EINTR is handled internally and automatically triggers a retry loop. 174 return Os.read(mFd.getFileDescriptor(), bytes, off, len); 175 } catch (ErrnoException e) { 176 throw new IOException(e); 177 } 178 } 179 180 /** 181 * Write to BluetoothSocket. 182 */ 183 @Override write(byte[] bytes, int off, int len)184 public void write(byte[] bytes, int off, int len) throws IOException { 185 try { 186 // Note: EINTR is handled internally and automatically triggers a retry loop. 187 Os.write(mFd.getFileDescriptor(), bytes, off, len); 188 } catch (ErrnoException e) { 189 throw new IOException(e); 190 } 191 } 192 193 @Override shutdownRead()194 public void shutdownRead() { 195 try { 196 Os.shutdown(mFd.getFileDescriptor(), OsConstants.SHUT_RD); 197 } catch (ErrnoException e) { 198 Log.w(TAG, "shutdownRead: Failed to shutdown(fd, SHUT_RD)", e); 199 } 200 } 201 202 @Override shutdownWrite()203 public void shutdownWrite() { 204 try { 205 Os.shutdown(mFd.getFileDescriptor(), OsConstants.SHUT_WR); 206 } catch (ErrnoException e) { 207 Log.w(TAG, "shutdownWrite: Failed to shutdown(fd, SHUT_WR)", e); 208 } 209 } 210 211 @Override close()212 public void close() { 213 try { 214 // Safe to call multiple times. Both Os.close(FileDescriptor) and 215 // ParcelFileDescriptor#close() offer protection against double-closing an fd. 216 mFd.close(); 217 } catch (IOException e) { 218 Log.w(TAG, "close: Failed to close fd", e); 219 } 220 } 221 } 222 223 private class L2capThread extends Thread { 224 // Set mBuffer length to MTU + 1 to catch read() overflows. 225 private final byte[] mBuffer = new byte[MTU + 1]; 226 private volatile boolean mIsRunning = true; 227 228 private final String mLogTag; 229 private final IReadWriteFd mReadFd; 230 private final IReadWriteFd mWriteFd; 231 private final boolean mIsIngress; 232 private final boolean mCompressHeaders; 233 L2capThread(IReadWriteFd readFd, IReadWriteFd writeFd, boolean isIngress, boolean compressHeaders)234 L2capThread(IReadWriteFd readFd, IReadWriteFd writeFd, boolean isIngress, 235 boolean compressHeaders) { 236 super("L2capNetworkProvider-ForwarderThread"); 237 mLogTag = isIngress ? "L2capForwarderThread-Ingress" : "L2capForwarderThread-Egress"; 238 mReadFd = readFd; 239 mWriteFd = writeFd; 240 mIsIngress = isIngress; 241 mCompressHeaders = compressHeaders; 242 } 243 postOnError()244 private void postOnError() { 245 mHandler.post(() -> { 246 // All callbacks must be called on handler thread. 247 mCallback.onError(); 248 }); 249 } 250 251 @Override run()252 public void run() { 253 while (mIsRunning) { 254 try { 255 int readBytes = mReadFd.read(mBuffer, 0 /*off*/, mBuffer.length); 256 // No bytes to write, continue. 257 if (readBytes <= 0) { 258 Log.w(mLogTag, "Zero-byte read encountered: " + readBytes); 259 continue; 260 } 261 262 if (mCompressHeaders) { 263 if (mIsIngress) { 264 readBytes = decompress6lowpan(mBuffer, readBytes); 265 } else { 266 readBytes = compress6lowpan(mBuffer, readBytes); 267 } 268 } 269 270 // If the packet is 0-length post de/compression or exceeds MTU, drop it. 271 // Note that a large read on BluetoothSocket throws an IOException to tear down 272 // the network. 273 if (readBytes <= 0 || readBytes > MTU) continue; 274 275 mWriteFd.write(mBuffer, 0 /*off*/, readBytes); 276 } catch (IOException|BufferUnderflowException e) { 277 Log.e(mLogTag, "L2capThread exception", e); 278 // Tear down the network on any error. 279 mIsRunning = false; 280 // Notify provider that forwarding has stopped. 281 postOnError(); 282 } 283 } 284 } 285 tearDown()286 public void tearDown() { 287 mIsRunning = false; 288 mReadFd.shutdownRead(); 289 mWriteFd.shutdownWrite(); 290 } 291 } 292 L2capPacketForwarder(Handler handler, ParcelFileDescriptor tunFd, BluetoothSocket socket, boolean compressHdrs, ICallback cb)293 public L2capPacketForwarder(Handler handler, ParcelFileDescriptor tunFd, BluetoothSocket socket, 294 boolean compressHdrs, ICallback cb) { 295 this(handler, new FdWrapper(tunFd), new BluetoothSocketWrapper(socket), compressHdrs, cb); 296 } 297 298 @VisibleForTesting L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd, boolean compressHeaders, ICallback cb)299 L2capPacketForwarder(Handler handler, IReadWriteFd tunFd, IReadWriteFd l2capFd, 300 boolean compressHeaders, ICallback cb) { 301 mHandler = handler; 302 mTunFd = tunFd; 303 mL2capFd = l2capFd; 304 mCallback = cb; 305 306 mIngressThread = new L2capThread(l2capFd, tunFd, true /*isIngress*/, compressHeaders); 307 mEgressThread = new L2capThread(tunFd, l2capFd, false /*isIngress*/, compressHeaders); 308 309 mIngressThread.start(); 310 mEgressThread.start(); 311 } 312 313 /** 314 * Tear down the L2capPacketForwarder. 315 * 316 * This operation closes both the passed tun fd and BluetoothSocket. 317 **/ tearDown()318 public void tearDown() { 319 // Destroying both threads first guarantees that both read and write side of FD have been 320 // shutdown. 321 mIngressThread.tearDown(); 322 mEgressThread.tearDown(); 323 324 // In order to interrupt a blocking read on the BluetoothSocket, the BluetoothSocket must be 325 // closed (which triggers shutdown()). This means, the BluetoothSocket must be closed inside 326 // L2capPacketForwarder. Tear down the tun fd alongside it for consistency. 327 mTunFd.close(); 328 mL2capFd.close(); 329 330 try { 331 mIngressThread.join(); 332 } catch (InterruptedException e) { 333 // join() interrupted in tearDown path, do nothing. 334 } 335 try { 336 mEgressThread.join(); 337 } catch (InterruptedException e) { 338 // join() interrupted in tearDown path, do nothing. 339 } 340 } 341 } 342