1 /* 2 * Copyright (C) 2017 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.networkstack.tethering; 18 19 import static com.android.net.module.util.netlink.NetlinkUtils.SOCKET_RECV_BUFSIZE; 20 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_DUMP; 21 import static com.android.net.module.util.netlink.StructNlMsgHdr.NLM_F_REQUEST; 22 23 import android.annotation.IntDef; 24 import android.annotation.NonNull; 25 import android.net.util.SocketUtils; 26 import android.os.Handler; 27 import android.os.NativeHandle; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.system.OsConstants; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.net.module.util.SharedLog; 34 import com.android.net.module.util.netlink.NetlinkUtils; 35 import com.android.net.module.util.netlink.StructNfGenMsg; 36 import com.android.net.module.util.netlink.StructNlMsgHdr; 37 38 import java.io.FileDescriptor; 39 import java.io.IOException; 40 import java.io.InterruptedIOException; 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.net.SocketAddress; 44 import java.net.SocketException; 45 import java.nio.ByteBuffer; 46 import java.nio.ByteOrder; 47 import java.util.ArrayList; 48 49 /** 50 * Capture tethering dependencies, for injection. 51 * 52 * @hide 53 */ 54 public class OffloadHardwareInterface { 55 private static final String TAG = OffloadHardwareInterface.class.getSimpleName(); 56 private static final String YIELDS = " -> "; 57 // Change this value to control whether tether offload is enabled or 58 // disabled by default in the absence of an explicit Settings value. 59 // See accompanying unittest to distinguish 0 from non-0 values. 60 private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0; 61 private static final String NO_INTERFACE_NAME = ""; 62 private static final String NO_IPV4_ADDRESS = ""; 63 private static final String NO_IPV4_GATEWAY = ""; 64 // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h 65 public static final int NF_NETLINK_CONNTRACK_NEW = 1; 66 public static final int NF_NETLINK_CONNTRACK_UPDATE = 2; 67 public static final int NF_NETLINK_CONNTRACK_DESTROY = 4; 68 // Reference libnetfilter_conntrack/linux_nfnetlink_conntrack.h 69 public static final short NFNL_SUBSYS_CTNETLINK = 1; 70 public static final short IPCTNL_MSG_CT_NEW = 0; 71 public static final short IPCTNL_MSG_CT_GET = 1; 72 73 private final long NETLINK_MESSAGE_TIMEOUT_MS = 500; 74 75 private final Handler mHandler; 76 private final SharedLog mLog; 77 private final Dependencies mDeps; 78 private IOffloadHal mIOffload; 79 80 // TODO: Use major-minor version control to prevent from defining new constants. 81 static final int OFFLOAD_HAL_VERSION_NONE = 0; 82 static final int OFFLOAD_HAL_VERSION_HIDL_1_0 = 1; 83 static final int OFFLOAD_HAL_VERSION_HIDL_1_1 = 2; 84 static final int OFFLOAD_HAL_VERSION_AIDL = 3; 85 /** @hide */ 86 @Retention(RetentionPolicy.SOURCE) 87 @IntDef(prefix = "OFFLOAD_HAL_VERSION_", value = { 88 OFFLOAD_HAL_VERSION_NONE, 89 OFFLOAD_HAL_VERSION_HIDL_1_0, 90 OFFLOAD_HAL_VERSION_HIDL_1_1, 91 OFFLOAD_HAL_VERSION_AIDL, 92 }) 93 public @interface OffloadHalVersion {} 94 95 @NonNull halVerToString(int version)96 static String halVerToString(int version) { 97 switch(version) { 98 case OFFLOAD_HAL_VERSION_HIDL_1_0: 99 return "HIDL 1.0"; 100 case OFFLOAD_HAL_VERSION_HIDL_1_1: 101 return "HIDL 1.1"; 102 case OFFLOAD_HAL_VERSION_AIDL: 103 return "AIDL"; 104 case OFFLOAD_HAL_VERSION_NONE: 105 return "None"; 106 default: 107 throw new IllegalArgumentException("Unsupported version int " + version); 108 } 109 } 110 111 private OffloadHalCallback mOffloadHalCallback; 112 113 /** The callback to notify status of offload management process. */ 114 public static class OffloadHalCallback { 115 /** Offload started. */ onStarted()116 public void onStarted() {} 117 /** 118 * Offload stopped because an error has occurred in lower layer. 119 */ onStoppedError()120 public void onStoppedError() {} 121 /** 122 * Offload stopped because the device has moved to a bearer on which hardware offload is 123 * not supported. Subsequent calls to setUpstreamParameters and add/removeDownstream will 124 * likely fail and cannot be presumed to be saved inside of the hardware management process. 125 * Upon receiving #onSupportAvailable(), the caller should reprogram the hardware to begin 126 * offload again. 127 */ onStoppedUnsupported()128 public void onStoppedUnsupported() {} 129 /** Indicate that offload is able to proivde support for this time. */ onSupportAvailable()130 public void onSupportAvailable() {} 131 /** Offload stopped because of usage limit reached. */ onStoppedLimitReached()132 public void onStoppedLimitReached() {} 133 /** Indicate that data warning quota is reached. */ onWarningReached()134 public void onWarningReached() {} 135 136 /** Indicate to update NAT timeout. */ onNatTimeoutUpdate(int proto, String srcAddr, int srcPort, String dstAddr, int dstPort)137 public void onNatTimeoutUpdate(int proto, 138 String srcAddr, int srcPort, 139 String dstAddr, int dstPort) {} 140 } 141 142 /** The object which records Tx/Rx forwarded bytes. */ 143 public static class ForwardedStats { 144 public long rxBytes; 145 public long txBytes; 146 ForwardedStats()147 public ForwardedStats() { 148 rxBytes = 0; 149 txBytes = 0; 150 } 151 152 @VisibleForTesting ForwardedStats(long rxBytes, long txBytes)153 public ForwardedStats(long rxBytes, long txBytes) { 154 this.rxBytes = rxBytes; 155 this.txBytes = txBytes; 156 } 157 158 /** Add Tx/Rx bytes. */ add(ForwardedStats other)159 public void add(ForwardedStats other) { 160 rxBytes += other.rxBytes; 161 txBytes += other.txBytes; 162 } 163 164 /** Returns the string representation of this object. */ toString()165 public String toString() { 166 return String.format("rx:%s tx:%s", rxBytes, txBytes); 167 } 168 } 169 OffloadHardwareInterface(Handler h, SharedLog log)170 public OffloadHardwareInterface(Handler h, SharedLog log) { 171 this(h, log, new Dependencies(h, log)); 172 } 173 OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps)174 OffloadHardwareInterface(Handler h, SharedLog log, Dependencies deps) { 175 mHandler = h; 176 mLog = log.forSubComponent(TAG); 177 mDeps = deps; 178 } 179 180 /** Capture OffloadHardwareInterface dependencies, for injection. */ 181 static class Dependencies { 182 private final Handler mHandler; 183 private final SharedLog mLog; 184 Dependencies(Handler handler, SharedLog log)185 Dependencies(Handler handler, SharedLog log) { 186 mHandler = handler; 187 mLog = log; 188 } 189 getOffload()190 public IOffloadHal getOffload() { 191 // Prefer AIDL implementation if its service is declared. 192 IOffloadHal hal = OffloadHalAidlImpl.getIOffloadHal(mHandler, mLog); 193 if (hal == null) { 194 hal = OffloadHalHidlImpl.getIOffloadHal(mHandler, mLog); 195 } 196 return hal; 197 } 198 createConntrackSocket(final int groups)199 public NativeHandle createConntrackSocket(final int groups) { 200 final FileDescriptor fd; 201 try { 202 fd = NetlinkUtils.netlinkSocketForProto(OsConstants.NETLINK_NETFILTER, 203 SOCKET_RECV_BUFSIZE); 204 } catch (ErrnoException e) { 205 mLog.e("Unable to create conntrack socket " + e); 206 return null; 207 } 208 209 final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups); 210 try { 211 Os.bind(fd, sockAddr); 212 } catch (ErrnoException | SocketException e) { 213 mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e); 214 try { 215 SocketUtils.closeSocket(fd); 216 } catch (IOException ie) { 217 // Nothing we can do here 218 } 219 return null; 220 } 221 try { 222 Os.connect(fd, sockAddr); 223 } catch (ErrnoException | SocketException e) { 224 mLog.e("connect to kernel fail for groups " + groups + " error: " + e); 225 try { 226 SocketUtils.closeSocket(fd); 227 } catch (IOException ie) { 228 // Nothing we can do here 229 } 230 return null; 231 } 232 233 return new NativeHandle(fd, true); 234 } 235 } 236 237 /** Get default value indicating whether offload is supported. */ getDefaultTetherOffloadDisabled()238 public int getDefaultTetherOffloadDisabled() { 239 return DEFAULT_TETHER_OFFLOAD_DISABLED; 240 } 241 242 @VisibleForTesting sendIpv4NfGenMsg(@onNull NativeHandle handle, short type, short flags)243 void sendIpv4NfGenMsg(@NonNull NativeHandle handle, short type, short flags) { 244 final int length = StructNlMsgHdr.STRUCT_SIZE + StructNfGenMsg.STRUCT_SIZE; 245 final byte[] msg = new byte[length]; 246 final ByteBuffer byteBuffer = ByteBuffer.wrap(msg); 247 byteBuffer.order(ByteOrder.nativeOrder()); 248 249 final StructNlMsgHdr nlh = new StructNlMsgHdr(); 250 nlh.nlmsg_len = length; 251 nlh.nlmsg_type = type; 252 nlh.nlmsg_flags = flags; 253 nlh.nlmsg_seq = 0; 254 nlh.pack(byteBuffer); 255 256 // Header needs to be added to buffer since a generic netlink request is being sent. 257 final StructNfGenMsg nfh = new StructNfGenMsg((byte) OsConstants.AF_INET); 258 nfh.pack(byteBuffer); 259 260 try { 261 NetlinkUtils.sendMessage(handle.getFileDescriptor(), msg, 0 /* offset */, length, 262 NETLINK_MESSAGE_TIMEOUT_MS); 263 } catch (ErrnoException | InterruptedIOException e) { 264 mLog.e("Unable to send netfilter message, error: " + e); 265 } 266 } 267 268 @VisibleForTesting requestSocketDump(NativeHandle handle)269 void requestSocketDump(NativeHandle handle) { 270 sendIpv4NfGenMsg(handle, (short) ((NFNL_SUBSYS_CTNETLINK << 8) | IPCTNL_MSG_CT_GET), 271 (short) (NLM_F_REQUEST | NLM_F_DUMP)); 272 } 273 maybeCloseFdInNativeHandles(final NativeHandle... handles)274 private void maybeCloseFdInNativeHandles(final NativeHandle... handles) { 275 for (NativeHandle h : handles) { 276 if (h == null) continue; 277 try { 278 h.close(); 279 } catch (IOException | IllegalStateException e) { 280 // IllegalStateException means fd is already closed, do nothing here. 281 // Also nothing we can do if IOException. 282 } 283 } 284 } 285 initWithHandles(NativeHandle h1, NativeHandle h2)286 private int initWithHandles(NativeHandle h1, NativeHandle h2) { 287 if (h1 == null || h2 == null) { 288 // Set mIOffload to null has two purposes: 289 // 1. NativeHandles can be closed after initWithHandles() fails 290 // 2. Prevent mIOffload.stopOffload() to be called in stopOffload() 291 mIOffload = null; 292 mLog.e("Failed to create socket."); 293 return OFFLOAD_HAL_VERSION_NONE; 294 } 295 296 requestSocketDump(h1); 297 if (!mIOffload.initOffload(h1, h2, mOffloadHalCallback)) { 298 mLog.e("Failed to initialize offload."); 299 return OFFLOAD_HAL_VERSION_NONE; 300 } 301 302 return mIOffload.getVersion(); 303 } 304 305 /** 306 * Initialize the tethering offload HAL. 307 * 308 * @return one of {@code OFFLOAD_HAL_VERSION_*} represents the HAL version, or 309 * {@link #OFFLOAD_HAL_VERSION_NONE} if failed. 310 */ initOffload(OffloadHalCallback offloadCb)311 public int initOffload(OffloadHalCallback offloadCb) { 312 if (mIOffload == null) { 313 mIOffload = mDeps.getOffload(); 314 if (mIOffload == null) { 315 mLog.i("No tethering offload HAL service found."); 316 return OFFLOAD_HAL_VERSION_NONE; 317 } 318 mLog.i("Tethering offload version " 319 + halVerToString(mIOffload.getVersion()) + " is supported."); 320 } 321 322 // Per the IOffload definition: 323 // 324 // h1 provides a file descriptor bound to the following netlink groups 325 // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY). 326 // 327 // h2 provides a file descriptor bound to the following netlink groups 328 // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY). 329 final NativeHandle h1 = mDeps.createConntrackSocket( 330 NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY); 331 final NativeHandle h2 = mDeps.createConntrackSocket( 332 NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY); 333 334 mOffloadHalCallback = offloadCb; 335 final int version = initWithHandles(h1, h2); 336 337 // Explicitly close FDs for HIDL or when mIOffload is null (cleared in initWithHandles). 338 // AIDL will pass the original FDs to the service, they shouldn't be closed here. 339 if (mIOffload == null || mIOffload.getVersion() < OFFLOAD_HAL_VERSION_AIDL) { 340 maybeCloseFdInNativeHandles(h1, h2); 341 } 342 return version; 343 } 344 345 /** Stop the tethering offload HAL. */ stopOffload()346 public void stopOffload() { 347 if (mIOffload != null) { 348 if (!mIOffload.stopOffload()) { 349 mLog.e("Failed to stop offload."); 350 } 351 } 352 mIOffload = null; 353 mOffloadHalCallback = null; 354 } 355 356 /** Get Tx/Rx usage from last query. */ getForwardedStats(String upstream)357 public ForwardedStats getForwardedStats(String upstream) { 358 return mIOffload.getForwardedStats(upstream); 359 } 360 361 /** Set local prefixes to offload management process. */ setLocalPrefixes(ArrayList<String> localPrefixes)362 public boolean setLocalPrefixes(ArrayList<String> localPrefixes) { 363 return mIOffload.setLocalPrefixes(localPrefixes); 364 } 365 366 /** Set data limit value to offload management process. */ setDataLimit(String iface, long limit)367 public boolean setDataLimit(String iface, long limit) { 368 return mIOffload.setDataLimit(iface, limit); 369 } 370 371 /** Set data warning and limit value to offload management process. */ setDataWarningAndLimit(String iface, long warning, long limit)372 public boolean setDataWarningAndLimit(String iface, long warning, long limit) { 373 if (mIOffload.getVersion() < OFFLOAD_HAL_VERSION_HIDL_1_1) { 374 throw new UnsupportedOperationException( 375 "setDataWarningAndLimit is not supported below HAL V1.1"); 376 } 377 return mIOffload.setDataWarningAndLimit(iface, warning, limit); 378 } 379 380 /** Set upstream parameters to offload management process. */ setUpstreamParameters( String iface, String v4addr, String v4gateway, ArrayList<String> v6gws)381 public boolean setUpstreamParameters( 382 String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) { 383 iface = (iface != null) ? iface : NO_INTERFACE_NAME; 384 v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS; 385 v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY; 386 v6gws = (v6gws != null) ? v6gws : new ArrayList<>(); 387 return mIOffload.setUpstreamParameters(iface, v4addr, v4gateway, v6gws); 388 } 389 390 /** Add downstream prefix to offload management process. */ addDownstream(String ifname, String prefix)391 public boolean addDownstream(String ifname, String prefix) { 392 return mIOffload.addDownstream(ifname, prefix); 393 } 394 395 /** Remove downstream prefix from offload management process. */ removeDownstream(String ifname, String prefix)396 public boolean removeDownstream(String ifname, String prefix) { 397 return mIOffload.removeDownstream(ifname, prefix); 398 } 399 } 400