blob: 293f8eae32d5e79fdab14f35cd20f71fe0e5319e [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
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
17package com.android.networkstack.tethering;
18
19import static android.net.util.TetheringUtils.uint16;
20
21import android.hardware.tetheroffload.config.V1_0.IOffloadConfig;
22import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
23import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
24import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
25import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
26import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
27import android.net.netlink.NetlinkSocket;
28import android.net.util.SharedLog;
29import android.net.util.SocketUtils;
30import android.os.Handler;
31import android.os.NativeHandle;
32import android.os.RemoteException;
33import android.system.ErrnoException;
34import android.system.Os;
35import android.system.OsConstants;
36
37import com.android.internal.annotations.VisibleForTesting;
38
39import java.io.FileDescriptor;
40import java.io.IOException;
41import java.net.SocketAddress;
42import java.net.SocketException;
43import java.util.ArrayList;
44import java.util.NoSuchElementException;
45
46
47/**
48 * Capture tethering dependencies, for injection.
49 *
50 * @hide
51 */
52public class OffloadHardwareInterface {
53 private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
54 private static final String YIELDS = " -> ";
55 // Change this value to control whether tether offload is enabled or
56 // disabled by default in the absence of an explicit Settings value.
57 // See accompanying unittest to distinguish 0 from non-0 values.
58 private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0;
59 private static final String NO_INTERFACE_NAME = "";
60 private static final String NO_IPV4_ADDRESS = "";
61 private static final String NO_IPV4_GATEWAY = "";
62 // Reference kernel/uapi/linux/netfilter/nfnetlink_compat.h
63 private static final int NF_NETLINK_CONNTRACK_NEW = 1;
64 private static final int NF_NETLINK_CONNTRACK_UPDATE = 2;
65 private static final int NF_NETLINK_CONNTRACK_DESTROY = 4;
66
67 private final Handler mHandler;
68 private final SharedLog mLog;
69 private IOffloadControl mOffloadControl;
70 private TetheringOffloadCallback mTetheringOffloadCallback;
71 private ControlCallback mControlCallback;
72
73 /** The callback to notify status of offload management process. */
74 public static class ControlCallback {
75 /** Offload started. */
76 public void onStarted() {}
77 /**
78 * Offload stopped because an error has occurred in lower layer.
79 */
80 public void onStoppedError() {}
81 /**
82 * Offload stopped because the device has moved to a bearer on which hardware offload is
83 * not supported. Subsequent calls to setUpstreamParameters and add/removeDownstream will
84 * likely fail and cannot be presumed to be saved inside of the hardware management process.
85 * Upon receiving #onSupportAvailable(), the caller should reprogram the hardware to begin
86 * offload again.
87 */
88 public void onStoppedUnsupported() {}
89 /** Indicate that offload is able to proivde support for this time. */
90 public void onSupportAvailable() {}
91 /** Offload stopped because of usage limit reached. */
92 public void onStoppedLimitReached() {}
93
94 /** Indicate to update NAT timeout. */
95 public void onNatTimeoutUpdate(int proto,
96 String srcAddr, int srcPort,
97 String dstAddr, int dstPort) {}
98 }
99
100 /** The object which records Tx/Rx forwarded bytes. */
101 public static class ForwardedStats {
102 public long rxBytes;
103 public long txBytes;
104
105 public ForwardedStats() {
106 rxBytes = 0;
107 txBytes = 0;
108 }
109
110 @VisibleForTesting
111 public ForwardedStats(long rxBytes, long txBytes) {
112 this.rxBytes = rxBytes;
113 this.txBytes = txBytes;
114 }
115
116 /** Add Tx/Rx bytes. */
117 public void add(ForwardedStats other) {
118 rxBytes += other.rxBytes;
119 txBytes += other.txBytes;
120 }
121
122 /** Returns the string representation of this object. */
123 public String toString() {
124 return String.format("rx:%s tx:%s", rxBytes, txBytes);
125 }
126 }
127
128 public OffloadHardwareInterface(Handler h, SharedLog log) {
129 mHandler = h;
130 mLog = log.forSubComponent(TAG);
131 }
132
133 /** Get default value indicating whether offload is supported. */
134 public int getDefaultTetherOffloadDisabled() {
135 return DEFAULT_TETHER_OFFLOAD_DISABLED;
136 }
137
138 /**
139 * Offload management process need to know conntrack rules to support NAT, but it may not have
140 * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
141 * share them with offload management process.
142 */
143 public boolean initOffloadConfig() {
144 IOffloadConfig offloadConfig;
145 try {
146 offloadConfig = IOffloadConfig.getService(true /*retry*/);
147 } catch (RemoteException | NoSuchElementException e) {
148 mLog.e("getIOffloadConfig error " + e);
149 return false;
150 }
151 if (offloadConfig == null) {
152 mLog.e("Could not find IOffloadConfig service");
153 return false;
154 }
155 // Per the IConfigOffload definition:
156 //
157 // h1 provides a file descriptor bound to the following netlink groups
158 // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
159 //
160 // h2 provides a file descriptor bound to the following netlink groups
161 // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
162 final NativeHandle h1 = createConntrackSocket(
163 NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY);
164 if (h1 == null) return false;
165
166 final NativeHandle h2 = createConntrackSocket(
167 NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY);
168 if (h2 == null) {
169 closeFdInNativeHandle(h1);
170 return false;
171 }
172
173 final CbResults results = new CbResults();
174 try {
175 offloadConfig.setHandles(h1, h2,
176 (boolean success, String errMsg) -> {
177 results.mSuccess = success;
178 results.mErrMsg = errMsg;
179 });
180 } catch (RemoteException e) {
181 record("initOffloadConfig, setHandles fail", e);
182 return false;
183 }
184 // Explicitly close FDs.
185 closeFdInNativeHandle(h1);
186 closeFdInNativeHandle(h2);
187
188 record("initOffloadConfig, setHandles results:", results);
189 return results.mSuccess;
190 }
191
192 private void closeFdInNativeHandle(final NativeHandle h) {
193 try {
194 h.close();
195 } catch (IOException | IllegalStateException e) {
196 // IllegalStateException means fd is already closed, do nothing here.
197 // Also nothing we can do if IOException.
198 }
199 }
200
201 private NativeHandle createConntrackSocket(final int groups) {
202 FileDescriptor fd;
203 try {
204 fd = NetlinkSocket.forProto(OsConstants.NETLINK_NETFILTER);
205 } catch (ErrnoException e) {
206 mLog.e("Unable to create conntrack socket " + e);
207 return null;
208 }
209
210 final SocketAddress sockAddr = SocketUtils.makeNetlinkSocketAddress(0, groups);
211 try {
212 Os.bind(fd, sockAddr);
213 } catch (ErrnoException | SocketException e) {
214 mLog.e("Unable to bind conntrack socket for groups " + groups + " error: " + e);
215 try {
216 SocketUtils.closeSocket(fd);
217 } catch (IOException ie) {
218 // Nothing we can do here
219 }
220 return null;
221 }
222 try {
223 Os.connect(fd, sockAddr);
224 } catch (ErrnoException | SocketException e) {
225 mLog.e("connect to kernel fail for groups " + groups + " error: " + e);
226 try {
227 SocketUtils.closeSocket(fd);
228 } catch (IOException ie) {
229 // Nothing we can do here
230 }
231 return null;
232 }
233
234 return new NativeHandle(fd, true);
235 }
236
237 /** Initialize the tethering offload HAL. */
238 public boolean initOffloadControl(ControlCallback controlCb) {
239 mControlCallback = controlCb;
240
241 if (mOffloadControl == null) {
242 try {
243 mOffloadControl = IOffloadControl.getService(true /*retry*/);
244 } catch (RemoteException | NoSuchElementException e) {
245 mLog.e("tethering offload control not supported: " + e);
246 return false;
247 }
248 if (mOffloadControl == null) {
249 mLog.e("tethering IOffloadControl.getService() returned null");
250 return false;
251 }
252 }
253
254 final String logmsg = String.format("initOffloadControl(%s)",
255 (controlCb == null) ? "null"
256 : "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
257
258 mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog);
259 final CbResults results = new CbResults();
260 try {
261 mOffloadControl.initOffload(
262 mTetheringOffloadCallback,
263 (boolean success, String errMsg) -> {
264 results.mSuccess = success;
265 results.mErrMsg = errMsg;
266 });
267 } catch (RemoteException e) {
268 record(logmsg, e);
269 return false;
270 }
271
272 record(logmsg, results);
273 return results.mSuccess;
274 }
275
276 /** Stop IOffloadControl. */
277 public void stopOffloadControl() {
278 if (mOffloadControl != null) {
279 try {
280 mOffloadControl.stopOffload(
281 (boolean success, String errMsg) -> {
282 if (!success) mLog.e("stopOffload failed: " + errMsg);
283 });
284 } catch (RemoteException e) {
285 mLog.e("failed to stopOffload: " + e);
286 }
287 }
288 mOffloadControl = null;
289 mTetheringOffloadCallback = null;
290 mControlCallback = null;
291 mLog.log("stopOffloadControl()");
292 }
293
294 /** Get Tx/Rx usage from last query. */
295 public ForwardedStats getForwardedStats(String upstream) {
296 final String logmsg = String.format("getForwardedStats(%s)", upstream);
297
298 final ForwardedStats stats = new ForwardedStats();
299 try {
300 mOffloadControl.getForwardedStats(
301 upstream,
302 (long rxBytes, long txBytes) -> {
303 stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
304 stats.txBytes = (txBytes > 0) ? txBytes : 0;
305 });
306 } catch (RemoteException e) {
307 record(logmsg, e);
308 return stats;
309 }
310
311 return stats;
312 }
313
314 /** Set local prefixes to offload management process. */
315 public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
316 final String logmsg = String.format("setLocalPrefixes([%s])",
317 String.join(",", localPrefixes));
318
319 final CbResults results = new CbResults();
320 try {
321 mOffloadControl.setLocalPrefixes(localPrefixes,
322 (boolean success, String errMsg) -> {
323 results.mSuccess = success;
324 results.mErrMsg = errMsg;
325 });
326 } catch (RemoteException e) {
327 record(logmsg, e);
328 return false;
329 }
330
331 record(logmsg, results);
332 return results.mSuccess;
333 }
334
335 /** Set data limit value to offload management process. */
336 public boolean setDataLimit(String iface, long limit) {
337
338 final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
339
340 final CbResults results = new CbResults();
341 try {
342 mOffloadControl.setDataLimit(
343 iface, limit,
344 (boolean success, String errMsg) -> {
345 results.mSuccess = success;
346 results.mErrMsg = errMsg;
347 });
348 } catch (RemoteException e) {
349 record(logmsg, e);
350 return false;
351 }
352
353 record(logmsg, results);
354 return results.mSuccess;
355 }
356
357 /** Set upstream parameters to offload management process. */
358 public boolean setUpstreamParameters(
359 String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
360 iface = (iface != null) ? iface : NO_INTERFACE_NAME;
361 v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
362 v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
363 v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
364
365 final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
366 iface, v4addr, v4gateway, String.join(",", v6gws));
367
368 final CbResults results = new CbResults();
369 try {
370 mOffloadControl.setUpstreamParameters(
371 iface, v4addr, v4gateway, v6gws,
372 (boolean success, String errMsg) -> {
373 results.mSuccess = success;
374 results.mErrMsg = errMsg;
375 });
376 } catch (RemoteException e) {
377 record(logmsg, e);
378 return false;
379 }
380
381 record(logmsg, results);
382 return results.mSuccess;
383 }
384
385 /** Add downstream prefix to offload management process. */
386 public boolean addDownstreamPrefix(String ifname, String prefix) {
387 final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
388
389 final CbResults results = new CbResults();
390 try {
391 mOffloadControl.addDownstream(ifname, prefix,
392 (boolean success, String errMsg) -> {
393 results.mSuccess = success;
394 results.mErrMsg = errMsg;
395 });
396 } catch (RemoteException e) {
397 record(logmsg, e);
398 return false;
399 }
400
401 record(logmsg, results);
402 return results.mSuccess;
403 }
404
405 /** Remove downstream prefix from offload management process. */
406 public boolean removeDownstreamPrefix(String ifname, String prefix) {
407 final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
408
409 final CbResults results = new CbResults();
410 try {
411 mOffloadControl.removeDownstream(ifname, prefix,
412 (boolean success, String errMsg) -> {
413 results.mSuccess = success;
414 results.mErrMsg = errMsg;
415 });
416 } catch (RemoteException e) {
417 record(logmsg, e);
418 return false;
419 }
420
421 record(logmsg, results);
422 return results.mSuccess;
423 }
424
425 private void record(String msg, Throwable t) {
426 mLog.e(msg + YIELDS + "exception: " + t);
427 }
428
429 private void record(String msg, CbResults results) {
430 final String logmsg = msg + YIELDS + results;
431 if (!results.mSuccess) {
432 mLog.e(logmsg);
433 } else {
434 mLog.log(logmsg);
435 }
436 }
437
438 private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
439 public final Handler handler;
440 public final ControlCallback controlCb;
441 public final SharedLog log;
442
443 TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) {
444 handler = h;
445 controlCb = cb;
446 log = sharedLog;
447 }
448
449 @Override
450 public void onEvent(int event) {
451 handler.post(() -> {
452 switch (event) {
453 case OffloadCallbackEvent.OFFLOAD_STARTED:
454 controlCb.onStarted();
455 break;
456 case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
457 controlCb.onStoppedError();
458 break;
459 case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
460 controlCb.onStoppedUnsupported();
461 break;
462 case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
463 controlCb.onSupportAvailable();
464 break;
465 case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
466 controlCb.onStoppedLimitReached();
467 break;
468 default:
469 log.e("Unsupported OffloadCallbackEvent: " + event);
470 }
471 });
472 }
473
474 @Override
475 public void updateTimeout(NatTimeoutUpdate params) {
476 handler.post(() -> {
477 controlCb.onNatTimeoutUpdate(
478 networkProtocolToOsConstant(params.proto),
479 params.src.addr, uint16(params.src.port),
480 params.dst.addr, uint16(params.dst.port));
481 });
482 }
483 }
484
485 private static int networkProtocolToOsConstant(int proto) {
486 switch (proto) {
487 case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
488 case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
489 default:
490 // The caller checks this value and will log an error. Just make
491 // sure it won't collide with valid OsContants.IPPROTO_* values.
492 return -Math.abs(proto);
493 }
494 }
495
496 private static class CbResults {
497 boolean mSuccess;
498 String mErrMsg;
499
500 @Override
501 public String toString() {
502 if (mSuccess) {
503 return "ok";
504 } else {
505 return "fail: " + mErrMsg;
506 }
507 }
508 }
509}