/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wifi.util; import android.content.Context; import android.net.INetd; import android.net.INetdUnsolicitedEventListener; import android.net.InetAddresses; import android.net.InterfaceConfiguration; import android.net.InterfaceConfigurationParcel; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.RouteInfo; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.text.TextUtils; import android.util.Log; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; /** * This is a simple wrapper over INetd calls used by wifi stack. * * Based on {@link com.android.server.NetworkManagementService} */ public class NetdWrapper { private static final String TAG = "NetdWrapper"; static final boolean MODIFY_OPERATION_ADD = true; static final boolean MODIFY_OPERATION_REMOVE = false; private final INetd mNetdService; private final NetdUnsolicitedEventListener mNetdUnsolicitedEventListener; private final Handler mHandler; private final Set mObservers = new HashSet<>(); /** * Observer for iface events. */ public interface NetdEventObserver { /** * Interface configuration status has changed. * * @param iface The interface. * @param up True if the interface has been enabled. */ void interfaceStatusChanged(String iface, boolean up); /** * Interface physical-layer link state has changed. For Ethernet, * this method is invoked when the cable is plugged in or unplugged. * * @param iface The interface. * @param up True if the physical link-layer connection signal is valid. */ void interfaceLinkStateChanged(String iface, boolean up); } private class NetdUnsolicitedEventListener extends INetdUnsolicitedEventListener.Stub { @Override public void onInterfaceClassActivityChanged(boolean isActive, int label, long timestamp, int uid) throws RemoteException { // Unused. } @Override public void onQuotaLimitReached(String alertName, String ifName) throws RemoteException { // Unused. } @Override public void onInterfaceDnsServerInfo(String ifName, long lifetime, String[] servers) throws RemoteException { // Unused. } @Override public void onInterfaceAddressUpdated(String addr, String ifName, int flags, int scope) throws RemoteException { // Unused. } @Override public void onInterfaceAddressRemoved(String addr, String ifName, int flags, int scope) throws RemoteException { // Unused. } @Override public void onInterfaceAdded(String ifName) throws RemoteException { // Unused. } @Override public void onInterfaceRemoved(String ifName) throws RemoteException { // Unused. } @Override public void onInterfaceChanged(String ifName, boolean up) throws RemoteException { mHandler.post(() -> notifyInterfaceStatusChanged(ifName, up)); } @Override public void onInterfaceLinkStateChanged(String ifName, boolean up) throws RemoteException { mHandler.post(() -> notifyInterfaceLinkStateChanged(ifName, up)); } @Override public void onRouteChanged(boolean updated, String route, String gateway, String ifName) throws RemoteException { // Unused. } @Override public void onStrictCleartextDetected(int uid, String hex) throws RemoteException { // Unused. } @Override public int getInterfaceVersion() { return INetdUnsolicitedEventListener.VERSION; } @Override public String getInterfaceHash() { return INetdUnsolicitedEventListener.HASH; } } public NetdWrapper(Context context, Handler handler) { mNetdService = INetd.Stub.asInterface( (IBinder) context.getSystemService(Context.NETD_SERVICE)); mNetdUnsolicitedEventListener = new NetdUnsolicitedEventListener(); mHandler = handler; try { mNetdService.registerUnsolicitedEventListener(mNetdUnsolicitedEventListener); } catch (RemoteException | ServiceSpecificException e) { Log.e(TAG, "Failed to set Netd unsolicited event listener " + e); } } private void modifyInterfaceInNetwork(boolean add, int netId, String iface) { try { if (add) { mNetdService.networkAddInterface(netId, iface); } else { mNetdService.networkRemoveInterface(netId, iface); } } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } private void modifyRoute(boolean add, int netId, RouteInfo route) { final String ifName = route.getInterface(); final String dst = route.getDestination().toString(); final String nextHop; switch (route.getType()) { case RouteInfo.RTN_UNICAST: if (route.hasGateway()) { nextHop = route.getGateway().getHostAddress(); } else { nextHop = INetd.NEXTHOP_NONE; } break; case RouteInfo.RTN_UNREACHABLE: nextHop = INetd.NEXTHOP_UNREACHABLE; break; case RouteInfo.RTN_THROW: nextHop = INetd.NEXTHOP_THROW; break; default: nextHop = INetd.NEXTHOP_NONE; break; } try { if (add) { mNetdService.networkAddRoute(netId, ifName, dst, nextHop); } else { mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop); } } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Add iface to local network. */ public void addInterfaceToLocalNetwork(String iface, List routes) { modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface); for (RouteInfo route : routes) { if (!route.isDefaultRoute()) { modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route); } } // IPv6 link local should be activated always. modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, new RouteInfo(new IpPrefix("fe80::/64"), null, iface, RouteInfo.RTN_UNICAST)); } /** * Remove iface from local network. */ public void removeInterfaceFromLocalNetwork(String iface) { modifyInterfaceInNetwork(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, iface); } /** * Clear iface addresses. */ public void clearInterfaceAddresses(String iface) { try { mNetdService.interfaceClearAddrs(iface); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Enable IPv6 on iface. */ public void enableIpv6(String iface) { try { mNetdService.interfaceSetEnableIPv6(iface, true); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Disable IPv6 on iface. */ public void disableIpv6(String iface) { try { mNetdService.interfaceSetEnableIPv6(iface, false); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Convert InterfaceConfiguration to InterfaceConfigurationParcel with given ifname. */ private static InterfaceConfigurationParcel toStableParcel(InterfaceConfiguration cfg, String iface) { InterfaceConfigurationParcel cfgParcel = new InterfaceConfigurationParcel(); cfgParcel.ifName = iface; String hwAddr = cfg.getHardwareAddress(); if (!TextUtils.isEmpty(hwAddr)) { cfgParcel.hwAddr = hwAddr; } else { cfgParcel.hwAddr = ""; } cfgParcel.ipv4Addr = cfg.getLinkAddress().getAddress().getHostAddress(); cfgParcel.prefixLength = cfg.getLinkAddress().getPrefixLength(); ArrayList flags = new ArrayList<>(); for (String flag : cfg.getFlags()) { flags.add(flag); } cfgParcel.flags = flags.toArray(new String[0]); return cfgParcel; } /** * Construct InterfaceConfiguration from InterfaceConfigurationParcel. */ private static InterfaceConfiguration fromStableParcel(InterfaceConfigurationParcel p) { InterfaceConfiguration cfg = new InterfaceConfiguration(); cfg.setHardwareAddress(p.hwAddr); final InetAddress addr = InetAddresses.parseNumericAddress(p.ipv4Addr); cfg.setLinkAddress(new LinkAddress(addr, p.prefixLength)); for (String flag : p.flags) { cfg.setFlag(flag); } return cfg; } private InterfaceConfiguration getInterfaceConfig(String iface) { final InterfaceConfigurationParcel result; try { result = mNetdService.interfaceGetCfg(iface); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } try { final InterfaceConfiguration cfg = fromStableParcel(result); return cfg; } catch (IllegalArgumentException iae) { throw new IllegalStateException("Invalid InterfaceConfigurationParcel", iae); } } private void setInterfaceConfig(String iface, InterfaceConfiguration cfg) { LinkAddress linkAddr = cfg.getLinkAddress(); if (linkAddr == null || linkAddr.getAddress() == null) { throw new IllegalStateException("Null LinkAddress given"); } final InterfaceConfigurationParcel cfgParcel = toStableParcel(cfg, iface); try { mNetdService.interfaceSetCfg(cfgParcel); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * List all tethered interfaces. */ public String[] listTetheredInterfaces() { try { return mNetdService.tetherInterfaceList(); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Register a new netd event observer. */ public void registerObserver(NetdEventObserver observer) { mObservers.add(observer); } /** * Unregister a new netd event observer. */ public void unregisterObserver(NetdEventObserver observer) { mObservers.remove(observer); } /** * Set iface down. */ public void setInterfaceDown(String iface) { final InterfaceConfiguration ifcg = getInterfaceConfig(iface); ifcg.setInterfaceDown(); setInterfaceConfig(iface, ifcg); } /** * Set iface up. */ public void setInterfaceUp(String iface) { final InterfaceConfiguration ifcg = getInterfaceConfig(iface); ifcg.setInterfaceUp(); setInterfaceConfig(iface, ifcg); } /** * Returns whether iface is up. */ public boolean isInterfaceUp(String iface) { final InterfaceConfiguration ifcg = getInterfaceConfig(iface); return ifcg.isUp(); } /** * Set iface link address. */ public void setInterfaceLinkAddress(String iface, LinkAddress linkAddress) { final InterfaceConfiguration ifcg = getInterfaceConfig(iface); ifcg.setLinkAddress(linkAddress); ifcg.setInterfaceUp(); setInterfaceConfig(iface, ifcg); } /** * Set iface IPv6 privacy extensions. */ public void setInterfaceIpv6PrivacyExtensions(String iface, boolean enable) { try { mNetdService.interfaceSetIPv6PrivacyExtensions(iface, enable); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Start tethering. */ public void startTethering(String[] dhcpRange) { // an odd number of addrs will fail try { mNetdService.tetherStart(dhcpRange); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Stop tethering. */ public void stopTethering() { try { mNetdService.tetherStop(); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Add tethering on iface. */ public void tetherInterface(String iface) { try { mNetdService.tetherInterfaceAdd(iface); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } List routes = new ArrayList<>(); // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it // suitable to use as a route destination. LinkAddress dest = getInterfaceConfig(iface).getLinkAddress(); RouteInfo route = new RouteInfo( new IpPrefix(dest.getAddress(), dest.getPrefixLength()), null, null, RouteInfo.RTN_UNICAST); routes.add(route); addInterfaceToLocalNetwork(iface, routes); } /** * Remove tethering on iface. */ public void untetherInterface(String iface) { try { mNetdService.tetherInterfaceRemove(iface); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } finally { removeInterfaceFromLocalNetwork(iface); } } /** * Returns whether tethering has been started. */ public boolean isTetheringStarted() { try { final boolean isEnabled = mNetdService.tetherIsEnabled(); return isEnabled; } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } } /** * Notify our observers of an interface status change */ private void notifyInterfaceStatusChanged(String iface, boolean up) { for (NetdEventObserver observer : mObservers) { observer.interfaceStatusChanged(iface, up); } } /** * Notify our observers of an interface link state change * (typically, an Ethernet cable has been plugged-in or unplugged). */ private void notifyInterfaceLinkStateChanged(String iface, boolean up) { for (NetdEventObserver observer : mObservers) { observer.interfaceLinkStateChanged(iface, up); } } }