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