1/*
2 * Copyright (C) 2018 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.ethernet;
18
19import android.annotation.Nullable;
20import android.content.Context;
21import android.net.IEthernetServiceListener;
22import android.net.InterfaceConfiguration;
23import android.net.IpConfiguration;
24import android.net.IpConfiguration.IpAssignment;
25import android.net.IpConfiguration.ProxySettings;
26import android.net.LinkAddress;
27import android.net.NetworkCapabilities;
28import android.net.StaticIpConfiguration;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.INetworkManagementService;
32import android.os.RemoteCallbackList;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.text.TextUtils;
36import android.util.ArrayMap;
37import android.util.Log;
38
39import com.android.internal.annotations.VisibleForTesting;
40import com.android.internal.util.IndentingPrintWriter;
41import com.android.server.net.BaseNetworkObserver;
42
43import java.io.FileDescriptor;
44import java.net.InetAddress;
45import java.util.ArrayList;
46import java.util.concurrent.ConcurrentHashMap;
47
48/**
49 * Tracks Ethernet interfaces and manages interface configurations.
50 *
51 * <p>Interfaces may have different {@link android.net.NetworkCapabilities}. This mapping is defined
52 * in {@code config_ethernet_interfaces}. Notably, some interfaces could be marked as restricted by
53 * not specifying {@link android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED} flag.
54 * Interfaces could have associated {@link android.net.IpConfiguration}.
55 * Ethernet Interfaces may be present at boot time or appear after boot (e.g., for Ethernet adapters
56 * connected over USB). This class supports multiple interfaces. When an interface appears on the
57 * system (or is present at boot time) this class will start tracking it and bring it up. Only
58 * interfaces whose names match the {@code config_ethernet_iface_regex} regular expression are
59 * tracked.
60 *
61 * <p>All public or package private methods must be thread-safe unless stated otherwise.
62 */
63final class EthernetTracker {
64    private final static String TAG = EthernetTracker.class.getSimpleName();
65    private final static boolean DBG = EthernetNetworkFactory.DBG;
66
67    /** Product-dependent regular expression of interface names we track. */
68    private final String mIfaceMatch;
69
70    /** Mapping between {iface name | mac address} -> {NetworkCapabilities} */
71    private final ConcurrentHashMap<String, NetworkCapabilities> mNetworkCapabilities =
72            new ConcurrentHashMap<>();
73    private final ConcurrentHashMap<String, IpConfiguration> mIpConfigurations =
74            new ConcurrentHashMap<>();
75
76    private final INetworkManagementService mNMService;
77    private final Handler mHandler;
78    private final EthernetNetworkFactory mFactory;
79    private final EthernetConfigStore mConfigStore;
80
81    private final RemoteCallbackList<IEthernetServiceListener> mListeners =
82            new RemoteCallbackList<>();
83
84    private volatile IpConfiguration mIpConfigForDefaultInterface;
85
86    EthernetTracker(Context context, Handler handler) {
87        mHandler = handler;
88
89        // The services we use.
90        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
91        mNMService = INetworkManagementService.Stub.asInterface(b);
92
93        // Interface match regex.
94        mIfaceMatch = context.getResources().getString(
95                com.android.internal.R.string.config_ethernet_iface_regex);
96
97        // Read default Ethernet interface configuration from resources
98        final String[] interfaceConfigs = context.getResources().getStringArray(
99                com.android.internal.R.array.config_ethernet_interfaces);
100        for (String strConfig : interfaceConfigs) {
101            parseEthernetConfig(strConfig);
102        }
103
104        mConfigStore = new EthernetConfigStore();
105
106        NetworkCapabilities nc = createNetworkCapabilities(true /* clear default capabilities */);
107        mFactory = new EthernetNetworkFactory(handler, context, nc);
108        mFactory.register();
109    }
110
111    void start() {
112        mConfigStore.read();
113
114        // Default interface is just the first one we want to track.
115        mIpConfigForDefaultInterface = mConfigStore.getIpConfigurationForDefaultInterface();
116        final ArrayMap<String, IpConfiguration> configs = mConfigStore.getIpConfigurations();
117        for (int i = 0; i < configs.size(); i++) {
118            mIpConfigurations.put(configs.keyAt(i), configs.valueAt(i));
119        }
120
121        try {
122            mNMService.registerObserver(new InterfaceObserver());
123        } catch (RemoteException e) {
124            Log.e(TAG, "Could not register InterfaceObserver " + e);
125        }
126
127        mHandler.post(this::trackAvailableInterfaces);
128    }
129
130    void updateIpConfiguration(String iface, IpConfiguration ipConfiguration) {
131        if (DBG) {
132            Log.i(TAG, "updateIpConfiguration, iface: " + iface + ", cfg: " + ipConfiguration);
133        }
134
135        mConfigStore.write(iface, ipConfiguration);
136        mIpConfigurations.put(iface, ipConfiguration);
137
138        mHandler.post(() -> mFactory.updateIpConfiguration(iface, ipConfiguration));
139    }
140
141    IpConfiguration getIpConfiguration(String iface) {
142        return mIpConfigurations.get(iface);
143    }
144
145    boolean isTrackingInterface(String iface) {
146        return mFactory.hasInterface(iface);
147    }
148
149    String[] getInterfaces(boolean includeRestricted) {
150        return mFactory.getAvailableInterfaces(includeRestricted);
151    }
152
153    /**
154     * Returns true if given interface was configured as restricted (doesn't have
155     * NET_CAPABILITY_NOT_RESTRICTED) capability. Otherwise, returns false.
156     */
157    boolean isRestrictedInterface(String iface) {
158        final NetworkCapabilities nc = mNetworkCapabilities.get(iface);
159        return nc != null && !nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
160    }
161
162    void addListener(IEthernetServiceListener listener, boolean canUseRestrictedNetworks) {
163        mListeners.register(listener, new ListenerInfo(canUseRestrictedNetworks));
164    }
165
166    void removeListener(IEthernetServiceListener listener) {
167        mListeners.unregister(listener);
168    }
169
170    private void removeInterface(String iface) {
171        mFactory.removeInterface(iface);
172    }
173
174    private void addInterface(String iface) {
175        InterfaceConfiguration config = null;
176        // Bring up the interface so we get link status indications.
177        try {
178            mNMService.setInterfaceUp(iface);
179            config = mNMService.getInterfaceConfig(iface);
180        } catch (RemoteException | IllegalStateException e) {
181            // Either the system is crashing or the interface has disappeared. Just ignore the
182            // error; we haven't modified any state because we only do that if our calls succeed.
183            Log.e(TAG, "Error upping interface " + iface, e);
184        }
185
186        if (config == null) {
187            Log.e(TAG, "Null interface config for " + iface + ". Bailing out.");
188            return;
189        }
190
191        final String hwAddress = config.getHardwareAddress();
192
193        NetworkCapabilities nc = mNetworkCapabilities.get(iface);
194        if (nc == null) {
195            // Try to resolve using mac address
196            nc = mNetworkCapabilities.get(hwAddress);
197            if (nc == null) {
198                nc = createDefaultNetworkCapabilities();
199            }
200        }
201        IpConfiguration ipConfiguration = mIpConfigurations.get(iface);
202        if (ipConfiguration == null) {
203            ipConfiguration = createDefaultIpConfiguration();
204        }
205
206        Log.d(TAG, "Started tracking interface " + iface);
207        mFactory.addInterface(iface, hwAddress, nc, ipConfiguration);
208
209        // Note: if the interface already has link (e.g., if we crashed and got
210        // restarted while it was running), we need to fake a link up notification so we
211        // start configuring it.
212        if (config.hasFlag("running")) {
213            updateInterfaceState(iface, true);
214        }
215    }
216
217    private void updateInterfaceState(String iface, boolean up) {
218        boolean modified = mFactory.updateInterfaceLinkState(iface, up);
219        if (modified) {
220            boolean restricted = isRestrictedInterface(iface);
221            int n = mListeners.beginBroadcast();
222            for (int i = 0; i < n; i++) {
223                try {
224                    if (restricted) {
225                        ListenerInfo listenerInfo = (ListenerInfo) mListeners.getBroadcastCookie(i);
226                        if (!listenerInfo.canUseRestrictedNetworks) {
227                            continue;
228                        }
229                    }
230                    mListeners.getBroadcastItem(i).onAvailabilityChanged(iface, up);
231                } catch (RemoteException e) {
232                    // Do nothing here.
233                }
234            }
235            mListeners.finishBroadcast();
236        }
237    }
238
239    private void maybeTrackInterface(String iface) {
240        if (DBG) Log.i(TAG, "maybeTrackInterface " + iface);
241        // If we don't already track this interface, and if this interface matches
242        // our regex, start tracking it.
243        if (!iface.matches(mIfaceMatch) || mFactory.hasInterface(iface)) {
244            return;
245        }
246
247        if (mIpConfigForDefaultInterface != null) {
248            updateIpConfiguration(iface, mIpConfigForDefaultInterface);
249            mIpConfigForDefaultInterface = null;
250        }
251
252        addInterface(iface);
253    }
254
255    private void trackAvailableInterfaces() {
256        try {
257            final String[] ifaces = mNMService.listInterfaces();
258            for (String iface : ifaces) {
259                maybeTrackInterface(iface);
260            }
261        } catch (RemoteException | IllegalStateException e) {
262            Log.e(TAG, "Could not get list of interfaces " + e);
263        }
264    }
265
266
267    private class InterfaceObserver extends BaseNetworkObserver {
268
269        @Override
270        public void interfaceLinkStateChanged(String iface, boolean up) {
271            if (DBG) {
272                Log.i(TAG, "interfaceLinkStateChanged, iface: " + iface + ", up: " + up);
273            }
274            mHandler.post(() -> updateInterfaceState(iface, up));
275        }
276
277        @Override
278        public void interfaceAdded(String iface) {
279            mHandler.post(() -> maybeTrackInterface(iface));
280        }
281
282        @Override
283        public void interfaceRemoved(String iface) {
284            mHandler.post(() -> removeInterface(iface));
285        }
286    }
287
288    private static class ListenerInfo {
289
290        boolean canUseRestrictedNetworks = false;
291
292        ListenerInfo(boolean canUseRestrictedNetworks) {
293            this.canUseRestrictedNetworks = canUseRestrictedNetworks;
294        }
295    }
296
297    private void parseEthernetConfig(String configString) {
298        String[] tokens = configString.split(";");
299        String name = tokens[0];
300        String capabilities = tokens.length > 1 ? tokens[1] : null;
301        NetworkCapabilities nc = createNetworkCapabilities(
302                !TextUtils.isEmpty(capabilities)  /* clear default capabilities */, capabilities);
303        mNetworkCapabilities.put(name, nc);
304
305        if (tokens.length > 2 && !TextUtils.isEmpty(tokens[2])) {
306            IpConfiguration ipConfig = parseStaticIpConfiguration(tokens[2]);
307            mIpConfigurations.put(name, ipConfig);
308        }
309    }
310
311    private static NetworkCapabilities createDefaultNetworkCapabilities() {
312        NetworkCapabilities nc = createNetworkCapabilities(false /* clear default capabilities */);
313        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
314        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
315        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
316        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
317        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED);
318
319        return nc;
320    }
321
322    private static NetworkCapabilities createNetworkCapabilities(boolean clearDefaultCapabilities) {
323        return createNetworkCapabilities(clearDefaultCapabilities, null);
324    }
325
326    private static NetworkCapabilities createNetworkCapabilities(
327            boolean clearDefaultCapabilities, @Nullable String commaSeparatedCapabilities) {
328
329        NetworkCapabilities nc = new NetworkCapabilities();
330        if (clearDefaultCapabilities) {
331            nc.clearAll();  // Remove default capabilities.
332        }
333        nc.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
334        nc.setLinkUpstreamBandwidthKbps(100 * 1000);
335        nc.setLinkDownstreamBandwidthKbps(100 * 1000);
336
337        if (!TextUtils.isEmpty(commaSeparatedCapabilities)) {
338            for (String strNetworkCapability : commaSeparatedCapabilities.split(",")) {
339                if (!TextUtils.isEmpty(strNetworkCapability)) {
340                    nc.addCapability(Integer.valueOf(strNetworkCapability));
341                }
342            }
343        }
344
345        return nc;
346    }
347
348    /**
349     * Parses static IP configuration.
350     *
351     * @param staticIpConfig represents static IP configuration in the following format: {@code
352     * ip=<ip-address/mask> gateway=<ip-address> dns=<comma-sep-ip-addresses>
353     *     domains=<comma-sep-domains>}
354     */
355    @VisibleForTesting
356    static IpConfiguration parseStaticIpConfiguration(String staticIpConfig) {
357        StaticIpConfiguration ipConfig = new StaticIpConfiguration();
358
359        for (String keyValueAsString : staticIpConfig.trim().split(" ")) {
360            if (TextUtils.isEmpty(keyValueAsString)) continue;
361
362            String[] pair = keyValueAsString.split("=");
363            if (pair.length != 2) {
364                throw new IllegalArgumentException("Unexpected token: " + keyValueAsString
365                        + " in " + staticIpConfig);
366            }
367
368            String key = pair[0];
369            String value = pair[1];
370
371            switch (key) {
372                case "ip":
373                    ipConfig.ipAddress = new LinkAddress(value);
374                    break;
375                case "domains":
376                    ipConfig.domains = value;
377                    break;
378                case "gateway":
379                    ipConfig.gateway = InetAddress.parseNumericAddress(value);
380                    break;
381                case "dns": {
382                    ArrayList<InetAddress> dnsAddresses = new ArrayList<>();
383                    for (String address: value.split(",")) {
384                        dnsAddresses.add(InetAddress.parseNumericAddress(address));
385                    }
386                    ipConfig.dnsServers.addAll(dnsAddresses);
387                    break;
388                }
389                default : {
390                    throw new IllegalArgumentException("Unexpected key: " + key
391                            + " in " + staticIpConfig);
392                }
393            }
394        }
395        return new IpConfiguration(IpAssignment.STATIC, ProxySettings.NONE, ipConfig, null);
396    }
397
398    private static IpConfiguration createDefaultIpConfiguration() {
399        return new IpConfiguration(IpAssignment.DHCP, ProxySettings.NONE, null, null);
400    }
401
402    private void postAndWaitForRunnable(Runnable r) {
403        mHandler.runWithScissors(r, 2000L /* timeout */);
404    }
405
406    void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
407        postAndWaitForRunnable(() -> {
408            pw.println(getClass().getSimpleName());
409            pw.println("Ethernet interface name filter: " + mIfaceMatch);
410            pw.println("Listeners: " + mListeners.getRegisteredCallbackCount());
411            pw.println("IP Configurations:");
412            pw.increaseIndent();
413            for (String iface : mIpConfigurations.keySet()) {
414                pw.println(iface + ": " + mIpConfigurations.get(iface));
415            }
416            pw.decreaseIndent();
417            pw.println();
418
419            pw.println("Network Capabilities:");
420            pw.increaseIndent();
421            for (String iface : mNetworkCapabilities.keySet()) {
422                pw.println(iface + ": " + mNetworkCapabilities.get(iface));
423            }
424            pw.decreaseIndent();
425            pw.println();
426
427            mFactory.dump(fd, pw, args);
428        });
429    }
430}
431