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