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