• 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.server.connectivity.tethering;
18 
19 import static android.net.NetworkStats.SET_DEFAULT;
20 import static android.net.NetworkStats.STATS_PER_UID;
21 import static android.net.NetworkStats.TAG_NONE;
22 import static android.net.NetworkStats.UID_ALL;
23 import static android.net.TrafficStats.UID_TETHERING;
24 import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
25 
26 import android.content.ContentResolver;
27 import android.net.ITetheringStatsProvider;
28 import android.net.IpPrefix;
29 import android.net.LinkAddress;
30 import android.net.LinkProperties;
31 import android.net.NetworkStats;
32 import android.net.RouteInfo;
33 import android.net.netlink.ConntrackMessage;
34 import android.net.netlink.NetlinkConstants;
35 import android.net.netlink.NetlinkSocket;
36 import android.net.util.IpUtils;
37 import android.net.util.SharedLog;
38 import android.os.Handler;
39 import android.os.Looper;
40 import android.os.INetworkManagementService;
41 import android.os.RemoteException;
42 import android.os.SystemClock;
43 import android.provider.Settings;
44 import android.system.ErrnoException;
45 import android.system.OsConstants;
46 import android.text.TextUtils;
47 
48 import com.android.internal.util.IndentingPrintWriter;
49 import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
50 
51 import java.net.Inet4Address;
52 import java.net.Inet6Address;
53 import java.net.InetAddress;
54 import java.util.ArrayList;
55 import java.util.Collections;
56 import java.util.HashMap;
57 import java.util.HashSet;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Objects;
61 import java.util.Set;
62 import java.util.concurrent.ConcurrentHashMap;
63 import 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  */
71 public 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 
OffloadController(Handler h, OffloadHardwareInterface hwi, ContentResolver contentResolver, INetworkManagementService nms, SharedLog log)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 
start()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 
stop()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 
started()251     private boolean started() {
252         return mConfigInitialized && mControlInitialized;
253     }
254 
255     private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
256         @Override
getTetherStats(int how)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
setInterfaceQuota(String iface, long quotaBytes)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 
currentUpstreamInterface()298     private String currentUpstreamInterface() {
299         return (mUpstreamLinkProperties != null)
300                 ? mUpstreamLinkProperties.getInterfaceName() : null;
301     }
302 
maybeUpdateStats(String iface)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 
maybeUpdateDataLimit(String iface)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 
updateStatsForCurrentUpstream()339     private void updateStatsForCurrentUpstream() {
340         maybeUpdateStats(currentUpstreamInterface());
341     }
342 
updateStatsForAllUpstreams()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 
forceTetherStatsPoll()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 
setUpstreamLinkProperties(LinkProperties lp)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 
setLocalPrefixes(Set<IpPrefix> localPrefixes)379     public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
380         mExemptPrefixes = localPrefixes;
381 
382         if (!started()) return;
383         computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
384     }
385 
notifyDownstreamLinkProperties(LinkProperties lp)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 
pushDownstreamState(LinkProperties oldLp, LinkProperties newLp)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 
pushAllDownstreamState()418     private void pushAllDownstreamState() {
419         for (LinkProperties lp : mDownstreams.values()) {
420             pushDownstreamState(null, lp);
421         }
422     }
423 
removeDownstreamInterface(String ifname)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 
isOffloadDisabled()436     private boolean isOffloadDisabled() {
437         final int defaultDisposition = mHwInterface.getDefaultTetherOffloadDisabled();
438         return (Settings.Global.getInt(
439                 mContentResolver, TETHER_OFFLOAD_DISABLED, defaultDisposition) != 0);
440     }
441 
pushUpstreamParameters(String prevUpstream)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 
computeAndPushLocalPrefixes(UpdateType how)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.
computeLocalPrefixStrings( Set<IpPrefix> localPrefixes, LinkProperties upstreamLinkProperties)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 
shouldIgnoreDownstreamRoute(RouteInfo route)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 
dump(IndentingPrintWriter pw)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 
updateNatTimeout( int proto, String srcAddr, int srcPort, String dstAddr, int dstPort)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 
parseIPv4Address(String addrString)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 
protoNameFor(int proto)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 
connectionTimeoutUpdateSecondsFor(int proto)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