EthernetNetworkFactory.java revision 20c1c99c4351abd8bb3d6e8f966fcf3b6de0e5b0
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.ConnectivityServiceProtocol.NetworkFactoryProtocol;
22import android.net.DhcpResults;
23import android.net.InterfaceConfiguration;
24import android.net.NetworkUtils;
25import android.net.IpConfiguration;
26import android.net.IpConfiguration.IpAssignment;
27import android.net.IpConfiguration.ProxySettings;
28import android.net.LinkAddress;
29import android.net.LinkProperties;
30import android.net.NetworkAgent;
31import android.net.NetworkCapabilities;
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
54class NetworkFactory extends Handler {
55    public interface Callback {
56        public void onRequestNetwork(NetworkRequest request, int currentScore);
57        public void onCancelRequest(NetworkRequest request);
58    }
59
60    private String mName;
61    private Callback mCallback;
62    private ConnectivityManager mCM;
63
64    NetworkFactory(String name, Context context, Looper looper, Callback callback) {
65        super(looper);
66        mCallback = callback;
67        mName = name;
68        mCM = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
69    }
70
71    public void register() {
72        logi("Registering network factory");
73        mCM.registerNetworkFactory(new Messenger(this), mName);
74    }
75
76    @Override
77    public void handleMessage(Message message) {
78        switch(message.what) {
79            case NetworkFactoryProtocol.CMD_REQUEST_NETWORK:
80                mCallback.onRequestNetwork((NetworkRequest) message.obj, message.arg1);
81                break;
82            case NetworkFactoryProtocol.CMD_CANCEL_REQUEST:
83                mCallback.onCancelRequest((NetworkRequest) message.obj);
84                break;
85            default:
86                loge("Unhandled message " + message.what);
87        }
88    }
89
90    private void logi(String s) {
91        Log.i("NetworkFactory" + mName, s);
92    }
93
94    private void loge(String s) {
95        Log.e("NetworkFactory" + mName, s);
96    }
97}
98
99/**
100 * This class tracks the data connection associated with Ethernet.
101 * @hide
102 */
103class EthernetNetworkFactory implements NetworkFactory.Callback {
104    private static final String NETWORK_TYPE = "ETHERNET";
105    private static final String TAG = "EthernetNetworkFactory";
106    private static final int NETWORK_SCORE = 70;
107    private static final boolean DBG = true;
108
109    /** Tracks interface changes. Called from the NetworkManagementService thread. */
110    private InterfaceObserver mInterfaceObserver;
111
112    /** For static IP configuration */
113    private EthernetManager mEthernetManager;
114
115    /** To set link state and configure IP addresses. */
116    private INetworkManagementService mNMService;
117
118    /* To communicate with ConnectivityManager */
119    private NetworkCapabilities mNetworkCapabilities;
120    private NetworkAgent mNetworkAgent;
121    private NetworkFactory mFactory;
122
123    /** Product-dependent regular expression of interface names we want to track. */
124    private static String mIfaceMatch = "";
125
126    /** Data members. All accesses must be synchronized(this). */
127    private static String mIface = "";
128    private String mHwAddr;
129    private static boolean mLinkUp;
130    private NetworkInfo mNetworkInfo;
131    private LinkProperties mLinkProperties;
132
133    EthernetNetworkFactory() {
134        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
135        mLinkProperties = new LinkProperties();
136        initNetworkCapabilities();
137    }
138
139    private void updateInterfaceState(String iface, boolean up) {
140        if (!mIface.equals(iface)) {
141            // We only support one interface.
142            return;
143        }
144        Log.d(TAG, "updateInterface: " + iface + " link " + (up ? "up" : "down"));
145
146        synchronized(this) {
147            mLinkUp = up;
148            mNetworkInfo.setIsAvailable(up);
149            DetailedState state = up ? DetailedState.CONNECTED: DetailedState.DISCONNECTED;
150            mNetworkInfo.setDetailedState(state, null, mHwAddr);
151            mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
152            updateAgent();
153        }
154    }
155
156    private class InterfaceObserver extends BaseNetworkObserver {
157        @Override
158        public void interfaceLinkStateChanged(String iface, boolean up) {
159            updateInterfaceState(iface, up);
160        }
161
162        @Override
163        public void interfaceAdded(String iface) {
164            maybeTrackInterface(iface);
165        }
166
167        @Override
168        public void interfaceRemoved(String iface) {
169            stopTrackingInterface(iface);
170        }
171    }
172
173    private void setInterfaceUp(String iface) {
174        // Bring up the interface so we get link status indications.
175        try {
176            mNMService.setInterfaceUp(iface);
177            String hwAddr = null;
178            InterfaceConfiguration config = mNMService.getInterfaceConfig(iface);
179
180            if (config == null) {
181                Log.e(TAG, "Null iterface config for " + iface + ". Bailing out.");
182                return;
183            }
184
185            synchronized (this) {
186                if (mIface.isEmpty()) {
187                    mIface = iface;
188                    mHwAddr = config.getHardwareAddress();
189                    mNetworkInfo.setIsAvailable(true);
190                    mNetworkInfo.setExtraInfo(mHwAddr);
191                } else {
192                    Log.e(TAG, "Interface unexpectedly changed from " + iface + " to " + mIface);
193                    mNMService.setInterfaceDown(iface);
194                }
195            }
196        } catch (RemoteException e) {
197            Log.e(TAG, "Error upping interface " + mIface + ": " + e);
198        }
199    }
200
201    private boolean maybeTrackInterface(String iface) {
202        // If we don't already have an interface, and if this interface matches
203        // our regex, start tracking it.
204        if (!iface.matches(mIfaceMatch) || !mIface.isEmpty())
205            return false;
206
207        Log.d(TAG, "Started tracking interface " + iface);
208        setInterfaceUp(iface);
209        return true;
210    }
211
212    private void stopTrackingInterface(String iface) {
213        if (!iface.equals(mIface))
214            return;
215
216        Log.d(TAG, "Stopped tracking interface " + iface);
217        disconnect();
218        synchronized (this) {
219            mIface = "";
220            mHwAddr = null;
221            mNetworkInfo.setExtraInfo(null);
222        }
223    }
224
225    private void setStaticIpAddress(LinkProperties linkProperties) {
226        Log.i(TAG, "Applying static IPv4 configuration to " + mIface + ": " + mLinkProperties);
227        try {
228            InterfaceConfiguration config = mNMService.getInterfaceConfig(mIface);
229            for (LinkAddress address: linkProperties.getLinkAddresses()) {
230                // IPv6 uses autoconfiguration.
231                if (address.getAddress() instanceof Inet4Address) {
232                    config.setLinkAddress(address);
233                    // This API only supports one IPv4 address.
234                    mNMService.setInterfaceConfig(mIface, config);
235                    break;
236                }
237            }
238        } catch(RemoteException e) {
239           Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
240        } catch(IllegalStateException e) {
241           Log.e(TAG, "Setting static IP address failed: " + e.getMessage());
242        }
243    }
244
245    public void updateAgent() {
246        if (DBG) {
247            Log.i(TAG, "Updating mNetworkAgent with: " +
248                  mNetworkCapabilities + ", " +
249                  mNetworkInfo + ", " +
250                  mLinkProperties);
251        }
252        mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities);
253        mNetworkAgent.sendNetworkInfo(mNetworkInfo);
254        mNetworkAgent.sendLinkProperties(mLinkProperties);
255    }
256
257    /* Called by the NetworkAgent on the handler thread. */
258    public void connect() {
259        // TODO: Handle DHCP renew.
260        Thread dhcpThread = new Thread(new Runnable() {
261            public void run() {
262                if (DBG) Log.i(TAG, "dhcpThread: mNetworkInfo=" + mNetworkInfo);
263                mNetworkInfo.setDetailedState(DetailedState.OBTAINING_IPADDR, null, mHwAddr);
264                LinkProperties linkProperties;
265
266                IpConfiguration config = mEthernetManager.getConfiguration();
267
268                if (config.ipAssignment == IpAssignment.STATIC) {
269                    linkProperties = config.linkProperties;
270                    linkProperties.setInterfaceName(mIface);
271                    setStaticIpAddress(linkProperties);
272                } else {
273                    DhcpResults dhcpResults = new DhcpResults();
274                    // TODO: support more than one DHCP client.
275                    if (!NetworkUtils.runDhcp(mIface, dhcpResults)) {
276                        Log.e(TAG, "DHCP request error:" + NetworkUtils.getDhcpError());
277                        mNetworkAgent.sendNetworkScore(0);
278                        return;
279                    }
280                    linkProperties = dhcpResults.linkProperties;
281                }
282                if (config.proxySettings == ProxySettings.STATIC) {
283                    linkProperties.setHttpProxy(config.linkProperties.getHttpProxy());
284                }
285
286                synchronized(EthernetNetworkFactory.this) {
287                    mLinkProperties = linkProperties;
288                    mNetworkInfo.setIsAvailable(true);
289                    mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
290                    updateAgent();
291                }
292            }
293        });
294        dhcpThread.start();
295    }
296
297    public void disconnect() {
298        NetworkUtils.stopDhcp(mIface);
299
300        synchronized(this) {
301            mLinkProperties.clear();
302            mNetworkInfo.setIsAvailable(false);
303            mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
304            updateAgent();
305        }
306
307        try {
308            mNMService.clearInterfaceAddresses(mIface);
309        } catch (Exception e) {
310            Log.e(TAG, "Failed to clear addresses or disable ipv6" + e);
311        }
312    }
313
314    /**
315     * Begin monitoring connectivity
316     */
317    public synchronized void start(Context context, Handler target) {
318        // The services we use.
319        IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
320        mNMService = INetworkManagementService.Stub.asInterface(b);
321        mEthernetManager = (EthernetManager) context.getSystemService(Context.ETHERNET_SERVICE);
322
323        // Interface match regex.
324        mIfaceMatch = context.getResources().getString(
325                com.android.internal.R.string.config_ethernet_iface_regex);
326
327        // Create our NetworkAgent.
328        mNetworkAgent = new NetworkAgent(target.getLooper(), context, NETWORK_TYPE) {
329            public synchronized void sendNetworkScore(int score) {
330                Log.i(TAG, "sendNetworkScore(" + score + ")");
331                super.sendNetworkScore(score);
332            }
333            public void connect() {
334                EthernetNetworkFactory.this.connect();
335            };
336            public void disconnect() {
337                EthernetNetworkFactory.this.disconnect();
338            };
339        };
340        mNetworkAgent.sendNetworkScore(0);
341
342        // Create and register our NetworkFactory.
343        mFactory = new NetworkFactory(NETWORK_TYPE, context, target.getLooper(), this);
344        mFactory.register();
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                if (maybeTrackInterface(iface)) {
360                    break;
361                }
362            }
363        } catch (RemoteException e) {
364            Log.e(TAG, "Could not get list of interfaces " + e);
365        }
366    }
367
368    public synchronized void stop() {
369        disconnect();
370        mIface = "";
371        mHwAddr = null;
372        mLinkUp = false;
373        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_ETHERNET, 0, NETWORK_TYPE, "");
374        mLinkProperties = new LinkProperties();
375    }
376
377    public void onRequestNetwork(NetworkRequest request, int currentScore) {
378        Log.i(TAG, "onRequestNetwork: (" + currentScore + "): " + request);
379        // TODO check that the transport is compatible.
380        mNetworkAgent.addNetworkRequest(request, currentScore);
381    }
382
383    public void onCancelRequest(NetworkRequest request) {
384        Log.i(TAG, "onCancelRequest: " + request);
385        mNetworkAgent.removeNetworkRequest(request);
386    }
387
388    private void initNetworkCapabilities() {
389        mNetworkCapabilities = new NetworkCapabilities();
390        mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET);
391        mNetworkCapabilities.addNetworkCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
392        mNetworkCapabilities.addNetworkCapability(
393                NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
394        // We have no useful data on bandwidth. Say 100M up and 100M down. :-(
395        mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100 * 1000);
396        mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100 * 1000);
397    }
398}
399