EthernetNetworkFactory.java revision ed75bcf13a0b416843cf8d8e349a3340ae270f9d
1/*
2 * Copyright (C) 2014 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.content.Context;
20import android.net.ConnectivityManager;
21import android.net.DhcpResults;
22import android.net.InterfaceConfiguration;
23import android.net.NetworkUtils;
24import android.net.IpConfiguration;
25import android.net.IpConfiguration.IpAssignment;
26import android.net.IpConfiguration.ProxySettings;
27import android.net.LinkAddress;
28import android.net.LinkProperties;
29import android.net.NetworkAgent;
30import android.net.NetworkCapabilities;
31import android.net.NetworkFactory;
32import android.net.NetworkInfo;
33import android.net.NetworkInfo.DetailedState;
34import android.net.NetworkRequest;
35import android.net.EthernetManager;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.INetworkManagementService;
39import android.os.Looper;
40import android.os.Message;
41import android.os.Messenger;
42import android.os.RemoteException;
43import android.os.ServiceManager;
44import android.text.TextUtils;
45import android.util.Log;
46
47import com.android.server.net.BaseNetworkObserver;
48
49import java.net.Inet4Address;
50import java.util.concurrent.atomic.AtomicBoolean;
51import java.util.concurrent.atomic.AtomicInteger;
52
53
54/**
55 * Manages connectivity for an Ethernet interface.
56 *
57 * Ethernet Interfaces may be present at boot time or appear after boot (e.g.,
58 * for Ethernet adapters connected over USB). This class currently supports
59 * only one interface. When an interface appears on the system (or is present
60 * at boot time) this class will start tracking it and bring it up, and will
61 * attempt to connect when requested. Any other interfaces that subsequently
62 * appear will be ignored until the tracked interface disappears. Only
63 * interfaces whose names match the <code>config_ethernet_iface_regex</code>
64 * regular expression are tracked.
65 *
66 * This class reports a static network score of 70 when it is tracking an
67 * interface and that interface's link is up, and a score of 0 otherwise.
68 *
69 * @hide
70 */
71class EthernetNetworkFactory {
72    private static final String NETWORK_TYPE = "Ethernet";
73    private static final String TAG = "EthernetNetworkFactory";
74    private static final int NETWORK_SCORE = 70;
75    private static final boolean DBG = true;
76
77    /** Tracks interface changes. Called from NetworkManagementService. */
78    private InterfaceObserver mInterfaceObserver;
79
80    /** For static IP configuration */
81    private EthernetManager mEthernetManager;
82
83    /** To set link state and configure IP addresses. */
84    private INetworkManagementService mNMService;
85
86    /* To communicate with ConnectivityManager */
87    private NetworkCapabilities mNetworkCapabilities;
88    private NetworkAgent mNetworkAgent;
89    private LocalNetworkFactory mFactory;
90    private Context mContext;
91
92    /** Product-dependent regular expression of interface names we track. */
93    private static String mIfaceMatch = "";
94
95    /** Data members. All accesses to these must be synchronized(this). */
96    private static String mIface = "";
97    private String mHwAddr;
98    private static boolean mLinkUp;
99    private NetworkInfo mNetworkInfo;
100    private LinkProperties mLinkProperties;
101
102    EthernetNetworkFactory() {
103        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
104        mLinkProperties = new LinkProperties();
105        initNetworkCapabilities();
106    }
107
108    private class LocalNetworkFactory extends NetworkFactory {
109        LocalNetworkFactory(String name, Context context, Looper looper) {
110            super(looper, context, name, new NetworkCapabilities());
111        }
112
113        protected void startNetwork() {
114            onRequestNetwork();
115        }
116        protected void stopNetwork() {
117        }
118    }
119
120
121    /**
122     * Updates interface state variables.
123     * Called on link state changes or on startup.
124     */
125    private void updateInterfaceState(String iface, boolean up) {
126        if (!mIface.equals(iface)) {
127            return;
128        }
129        Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
130
131        synchronized(this) {
132            mLinkUp = up;
133            mNetworkInfo.setIsAvailable(up);
134            if (!up) {
135                // Tell the agent we're disconnected. It will call disconnect().
136                mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
137            }
138            updateAgent();
139            mFactory.setScoreFilter(up ? NETWORK_SCORE : -1);
140        }
141    }
142
143    private class InterfaceObserver extends BaseNetworkObserver {
144        @Override
145        public void interfaceLinkStateChanged(String iface, boolean up) {
146            updateInterfaceState(iface, up);
147        }
148
149        @Override
150        public void interfaceAdded(String iface) {
151            maybeTrackInterface(iface);
152        }
153
154        @Override
155        public void interfaceRemoved(String iface) {
156            stopTrackingInterface(iface);
157        }
158    }
159
160    private void setInterfaceUp(String iface) {
161        // Bring up the interface so we get link status indications.
162        try {
163            mNMService.setInterfaceUp(iface);
164            String hwAddr = null;
165            InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
166
167            if (config == null) {
168                Log.e(TAG, "Null iterface config for " + iface + ". Bailing out.");
169                return;
170            }
171
172            synchronized (this) {
173                if (mIface.isEmpty()) {
174                    mIface = iface;
175                    mHwAddr = config.getHardwareAddress();
176                    mNetworkInfo.setIsAvailable(true);
177                    mNetworkInfo.setExtraInfo(mHwAddr);
178                } else {
179                    Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
180                    mNMService.setInterfaceDown(iface);
181                }
182            }
183        } catch (RemoteException e) {
184            Log.e(TAG, "Error upping interface " + mIface + ": " + e);
185        }
186    }
187
188    private boolean maybeTrackInterface(String iface) {
189        // If we don't already have an interface, and if this interface matches
190        // our regex, start tracking it.
191        if (!iface.matches(mIfaceMatch) || !mIface.isEmpty())
192            return false;
193
194        Log.d(TAG, "Started tracking interface " + iface);
195        setInterfaceUp(iface);
196        return true;
197    }
198
199    private void stopTrackingInterface(String iface) {
200        if (!iface.equals(mIface))
201            return;
202
203        Log.d(TAG, "Stopped tracking interface " + iface);
204        synchronized (this) {
205            mIface = "";
206            mHwAddr = null;
207            mNetworkInfo.setExtraInfo(null);
208            mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
209            mLinkProperties = new LinkProperties();
210            updateAgent();
211        }
212    }
213
214    private void setStaticIpAddress(LinkProperties linkProperties) {
215        Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + mLinkProperties);
216        try {
217            InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
218            for (LinkAddress address: linkProperties.getLinkAddresses()) {
219                // IPv6 uses autoconfiguration.
220                if (address.getAddress() instanceof Inet4Address) {
221                    config.setLinkAddress(address);
222                    // This API only supports one IPv4 address.
223                    mNMService.setInterfaceConfig(mIface, config);
224                    break;
225                }
226            }
227        } catch(RemoteException e) {
228           Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
229        } catch(IllegalStateException e) {
230           Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
231        }
232    }
233
234    public void updateAgent() {
235        synchronized (EthernetNetworkFactory.this) {
236            if (mNetworkAgent == null) return;
237            if (DBG) {
238                Log.i(TAG, "Updating mNetworkAgent with: " +
239                      mNetworkCapabilities + ", " +
240                      mNetworkInfo + ", " +
241                      mLinkProperties);
242            }
243            mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
244            mNetworkAgent.sendNetworkInfo(mNetworkInfo);
245            mNetworkAgent.sendLinkProperties(mLinkProperties);
246            mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : -1);
247        }
248    }
249
250    /* Called by the NetworkFactory on the handler thread. */
251    public void onRequestNetwork() {
252        // TODO: Handle DHCP renew.
253        Thread dhcpThread = new Thread(new Runnable() {
254            public void run() {
255                if (DBG) Log.i(TAG, "dhcpThread(+" + mIface + "): mNetworkInfo=" + mNetworkInfo);
256                LinkProperties linkProperties;
257
258                IpConfiguration config = mEthernetManager.getConfiguration();
259
260                if (config.ipAssignment == IpAssignment.STATIC) {
261                    linkProperties = config.linkProperties;
262                    setStaticIpAddress(linkProperties);
263                } else {
264                    mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
265
266                    DhcpResults dhcpResults = new DhcpResults();
267                    // TODO: Handle DHCP renewals better.
268                    // In general runDhcp handles DHCP renewals for us, because
269                    // the dhcp client stays running, but if the renewal fails,
270                    // we will lose our IP address and connectivity without
271                    // noticing.
272                    if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
273                        Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
274                        mFactory.setScoreFilter(-1);
275                        return;
276                    }
277                    linkProperties = dhcpResults.linkProperties;
278                    linkProperties.setInterfaceName(mIface);
279                }
280                if (config.proxySettings == ProxySettings.STATIC) {
281                    linkProperties.setHttpProxy(config.linkProperties.getHttpProxy());
282                }
283
284                synchronized(EthernetNetworkFactory.this) {
285                    if (mNetworkAgent != null) {
286                        Log.e(TAG, "Already have a NetworkAgent - aborting new request");
287                        return;
288                    }
289                    mLinkProperties = linkProperties;
290                    mNetworkInfo.setIsAvailable(true);
291                    mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
292
293                    // Create our NetworkAgent.
294                    mNetworkAgent = new NetworkAgent(mFactory.getLooper(), mContext,
295                            NETWORK_TYPE, mNetworkInfo, mNetworkCapabilities, mLinkProperties,
296                            NETWORK_SCORE) {
297                        public void unwanted() {
298                            synchronized(EthernetNetworkFactory.this) {
299                                if (this == mNetworkAgent) {
300                                    NetworkUtils.stopDhcp(mIface);
301
302                                    mLinkProperties.clear();
303                                    mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null,
304                                            mHwAddr);
305                                    updateAgent();
306                                    mNetworkAgent = null;
307                                    try {
308                                        mNMService.clearInterfaceAddresses(mIface);
309                                    } catch (Exception e) {
310                                        Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
311                                    }
312                                } else {
313                                    Log.d(TAG, "Ignoring unwanted as we have a more modern " +
314                                            "instance");
315                                }
316                            }
317                        };
318                    };
319                }
320            }
321        });
322        dhcpThread.start();
323    }
324
325    /**
326     * Begin monitoring connectivity
327     */
328    public synchronized void start(Context context, Handler target) {
329        // The services we use.
330        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
331        mNMService = INetworkManagementService.Stub.asInterface(b);
332        mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
333
334        // Interface match regex.
335        mIfaceMatch = context.getResources().getString(
336                com.android.internal.R.string.config_ethernet_iface_regex);
337
338        // Create and register our NetworkFactory.
339        mFactory = new LocalNetworkFactory(NETWORK_TYPE, context, target.getLooper());
340        mFactory.setCapabilityFilter(mNetworkCapabilities);
341        mFactory.setScoreFilter(-1); // this set high when we have an iface
342        mFactory.register();
343
344        mContext = context;
345
346        // Start tracking interface change events.
347        mInterfaceObserver = new InterfaceObserver();
348        try {
349            mNMService.registerObserver(mInterfaceObserver);
350        } catch (RemoteException e) {
351            Log.e(TAG, "Could not register InterfaceObserver " + e);
352        }
353
354        // If an Ethernet interface is already connected, start tracking that.
355        // Otherwise, the first Ethernet interface to appear will be tracked.
356        try {
357            final String[] ifaces = mNMService.listInterfaces();
358            for (String iface : ifaces) {
359                synchronized(this) {
360                    if (maybeTrackInterface(iface)) {
361                        // We have our interface. Track it.
362                        // Note: if the interface already has link (e.g., if we
363                        // crashed and got restarted while it was running),
364                        // we need to fake a link up notification so we start
365                        // configuring it. Since we're already holding the lock,
366                        // any real link up/down notification will only arrive
367                        // after we've done this.
368                        if (mNMService.getInterfaceConfig(iface).hasFlag("running")) {
369                            updateInterfaceState(iface, true);
370                        }
371                        break;
372                    }
373                }
374            }
375        } catch (RemoteException e) {
376            Log.e(TAG, "Could not get list of interfaces " + e);
377        }
378    }
379
380    public synchronized void stop() {
381        mIface = "";
382        mHwAddr = null;
383        mLinkUp = false;
384        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
385        mLinkProperties = new LinkProperties();
386        updateAgent();
387        mFactory.unregister();
388    }
389
390    private void initNetworkCapabilities() {
391        mNetworkCapabilities = new NetworkCapabilities();
392        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
393        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
394        mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
395        // We have no useful data on bandwidth. Say 100M up and 100M down. :-(
396        mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
397        mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000);
398    }
399}
400