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
17package com.android.server.connectivity.tethering;
18
19import static com.android.internal.util.BitUtils.uint16;
20
21import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
22import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
23import android.hardware.tetheroffload.control.V1_0.NatTimeoutUpdate;
24import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
25import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
26import android.os.Handler;
27import android.os.RemoteException;
28import android.net.util.SharedLog;
29import android.system.OsConstants;
30
31import java.util.ArrayList;
32
33
34/**
35 * Capture tethering dependencies, for injection.
36 *
37 * @hide
38 */
39public class OffloadHardwareInterface {
40    private static final String TAG = OffloadHardwareInterface.class.getSimpleName();
41    private static final String YIELDS = " -> ";
42    // Change this value to control whether tether offload is enabled or
43    // disabled by default in the absence of an explicit Settings value.
44    // See accompanying unittest to distinguish 0 from non-0 values.
45    private static final int DEFAULT_TETHER_OFFLOAD_DISABLED = 0;
46    private static final String NO_INTERFACE_NAME = "";
47    private static final String NO_IPV4_ADDRESS = "";
48    private static final String NO_IPV4_GATEWAY = "";
49
50    private static native boolean configOffload();
51
52    private final Handler mHandler;
53    private final SharedLog mLog;
54    private IOffloadControl mOffloadControl;
55    private TetheringOffloadCallback mTetheringOffloadCallback;
56    private ControlCallback mControlCallback;
57
58    public static class ControlCallback {
59        public void onStarted() {}
60        public void onStoppedError() {}
61        public void onStoppedUnsupported() {}
62        public void onSupportAvailable() {}
63        public void onStoppedLimitReached() {}
64
65        public void onNatTimeoutUpdate(int proto,
66                                       String srcAddr, int srcPort,
67                                       String dstAddr, int dstPort) {}
68    }
69
70    public static class ForwardedStats {
71        public long rxBytes;
72        public long txBytes;
73
74        public ForwardedStats() {
75            rxBytes = 0;
76            txBytes = 0;
77        }
78
79        public void add(ForwardedStats other) {
80            rxBytes += other.rxBytes;
81            txBytes += other.txBytes;
82        }
83
84        public String toString() {
85            return String.format("rx:%s tx:%s", rxBytes, txBytes);
86        }
87    }
88
89    public OffloadHardwareInterface(Handler h, SharedLog log) {
90        mHandler = h;
91        mLog = log.forSubComponent(TAG);
92    }
93
94    public int getDefaultTetherOffloadDisabled() {
95        return DEFAULT_TETHER_OFFLOAD_DISABLED;
96    }
97
98    public boolean initOffloadConfig() {
99        return configOffload();
100    }
101
102    public boolean initOffloadControl(ControlCallback controlCb) {
103        mControlCallback = controlCb;
104
105        if (mOffloadControl == null) {
106            try {
107                mOffloadControl = IOffloadControl.getService();
108            } catch (RemoteException e) {
109                mLog.e("tethering offload control not supported: " + e);
110                return false;
111            }
112            if (mOffloadControl == null) {
113                mLog.e("tethering IOffloadControl.getService() returned null");
114                return false;
115            }
116        }
117
118        final String logmsg = String.format("initOffloadControl(%s)",
119                (controlCb == null) ? "null"
120                        : "0x" + Integer.toHexString(System.identityHashCode(controlCb)));
121
122        mTetheringOffloadCallback = new TetheringOffloadCallback(mHandler, mControlCallback, mLog);
123        final CbResults results = new CbResults();
124        try {
125            mOffloadControl.initOffload(
126                    mTetheringOffloadCallback,
127                    (boolean success, String errMsg) -> {
128                        results.success = success;
129                        results.errMsg = errMsg;
130                    });
131        } catch (RemoteException e) {
132            record(logmsg, e);
133            return false;
134        }
135
136        record(logmsg, results);
137        return results.success;
138    }
139
140    public void stopOffloadControl() {
141        if (mOffloadControl != null) {
142            try {
143                mOffloadControl.stopOffload(
144                        (boolean success, String errMsg) -> {
145                            if (!success) mLog.e("stopOffload failed: " + errMsg);
146                        });
147            } catch (RemoteException e) {
148                mLog.e("failed to stopOffload: " + e);
149            }
150        }
151        mOffloadControl = null;
152        mTetheringOffloadCallback = null;
153        mControlCallback = null;
154        mLog.log("stopOffloadControl()");
155    }
156
157    public ForwardedStats getForwardedStats(String upstream) {
158        final String logmsg = String.format("getForwardedStats(%s)",  upstream);
159
160        final ForwardedStats stats = new ForwardedStats();
161        try {
162            mOffloadControl.getForwardedStats(
163                    upstream,
164                    (long rxBytes, long txBytes) -> {
165                        stats.rxBytes = (rxBytes > 0) ? rxBytes : 0;
166                        stats.txBytes = (txBytes > 0) ? txBytes : 0;
167                    });
168        } catch (RemoteException e) {
169            record(logmsg, e);
170            return stats;
171        }
172
173        mLog.log(logmsg + YIELDS + stats);
174        return stats;
175    }
176
177    public boolean setLocalPrefixes(ArrayList<String> localPrefixes) {
178        final String logmsg = String.format("setLocalPrefixes([%s])",
179                String.join(",", localPrefixes));
180
181        final CbResults results = new CbResults();
182        try {
183            mOffloadControl.setLocalPrefixes(localPrefixes,
184                    (boolean success, String errMsg) -> {
185                        results.success = success;
186                        results.errMsg = errMsg;
187                    });
188        } catch (RemoteException e) {
189            record(logmsg, e);
190            return false;
191        }
192
193        record(logmsg, results);
194        return results.success;
195    }
196
197    public boolean setDataLimit(String iface, long limit) {
198
199        final String logmsg = String.format("setDataLimit(%s, %d)", iface, limit);
200
201        final CbResults results = new CbResults();
202        try {
203            mOffloadControl.setDataLimit(
204                    iface, limit,
205                    (boolean success, String errMsg) -> {
206                        results.success = success;
207                        results.errMsg = errMsg;
208                    });
209        } catch (RemoteException e) {
210            record(logmsg, e);
211            return false;
212        }
213
214        record(logmsg, results);
215        return results.success;
216    }
217
218    public boolean setUpstreamParameters(
219            String iface, String v4addr, String v4gateway, ArrayList<String> v6gws) {
220        iface = (iface != null) ? iface : NO_INTERFACE_NAME;
221        v4addr = (v4addr != null) ? v4addr : NO_IPV4_ADDRESS;
222        v4gateway = (v4gateway != null) ? v4gateway : NO_IPV4_GATEWAY;
223        v6gws = (v6gws != null) ? v6gws : new ArrayList<>();
224
225        final String logmsg = String.format("setUpstreamParameters(%s, %s, %s, [%s])",
226                iface, v4addr, v4gateway, String.join(",", v6gws));
227
228        final CbResults results = new CbResults();
229        try {
230            mOffloadControl.setUpstreamParameters(
231                    iface, v4addr, v4gateway, v6gws,
232                    (boolean success, String errMsg) -> {
233                        results.success = success;
234                        results.errMsg = errMsg;
235                    });
236        } catch (RemoteException e) {
237            record(logmsg, e);
238            return false;
239        }
240
241        record(logmsg, results);
242        return results.success;
243    }
244
245    public boolean addDownstreamPrefix(String ifname, String prefix) {
246        final String logmsg = String.format("addDownstreamPrefix(%s, %s)", ifname, prefix);
247
248        final CbResults results = new CbResults();
249        try {
250            mOffloadControl.addDownstream(ifname, prefix,
251                    (boolean success, String errMsg) -> {
252                        results.success = success;
253                        results.errMsg = errMsg;
254                    });
255        } catch (RemoteException e) {
256            record(logmsg, e);
257            return false;
258        }
259
260        record(logmsg, results);
261        return results.success;
262    }
263
264    public boolean removeDownstreamPrefix(String ifname, String prefix) {
265        final String logmsg = String.format("removeDownstreamPrefix(%s, %s)", ifname, prefix);
266
267        final CbResults results = new CbResults();
268        try {
269            mOffloadControl.removeDownstream(ifname, prefix,
270                    (boolean success, String errMsg) -> {
271                        results.success = success;
272                        results.errMsg = errMsg;
273                    });
274        } catch (RemoteException e) {
275            record(logmsg, e);
276            return false;
277        }
278
279        record(logmsg, results);
280        return results.success;
281    }
282
283    private void record(String msg, Throwable t) {
284        mLog.e(msg + YIELDS + "exception: " + t);
285    }
286
287    private void record(String msg, CbResults results) {
288        final String logmsg = msg + YIELDS + results;
289        if (!results.success) {
290            mLog.e(logmsg);
291        } else {
292            mLog.log(logmsg);
293        }
294    }
295
296    private static class TetheringOffloadCallback extends ITetheringOffloadCallback.Stub {
297        public final Handler handler;
298        public final ControlCallback controlCb;
299        public final SharedLog log;
300
301        public TetheringOffloadCallback(Handler h, ControlCallback cb, SharedLog sharedLog) {
302            handler = h;
303            controlCb = cb;
304            log = sharedLog;
305        }
306
307        @Override
308        public void onEvent(int event) {
309            handler.post(() -> {
310                switch (event) {
311                    case OffloadCallbackEvent.OFFLOAD_STARTED:
312                        controlCb.onStarted();
313                        break;
314                    case OffloadCallbackEvent.OFFLOAD_STOPPED_ERROR:
315                        controlCb.onStoppedError();
316                        break;
317                    case OffloadCallbackEvent.OFFLOAD_STOPPED_UNSUPPORTED:
318                        controlCb.onStoppedUnsupported();
319                        break;
320                    case OffloadCallbackEvent.OFFLOAD_SUPPORT_AVAILABLE:
321                        controlCb.onSupportAvailable();
322                        break;
323                    case OffloadCallbackEvent.OFFLOAD_STOPPED_LIMIT_REACHED:
324                        controlCb.onStoppedLimitReached();
325                        break;
326                    default:
327                        log.e("Unsupported OffloadCallbackEvent: " + event);
328                }
329            });
330        }
331
332        @Override
333        public void updateTimeout(NatTimeoutUpdate params) {
334            handler.post(() -> {
335                    controlCb.onNatTimeoutUpdate(
336                        networkProtocolToOsConstant(params.proto),
337                        params.src.addr, uint16(params.src.port),
338                        params.dst.addr, uint16(params.dst.port));
339            });
340        }
341    }
342
343    private static int networkProtocolToOsConstant(int proto) {
344        switch (proto) {
345            case NetworkProtocol.TCP: return OsConstants.IPPROTO_TCP;
346            case NetworkProtocol.UDP: return OsConstants.IPPROTO_UDP;
347            default:
348                // The caller checks this value and will log an error. Just make
349                // sure it won't collide with valid OsContants.IPPROTO_* values.
350                return -Math.abs(proto);
351        }
352    }
353
354    private static class CbResults {
355        boolean success;
356        String errMsg;
357
358        @Override
359        public String toString() {
360            if (success) {
361                return "ok";
362            } else {
363                return "fail: " + errMsg;
364            }
365        }
366    }
367}
368