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 android.net.NetworkStats.SET_DEFAULT;
20import static android.net.NetworkStats.STATS_PER_UID;
21import static android.net.NetworkStats.TAG_NONE;
22import static android.net.NetworkStats.UID_ALL;
23import static android.net.TrafficStats.UID_TETHERING;
24import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
25
26import android.content.ContentResolver;
27import android.net.ITetheringStatsProvider;
28import android.net.IpPrefix;
29import android.net.LinkAddress;
30import android.net.LinkProperties;
31import android.net.NetworkStats;
32import android.net.RouteInfo;
33import android.net.netlink.ConntrackMessage;
34import android.net.netlink.NetlinkConstants;
35import android.net.netlink.NetlinkSocket;
36import android.net.util.IpUtils;
37import android.net.util.SharedLog;
38import android.os.Handler;
39import android.os.Looper;
40import android.os.INetworkManagementService;
41import android.os.RemoteException;
42import android.os.SystemClock;
43import android.provider.Settings;
44import android.system.ErrnoException;
45import android.system.OsConstants;
46import android.text.TextUtils;
47
48import com.android.internal.util.IndentingPrintWriter;
49import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
50
51import java.net.Inet4Address;
52import java.net.Inet6Address;
53import java.net.InetAddress;
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.HashMap;
57import java.util.HashSet;
58import java.util.List;
59import java.util.Map;
60import java.util.Objects;
61import java.util.Set;
62import java.util.concurrent.ConcurrentHashMap;
63import java.util.concurrent.TimeUnit;
64
65/**
66 * A class to encapsulate the business logic of programming the tethering
67 * hardware offload interface.
68 *
69 * @hide
70 */
71public class OffloadController {
72    private static final String TAG = OffloadController.class.getSimpleName();
73    private static final boolean DBG = false;
74    private static final String ANYIP = "0.0.0.0";
75    private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
76
77    private static enum UpdateType { IF_NEEDED, FORCE };
78
79    private final Handler mHandler;
80    private final OffloadHardwareInterface mHwInterface;
81    private final ContentResolver mContentResolver;
82    private final INetworkManagementService mNms;
83    private final ITetheringStatsProvider mStatsProvider;
84    private final SharedLog mLog;
85    private final HashMap<String, LinkProperties> mDownstreams;
86    private boolean mConfigInitialized;
87    private boolean mControlInitialized;
88    private LinkProperties mUpstreamLinkProperties;
89    // The complete set of offload-exempt prefixes passed in via Tethering from
90    // all upstream and downstream sources.
91    private Set<IpPrefix> mExemptPrefixes;
92    // A strictly "smaller" set of prefixes, wherein offload-approved prefixes
93    // (e.g. downstream on-link prefixes) have been removed and replaced with
94    // prefixes representing only the locally-assigned IP addresses.
95    private Set<String> mLastLocalPrefixStrs;
96
97    // Maps upstream interface names to offloaded traffic statistics.
98    // Always contains the latest value received from the hardware for each interface, regardless of
99    // whether offload is currently running on that interface.
100    private ConcurrentHashMap<String, ForwardedStats> mForwardedStats =
101            new ConcurrentHashMap<>(16, 0.75F, 1);
102
103    // Maps upstream interface names to interface quotas.
104    // Always contains the latest value received from the framework for each interface, regardless
105    // of whether offload is currently running (or is even supported) on that interface. Only
106    // includes upstream interfaces that have a quota set.
107    private HashMap<String, Long> mInterfaceQuotas = new HashMap<>();
108
109    private int mNatUpdateCallbacksReceived;
110    private int mNatUpdateNetlinkErrors;
111
112    public OffloadController(Handler h, OffloadHardwareInterface hwi,
113            ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
114        mHandler = h;
115        mHwInterface = hwi;
116        mContentResolver = contentResolver;
117        mNms = nms;
118        mStatsProvider = new OffloadTetheringStatsProvider();
119        mLog = log.forSubComponent(TAG);
120        mDownstreams = new HashMap<>();
121        mExemptPrefixes = new HashSet<>();
122        mLastLocalPrefixStrs = new HashSet<>();
123
124        try {
125            mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
126        } catch (RemoteException e) {
127            mLog.e("Cannot register offload stats provider: " + e);
128        }
129    }
130
131    public boolean start() {
132        if (started()) return true;
133
134        if (isOffloadDisabled()) {
135            mLog.i("tethering offload disabled");
136            return false;
137        }
138
139        if (!mConfigInitialized) {
140            mConfigInitialized = mHwInterface.initOffloadConfig();
141            if (!mConfigInitialized) {
142                mLog.i("tethering offload config not supported");
143                stop();
144                return false;
145            }
146        }
147
148        mControlInitialized = mHwInterface.initOffloadControl(
149                // OffloadHardwareInterface guarantees that these callback
150                // methods are called on the handler passed to it, which is the
151                // same as mHandler, as coordinated by the setup in Tethering.
152                new OffloadHardwareInterface.ControlCallback() {
153                    @Override
154                    public void onStarted() {
155                        if (!started()) return;
156                        mLog.log("onStarted");
157                    }
158
159                    @Override
160                    public void onStoppedError() {
161                        if (!started()) return;
162                        mLog.log("onStoppedError");
163                    }
164
165                    @Override
166                    public void onStoppedUnsupported() {
167                        if (!started()) return;
168                        mLog.log("onStoppedUnsupported");
169                        // Poll for statistics and trigger a sweep of tethering
170                        // stats by observers. This might not succeed, but it's
171                        // worth trying anyway. We need to do this because from
172                        // this point on we continue with software forwarding,
173                        // and we need to synchronize stats and limits between
174                        // software and hardware forwarding.
175                        updateStatsForAllUpstreams();
176                        forceTetherStatsPoll();
177                    }
178
179                    @Override
180                    public void onSupportAvailable() {
181                        if (!started()) return;
182                        mLog.log("onSupportAvailable");
183
184                        // [1] Poll for statistics and trigger a sweep of stats
185                        // by observers. We need to do this to ensure that any
186                        // limits set take into account any software tethering
187                        // traffic that has been happening in the meantime.
188                        updateStatsForAllUpstreams();
189                        forceTetherStatsPoll();
190                        // [2] (Re)Push all state.
191                        computeAndPushLocalPrefixes(UpdateType.FORCE);
192                        pushAllDownstreamState();
193                        pushUpstreamParameters(null);
194                    }
195
196                    @Override
197                    public void onStoppedLimitReached() {
198                        if (!started()) return;
199                        mLog.log("onStoppedLimitReached");
200
201                        // We cannot reliably determine on which interface the limit was reached,
202                        // because the HAL interface does not specify it. We cannot just use the
203                        // current upstream, because that might have changed since the time that
204                        // the HAL queued the callback.
205                        // TODO: rev the HAL so that it provides an interface name.
206
207                        // Fetch current stats, so that when our notification reaches
208                        // NetworkStatsService and triggers a poll, we will respond with
209                        // current data (which will be above the limit that was reached).
210                        // Note that if we just changed upstream, this is unnecessary but harmless.
211                        // The stats for the previous upstream were already updated on this thread
212                        // just after the upstream was changed, so they are also up-to-date.
213                        updateStatsForCurrentUpstream();
214                        forceTetherStatsPoll();
215                    }
216
217                    @Override
218                    public void onNatTimeoutUpdate(int proto,
219                                                   String srcAddr, int srcPort,
220                                                   String dstAddr, int dstPort) {
221                        if (!started()) return;
222                        updateNatTimeout(proto, srcAddr, srcPort, dstAddr, dstPort);
223                    }
224                });
225
226        final boolean isStarted = started();
227        if (!isStarted) {
228            mLog.i("tethering offload control not supported");
229            stop();
230        } else {
231            mLog.log("tethering offload started");
232            mNatUpdateCallbacksReceived = 0;
233            mNatUpdateNetlinkErrors = 0;
234        }
235        return isStarted;
236    }
237
238    public void stop() {
239        // Completely stops tethering offload. After this method is called, it is no longer safe to
240        // call any HAL method, no callbacks from the hardware will be delivered, and any in-flight
241        // callbacks must be ignored. Offload may be started again by calling start().
242        final boolean wasStarted = started();
243        updateStatsForCurrentUpstream();
244        mUpstreamLinkProperties = null;
245        mHwInterface.stopOffloadControl();
246        mControlInitialized = false;
247        mConfigInitialized = false;
248        if (wasStarted) mLog.log("tethering offload stopped");
249    }
250
251    private boolean started() {
252        return mConfigInitialized && mControlInitialized;
253    }
254
255    private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
256        @Override
257        public NetworkStats getTetherStats(int how) {
258            // getTetherStats() is the only function in OffloadController that can be called from
259            // a different thread. Do not attempt to update stats by querying the offload HAL
260            // synchronously from a different thread than our Handler thread. http://b/64771555.
261            Runnable updateStats = () -> { updateStatsForCurrentUpstream(); };
262            if (Looper.myLooper() == mHandler.getLooper()) {
263                updateStats.run();
264            } else {
265                mHandler.post(updateStats);
266            }
267
268            NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
269            NetworkStats.Entry entry = new NetworkStats.Entry();
270            entry.set = SET_DEFAULT;
271            entry.tag = TAG_NONE;
272            entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
273
274            for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
275                ForwardedStats value = kv.getValue();
276                entry.iface = kv.getKey();
277                entry.rxBytes = value.rxBytes;
278                entry.txBytes = value.txBytes;
279                stats.addValues(entry);
280            }
281
282            return stats;
283        }
284
285        @Override
286        public void setInterfaceQuota(String iface, long quotaBytes) {
287            mHandler.post(() -> {
288                if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
289                    mInterfaceQuotas.remove(iface);
290                } else {
291                    mInterfaceQuotas.put(iface, quotaBytes);
292                }
293                maybeUpdateDataLimit(iface);
294            });
295        }
296    }
297
298    private String currentUpstreamInterface() {
299        return (mUpstreamLinkProperties != null)
300                ? mUpstreamLinkProperties.getInterfaceName() : null;
301    }
302
303    private void maybeUpdateStats(String iface) {
304        if (TextUtils.isEmpty(iface)) {
305            return;
306        }
307
308        // Always called on the handler thread.
309        //
310        // Use get()/put() instead of updating ForwardedStats in place because we can be called
311        // concurrently with getTetherStats. In combination with the guarantees provided by
312        // ConcurrentHashMap, this ensures that getTetherStats always gets the most recent copy of
313        // the stats for each interface, and does not observe partial writes where rxBytes is
314        // updated and txBytes is not.
315        ForwardedStats diff = mHwInterface.getForwardedStats(iface);
316        ForwardedStats base = mForwardedStats.get(iface);
317        if (base != null) {
318            diff.add(base);
319        }
320        mForwardedStats.put(iface, diff);
321        // diff is a new object, just created by getForwardedStats(). Therefore, anyone reading from
322        // mForwardedStats (i.e., any caller of getTetherStats) will see the new stats immediately.
323    }
324
325    private boolean maybeUpdateDataLimit(String iface) {
326        // setDataLimit may only be called while offload is occurring on this upstream.
327        if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
328            return true;
329        }
330
331        Long limit = mInterfaceQuotas.get(iface);
332        if (limit == null) {
333            limit = Long.MAX_VALUE;
334        }
335
336        return mHwInterface.setDataLimit(iface, limit);
337    }
338
339    private void updateStatsForCurrentUpstream() {
340        maybeUpdateStats(currentUpstreamInterface());
341    }
342
343    private void updateStatsForAllUpstreams() {
344        // In practice, there should only ever be a single digit number of
345        // upstream interfaces over the lifetime of an active tethering session.
346        // Roughly speaking, imagine a very ambitious one or two of each of the
347        // following interface types: [ "rmnet_data", "wlan", "eth", "rndis" ].
348        for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
349            maybeUpdateStats(kv.getKey());
350        }
351    }
352
353    private void forceTetherStatsPoll() {
354        try {
355            mNms.tetherLimitReached(mStatsProvider);
356        } catch (RemoteException e) {
357            mLog.e("Cannot report data limit reached: " + e);
358        }
359    }
360
361    public void setUpstreamLinkProperties(LinkProperties lp) {
362        if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
363
364        final String prevUpstream = currentUpstreamInterface();
365
366        mUpstreamLinkProperties = (lp != null) ? new LinkProperties(lp) : null;
367        // Make sure we record this interface in the ForwardedStats map.
368        final String iface = currentUpstreamInterface();
369        if (!TextUtils.isEmpty(iface)) mForwardedStats.putIfAbsent(iface, EMPTY_STATS);
370
371        // TODO: examine return code and decide what to do if programming
372        // upstream parameters fails (probably just wait for a subsequent
373        // onOffloadEvent() callback to tell us offload is available again and
374        // then reapply all state).
375        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
376        pushUpstreamParameters(prevUpstream);
377    }
378
379    public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
380        mExemptPrefixes = localPrefixes;
381
382        if (!started()) return;
383        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
384    }
385
386    public void notifyDownstreamLinkProperties(LinkProperties lp) {
387        final String ifname = lp.getInterfaceName();
388        final LinkProperties oldLp = mDownstreams.put(ifname, new LinkProperties(lp));
389        if (Objects.equals(oldLp, lp)) return;
390
391        if (!started()) return;
392        pushDownstreamState(oldLp, lp);
393    }
394
395    private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
396        final String ifname = newLp.getInterfaceName();
397        final List<RouteInfo> oldRoutes =
398                (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
399        final List<RouteInfo> newRoutes = newLp.getRoutes();
400
401        // For each old route, if not in new routes: remove.
402        for (RouteInfo ri : oldRoutes) {
403            if (shouldIgnoreDownstreamRoute(ri)) continue;
404            if (!newRoutes.contains(ri)) {
405                mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
406            }
407        }
408
409        // For each new route, if not in old routes: add.
410        for (RouteInfo ri : newRoutes) {
411            if (shouldIgnoreDownstreamRoute(ri)) continue;
412            if (!oldRoutes.contains(ri)) {
413                mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
414            }
415        }
416    }
417
418    private void pushAllDownstreamState() {
419        for (LinkProperties lp : mDownstreams.values()) {
420            pushDownstreamState(null, lp);
421        }
422    }
423
424    public void removeDownstreamInterface(String ifname) {
425        final LinkProperties lp = mDownstreams.remove(ifname);
426        if (lp == null) return;
427
428        if (!started()) return;
429
430        for (RouteInfo route : lp.getRoutes()) {
431            if (shouldIgnoreDownstreamRoute(route)) continue;
432            mHwInterface.removeDownstreamPrefix(ifname, route.getDestination().toString());
433        }
434    }
435
436    private boolean isOffloadDisabled() {
437        final int defaultDisposition = mHwInterface.getDefaultTetherOffloadDisabled();
438        return (Settings.Global.getInt(
439                mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
440    }
441
442    private boolean pushUpstreamParameters(String prevUpstream) {
443        final String iface = currentUpstreamInterface();
444
445        if (TextUtils.isEmpty(iface)) {
446            final boolean rval = mHwInterface.setUpstreamParameters("", ANYIP, ANYIP, null);
447            // Update stats after we've told the hardware to stop forwarding so
448            // we don't miss packets.
449            maybeUpdateStats(prevUpstream);
450            return rval;
451        }
452
453        // A stacked interface cannot be an upstream for hardware offload.
454        // Consequently, we examine only the primary interface name, look at
455        // getAddresses() rather than getAllAddresses(), and check getRoutes()
456        // rather than getAllRoutes().
457        final ArrayList<String> v6gateways = new ArrayList<>();
458        String v4addr = null;
459        String v4gateway = null;
460
461        for (InetAddress ip : mUpstreamLinkProperties.getAddresses()) {
462            if (ip instanceof Inet4Address) {
463                v4addr = ip.getHostAddress();
464                break;
465            }
466        }
467
468        // Find the gateway addresses of all default routes of either address family.
469        for (RouteInfo ri : mUpstreamLinkProperties.getRoutes()) {
470            if (!ri.hasGateway()) continue;
471
472            final String gateway = ri.getGateway().getHostAddress();
473            if (ri.isIPv4Default()) {
474                v4gateway = gateway;
475            } else if (ri.isIPv6Default()) {
476                v6gateways.add(gateway);
477            }
478        }
479
480        boolean success = mHwInterface.setUpstreamParameters(
481                iface, v4addr, v4gateway, (v6gateways.isEmpty() ? null : v6gateways));
482
483        if (!success) {
484           return success;
485        }
486
487        // Update stats after we've told the hardware to change routing so we don't miss packets.
488        maybeUpdateStats(prevUpstream);
489
490        // Data limits can only be set once offload is running on the upstream.
491        success = maybeUpdateDataLimit(iface);
492        if (!success) {
493            // If we failed to set a data limit, don't use this upstream, because we don't want to
494            // blow through the data limit that we were told to apply.
495            mLog.log("Setting data limit for " + iface + " failed, disabling offload.");
496            stop();
497        }
498
499        return success;
500    }
501
502    private boolean computeAndPushLocalPrefixes(UpdateType how) {
503        final boolean force = (how == UpdateType.FORCE);
504        final Set<String> localPrefixStrs = computeLocalPrefixStrings(
505                mExemptPrefixes, mUpstreamLinkProperties);
506        if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
507
508        mLastLocalPrefixStrs = localPrefixStrs;
509        return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
510    }
511
512    // TODO: Factor in downstream LinkProperties once that information is available.
513    private static Set<String> computeLocalPrefixStrings(
514            Set<IpPrefix> localPrefixes, LinkProperties upstreamLinkProperties) {
515        // Create an editable copy.
516        final Set<IpPrefix> prefixSet = new HashSet<>(localPrefixes);
517
518        // TODO: If a downstream interface (not currently passed in) is reusing
519        // the /64 of the upstream (64share) then:
520        //
521        //     [a] remove that /64 from the local prefixes
522        //     [b] add in /128s for IP addresses on the downstream interface
523        //     [c] add in /128s for IP addresses on the upstream interface
524        //
525        // Until downstream information is available here, simply add /128s from
526        // the upstream network; they'll just be redundant with their /64.
527        if (upstreamLinkProperties != null) {
528            for (LinkAddress linkAddr : upstreamLinkProperties.getLinkAddresses()) {
529                if (!linkAddr.isGlobalPreferred()) continue;
530                final InetAddress ip = linkAddr.getAddress();
531                if (!(ip instanceof Inet6Address)) continue;
532                prefixSet.add(new IpPrefix(ip, 128));
533            }
534        }
535
536        final HashSet<String> localPrefixStrs = new HashSet<>();
537        for (IpPrefix pfx : prefixSet) localPrefixStrs.add(pfx.toString());
538        return localPrefixStrs;
539    }
540
541    private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
542        // Ignore any link-local routes.
543        if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
544
545        return false;
546    }
547
548    public void dump(IndentingPrintWriter pw) {
549        if (isOffloadDisabled()) {
550            pw.println("Offload disabled");
551            return;
552        }
553        final boolean isStarted = started();
554        pw.println("Offload HALs " + (isStarted ? "started" : "not started"));
555        LinkProperties lp = mUpstreamLinkProperties;
556        String upstream = (lp != null) ? lp.getInterfaceName() : null;
557        pw.println("Current upstream: " + upstream);
558        pw.println("Exempt prefixes: " + mLastLocalPrefixStrs);
559        pw.println("NAT timeout update callbacks received during the "
560                + (isStarted ? "current" : "last")
561                + " offload session: "
562                + mNatUpdateCallbacksReceived);
563        pw.println("NAT timeout update netlink errors during the "
564                + (isStarted ? "current" : "last")
565                + " offload session: "
566                + mNatUpdateNetlinkErrors);
567    }
568
569    private void updateNatTimeout(
570            int proto, String srcAddr, int srcPort, String dstAddr, int dstPort) {
571        final String protoName = protoNameFor(proto);
572        if (protoName == null) {
573            mLog.e("Unknown NAT update callback protocol: " + proto);
574            return;
575        }
576
577        final Inet4Address src = parseIPv4Address(srcAddr);
578        if (src == null) {
579            mLog.e("Failed to parse IPv4 address: " + srcAddr);
580            return;
581        }
582
583        if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
584            mLog.e("Invalid src port: " + srcPort);
585            return;
586        }
587
588        final Inet4Address dst = parseIPv4Address(dstAddr);
589        if (dst == null) {
590            mLog.e("Failed to parse IPv4 address: " + dstAddr);
591            return;
592        }
593
594        if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
595            mLog.e("Invalid dst port: " + dstPort);
596            return;
597        }
598
599        mNatUpdateCallbacksReceived++;
600        final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
601                protoName, srcAddr, srcPort, dstAddr, dstPort);
602        if (DBG) {
603            mLog.log("NAT timeout update: " + natDescription);
604        }
605
606        final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
607        final byte[] msg = ConntrackMessage.newIPv4TimeoutUpdateRequest(
608                proto, src, srcPort, dst, dstPort, timeoutSec);
609
610        try {
611            NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
612        } catch (ErrnoException e) {
613            mNatUpdateNetlinkErrors++;
614            mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
615                    + ", msg: " + NetlinkConstants.hexify(msg));
616            mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
617            mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
618        }
619    }
620
621    private static Inet4Address parseIPv4Address(String addrString) {
622        try {
623            final InetAddress ip = InetAddress.parseNumericAddress(addrString);
624            // TODO: Consider other sanitization steps here, including perhaps:
625            //           not eql to 0.0.0.0
626            //           not within 169.254.0.0/16
627            //           not within ::ffff:0.0.0.0/96
628            //           not within ::/96
629            // et cetera.
630            if (ip instanceof Inet4Address) {
631                return (Inet4Address) ip;
632            }
633        } catch (IllegalArgumentException iae) {}
634        return null;
635    }
636
637    private static String protoNameFor(int proto) {
638        // OsConstants values are not constant expressions; no switch statement.
639        if (proto == OsConstants.IPPROTO_UDP) {
640            return "UDP";
641        } else if (proto == OsConstants.IPPROTO_TCP) {
642            return "TCP";
643        }
644        return null;
645    }
646
647    private static int connectionTimeoutUpdateSecondsFor(int proto) {
648        // TODO: Replace this with more thoughtful work, perhaps reading from
649        // and maybe writing to any required
650        //
651        //     /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_*
652        //     /proc/sys/net/netfilter/nf_conntrack_udp_timeout{,_stream}
653        //
654        // entries.  TBD.
655        if (proto == OsConstants.IPPROTO_TCP) {
656            // Cf. /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established
657            return 432000;
658        } else {
659            // Cf. /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream
660            return 180;
661        }
662    }
663}
664