一、DHCP流程
分析netd之前先了解一下网络自动获取IP流程,借鉴下图流程查看代码:
(1)WIFI扫描到可用网络后进行连接,代码路径:frameworksopt etwifiservicejavacomandroidserverwifiWifiStateMachine.java
case WifiMonitor.NETWORK_CONNECTION_EVENT: if (mVerboseLoggingEnabled) log("Network connection established"); mLastNetworkId = message.arg1; mWifiConfigManager.clearRecentFailureReason(mLastNetworkId); mLastBssid = (String) message.obj; reasonCode = message.arg2; // TODO: This check should not be needed after WifiStateMachinePrime refactor. // Currently, the last connected network configuration is left in // wpa_supplicant, this may result in wpa_supplicant initiating connection // to it after a config store reload. Hence the old network Id lookups may not // work, so disconnect the network and let network selector reselect a new // network. config = getCurrentWifiConfiguration(); if (config != null) { mWifiInfo.setBSSID(mLastBssid); mWifiInfo.setNetworkId(mLastNetworkId); mWifiInfo.setMacAddress(mWifiNative.getMacAddress(mInterfaceName)); ScanDetailCache scanDetailCache = mWifiConfigManager.getScanDetailCacheForNetwork(config.networkId); if (scanDetailCache != null && mLastBssid != null) { ScanResult scanResult = scanDetailCache.getScanResult(mLastBssid); if (scanResult != null) { mWifiInfo.setFrequency(scanResult.frequency); } } mWifiConnectivityManager.trackBssid(mLastBssid, true, reasonCode); // We need to get the updated pseudonym from supplicant for EAP-SIM/AKA/AKA' if (config.enterpriseConfig != null && TelephonyUtil.isSimEapMethod( config.enterpriseConfig.getEapMethod())) { String anonymousIdentity = mWifiNative.getEapAnonymousIdentity(mInterfaceName); if (anonymousIdentity != null) { config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); } else { Log.d(TAG, "Failed to get updated anonymous identity" + " from supplicant, reset it in WifiConfiguration."); config.enterpriseConfig.setAnonymousIdentity(null); } mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); } sendNetworkStateChangeBroadcast(mLastBssid); transitionTo(mObtainingIpState); } else { logw("Connected to unknown networkId " + mLastNetworkId + ", disconnecting..."); sendMessage(CMD_DISCONNECT); } break;
其中 transitionTo(mObtainingIpState) 即调用如下,根据当前wifi配置文件信息,进行自动或静态IP配置:
class ObtainingIpState extends State { @Override public void enter() { final WifiConfiguration currentConfig = getCurrentWifiConfiguration(); final boolean isUsingStaticIp = (currentConfig.getIpAssignment() == IpConfiguration.IpAssignment.STATIC); if (mVerboseLoggingEnabled) { final String key = currentConfig.configKey(); log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId) + " " + key + " " + " roam=" + mIsAutoRoaming + " static=" + isUsingStaticIp); } // Send event to CM & network change broadcast setNetworkDetailedState(DetailedState.OBTAINING_IPADDR); // We must clear the config BSSID, as the wifi chipset may decide to roam // from this point on and having the BSSID specified in the network block would // cause the roam to fail and the device to disconnect. clearTargetBssid("ObtainingIpAddress"); // Stop IpClient in case we're switching from DHCP to static // configuration or vice versa. // // TODO: Only ever enter this state the first time we connect to a // network, never on switching between static configuration and // DHCP. When we transition from static configuration to DHCP in // particular, we must tell ConnectivityService that we're // disconnected, because DHCP might take a long time during which // connectivity APIs such as getActiveNetworkInfo should not return // CONNECTED. stopIpClient(); mIpClient.setHttpProxy(currentConfig.getHttpProxy()); if (!TextUtils.isEmpty(mTcpBufferSizes)) { mIpClient.setTcpBufferSizes(mTcpBufferSizes); } final IpClient.ProvisioningConfiguration prov; if (!isUsingStaticIp) { prov = IpClient.buildProvisioningConfiguration() .withPreDhcpAction() .withApfCapabilities(mWifiNative.getApfCapabilities(mInterfaceName)) .withNetwork(getCurrentNetwork()) .withDisplayName(currentConfig.SSID) .withRandomMacAddress() .build(); } else { StaticIpConfiguration staticIpConfig = currentConfig.getStaticIpConfiguration(); prov = IpClient.buildProvisioningConfiguration() .withStaticConfiguration(staticIpConfig) .withApfCapabilities(mWifiNative.getApfCapabilities(mInterfaceName)) .withNetwork(getCurrentNetwork()) .withDisplayName(currentConfig.SSID) .build(); } mIpClient.startProvisioning(prov); // Get Link layer stats so as we get fresh tx packet counters getWifiLinkLayerStats(); } @Override public boolean processMessage(Message message) { logStateAndMessage(message, this); switch(message.what) { case CMD_START_CONNECT: case CMD_START_ROAM: messageHandlingStatus = MESSAGE_HANDLING_STATUS_DISCARD; break; case WifiManager.SAVE_NETWORK: messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED; deferMessage(message); break; case WifiMonitor.NETWORK_DISCONNECTION_EVENT: reportConnectionAttemptEnd( WifiMetrics.ConnectionEvent.FAILURE_NETWORK_DISCONNECTION, WifiMetricsProto.ConnectionEvent.HLF_NONE); return NOT_HANDLED; case CMD_SET_HIGH_PERF_MODE: messageHandlingStatus = MESSAGE_HANDLING_STATUS_DEFERRED; deferMessage(message); break; default: return NOT_HANDLED; } return HANDLED; } }
将状态设为DetailedState.OBTAINING_IPADDR,初始化好配置后会调用IpClient的startProvisioning
public void startProvisioning(ProvisioningConfiguration req) { if (!req.isValid()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); return; } mInterfaceParams = mDependencies.getInterfaceParams(mInterfaceName); if (mInterfaceParams == null) { logError("Failed to find InterfaceParams for " + mInterfaceName); doImmediateProvisioningFailure(IpManagerEvent.ERROR_INTERFACE_NOT_FOUND); return; } mCallback.setNeighborDiscoveryOffload(true); sendMessage(CMD_START, new ProvisioningConfiguration(req)); }
IPClient的准备工作是随配置发出CMD_START的消息,进一步执行到RunningState 方法,enter方法区别是配置了ipv6还是ipv4走不同的流程,现在默认一般是ipv4。
class RunningState extends State { private ConnectivityPacketTracker mPacketTracker; private boolean mDhcpActionInFlight; @Override public void enter() { ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration(); apfConfig.apfCapabilities = mConfiguration.mApfCapabilities; apfConfig.multicastFilter = mMulticastFiltering; // Get the Configuration for ApfFilter from Context apfConfig.ieee802_3Filter = mContext.getResources().getBoolean(R.bool.config_apfDrop802_3Frames); apfConfig.ethTypeBlackList = mContext.getResources().getIntArray(R.array.config_apfEthTypeBlackList); mApfFilter = ApfFilter.maybeCreate(mContext, apfConfig, mInterfaceParams, mCallback); // TODO: investigate the effects of any multicast filtering racing/interfering with the // rest of this IP configuration startup. if (mApfFilter == null) { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } mPacketTracker = createPacketTracker(); if (mPacketTracker != null) mPacketTracker.start(mConfiguration.mDisplayName); if (mConfiguration.mEnableIPv6 && !startIPv6()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); transitionTo(mStoppingState); return; } if (mConfiguration.mEnableIPv4 && !startIPv4()) { doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); transitionTo(mStoppingState); return; } final InitialConfiguration config = mConfiguration.mInitialConfig; if ((config != null) && !applyInitialConfig(config)) { // TODO introduce a new IpManagerEvent constant to distinguish this error case. doImmediateProvisioningFailure(IpManagerEvent.ERROR_INVALID_PROVISIONING); transitionTo(mStoppingState); return; } if (mConfiguration.mUsingMultinetworkPolicyTracker) { mMultinetworkPolicyTracker = new MultinetworkPolicyTracker( mContext, getHandler(), () -> { mLog.log("OBSERVED AvoidBadWifi changed"); }); mMultinetworkPolicyTracker.start(); } if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { doImmediateProvisioningFailure( IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); transitionTo(mStoppingState); return; } }
看下startIPv4逻辑:
private boolean startIPv4() { // If we have a StaticIpConfiguration attempt to apply it and // handle the result accordingly. if (mConfiguration.mStaticIpConfig != null) { if (mInterfaceCtrl.setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); } else { return false; } } else { // Start DHCPv4. mDhcpClient = DhcpClient.makeDhcpClient(mContext, IpClient.this, mInterfaceParams); mDhcpClient.registerForPreDhcpNotification(); mDhcpClient.sendMessage(DhcpClient.CMD_START_DHCP); } return true; }
DhcpClient也处理CMD_START_DHCP开始状态变更,稍微等一下状态切换会回到mDhcpInitState开始。
class StoppedState extends State { @Override public boolean processMessage(Message message) { switch (message.what) { case CMD_START_DHCP: if (mRegisteredForPreDhcpNotification) { transitionTo(mWaitBeforeStartState); } else { transitionTo(mDhcpInitState); } return HANDLED; default: return NOT_HANDLED; } } } private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState); // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState. abstract class WaitBeforeOtherState extends LoggingState { protected State mOtherState; @Override public void enter() { super.enter(); mController.sendMessage(CMD_PRE_DHCP_ACTION); } @Override public boolean processMessage(Message message) { super.processMessage(message); switch (message.what) { case CMD_PRE_DHCP_ACTION_COMPLETE: transitionTo(mOtherState); return HANDLED; default: return NOT_HANDLED; } } }
IPClient处理DhcpClient发来的CMD_CONFIGURE_LINKADDRESS
case DhcpClient.CMD_CONFIGURE_LINKADDRESS: { final LinkAddress ipAddress = (LinkAddress) msg.obj; if (mInterfaceCtrl.setIPv4Address(ipAddress)) { mDhcpClient.sendMessage(DhcpClient.EVENT_LINKADDRESS_CONFIGURED); } else { logError("Failed to set IPv4 address."); dispatchCallback(ProvisioningChange.LOST_PROVISIONING, new LinkProperties(mLinkProperties)); transitionTo(mStoppingState); } break; }
DhcpClient切换到mWaitBeforeStartState,这是由于之前有调用mDhcpClient.registerForPreDhcpNotification();所以这边状态等待其他状态完成,其实是dhcp有些准备工作需要在WifiStateMachine中完成,所以这边流程需要等一下,流程完了自然会切换到DhcpInitState。
class DhcpInitState extends PacketRetransmittingState { public DhcpInitState() { super(); } @Override public void enter() { super.enter(); startNewTransaction(); mLastInitEnterTime = SystemClock.elapsedRealtime(); } protected boolean sendPacket() { return sendDiscoverPacket(); } protected void receivePacket(DhcpPacket packet) { if (!isValidPacket(packet)) return; if (!(packet instanceof DhcpOfferPacket)) return; mOffer = packet.toDhcpResults(); if (mOffer != null) { Log.d(TAG, "Got pending lease: " + mOffer); transitionTo(mDhcpRequestingState); } } }
DHCPREQUEST收到回应,向IpClient发出CMD_POST_DHCP_ACTION消息。
private void notifySuccess() { mController.sendMessage( CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, new DhcpResults(mDhcpLease)); } private void acceptDhcpResults(DhcpResults results, String msg) { mDhcpLease = results; mOffer = null; Log.d(TAG, msg + " lease: " + mDhcpLease); notifySuccess(); } class DhcpRequestingState extends PacketRetransmittingState { public DhcpRequestingState() { mTimeout = DHCP_TIMEOUT_MS / 2; } protected boolean sendPacket() { return sendRequestPacket( INADDR_ANY, // ciaddr (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER INADDR_BROADCAST); // packet destination address } protected void receivePacket(DhcpPacket packet) { if (!isValidPacket(packet)) return; if ((packet instanceof DhcpAckPacket)) { DhcpResults results = packet.toDhcpResults(); if (results != null) { setDhcpLeaseExpiry(packet); acceptDhcpResults(results, "Confirmed"); transitionTo(mConfiguringInterfaceState); } } else if (packet instanceof DhcpNakPacket) { // TODO: Wait a while before returning into INIT state. Log.d(TAG, "Received NAK, returning to INIT"); mOffer = null; transitionTo(mDhcpInitState); } }
IpClient进行处理:
case DhcpClient.CMD_POST_DHCP_ACTION: stopDhcpAction(); switch (msg.arg1) { case DhcpClient.DHCP_SUCCESS: handleIPv4Success((DhcpResults) msg.obj); break; case DhcpClient.DHCP_FAILURE: handleIPv4Failure(); break; default: logError("Unknown CMD_POST_DHCP_ACTION status: %s", msg.arg1); } break;
回调WifiStateMachine的callback:
@Override public void onProvisioningSuccess(LinkProperties newLp) { mWifiMetrics.logStaEvent(StaEvent.TYPE_CMD_IP_CONFIGURATION_SUCCESSFUL); sendMessage(CMD_UPDATE_LINKPROPERTIES, newLp); sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL); }
ip配置成功后设置网络状态变为connected并发送广播通知,最后状态切到ConnectedState。
二、netd工作原理详解
NETD是Android一个专门管理网络链接, 路由/带宽/防火墙策略以及iptables的系统Daemon进程, 其在Anroid系统启动时加载:
service netd /system/bin/netd class main socket netd stream 0660 root system socket dnsproxyd stream 0660 root inet socket mdns stream 0660 root system socket fwmarkd stream 0660 root inet onrestart restart zygote onrestart restart zygote_secondary
启动netd时, 会创建四个socket,用于其他进程与netd进行通信:
netd
: 主要与Framework的NetworkManagementService
交互, 用于控制网口状态, 路由表dnsproxyd
: DNS代理的控制与配置,用于私有DNS(DNS Over TLS)的请求转发mdns
: 多播DNS(Multicast DNS,参考RFChttps://tools.ietf.org/html/rfc6762), 用于基于WIFI连接的服务发现(NSD, Network Service Discovery)fwmarkd
: iptables的(fwmark)策略路由的配置(策略路由, 如设置网络权限, 连接打标签等
总的说来, netd进程在Android中间层服务NetworkManagementService
以及内核之间建立了一个沟通的桥梁。
1.netd的启动与初始化
netd进程启动时, 主要处理做以下事情:
- 创建一个
NetlinkManager
, 用于管理与内核通信的netlink连接 - 初始化网络控制类, 如路由控制
RouteController
, 带宽控制BandwidthController
- 启动各类事件监听类:
DnsProxyListener
监听DNS代理;CommandListener
监听来自NetworkManagement
的指令 - 启动NetdHwService, 为HAL层提供接口
int main() { using android::net::gCtls; Stopwatch s;
ALOGI("Netd 1.0 starting"); remove_pid_file(); blockSigpipe(); // Before we do anything that could fork, mark CLOEXEC the UNIX sockets that we get from init. // FrameworkListener does this on initialization as well, but we only initialize these // components after having initialized other subsystems that can fork. for (const auto& sock : { CommandListener::SOCKET_NAME, DnsProxyListener::SOCKET_NAME, FwmarkServer::SOCKET_NAME, MDnsSdListener::SOCKET_NAME }) { setCloseOnExec(sock); } NetlinkManager *nm = NetlinkManager::Instance(); if (nm == nullptr) { ALOGE("Unable to create NetlinkManager"); exit(1); }; gCtls = new android::net::Controllers(); gCtls->init(); CommandListener cl; nm->setBroadcaster((SocketListener *) &cl); if (nm->start()) { ALOGE("Unable to start NetlinkManager (%s)", strerror(errno)); exit(1); } std::unique_ptr<NFLogListener> logListener; { auto result = makeNFLogListener(); if (!isOk(result)) { ALOGE("Unable to create NFLogListener: %s", toString(result).c_str()); exit(1); } logListener = std::move(result.value()); auto status = gCtls->wakeupCtrl.init(logListener.get()); if (!isOk(result)) { ALOGE("Unable to init WakeupController: %s", toString(result).c_str()); // We can still continue without wakeup packet logging. } } // Set local DNS mode, to prevent bionic from proxying // back to this service, recursively. setenv("ANDROID_DNS_MODE", "local", 1); DnsProxyListener dpl(&gCtls->netCtrl, &gCtls->eventReporter); if (dpl.startListener()) { ALOGE("Unable to start DnsProxyListener (%s)", strerror(errno)); exit(1); } MDnsSdListener mdnsl; if (mdnsl.startListener()) { ALOGE("Unable to start MDnsSdListener (%s)", strerror(errno)); exit(1); } FwmarkServer fwmarkServer(&gCtls->netCtrl, &gCtls->eventReporter, &gCtls->trafficCtrl); if (fwmarkServer.startListener()) { ALOGE("Unable to start FwmarkServer (%s)", strerror(errno)); exit(1); } Stopwatch subTime; status_t ret; if ((ret = NetdNativeService::start()) != android::OK) { ALOGE("Unable to start NetdNativeService: %d", ret); exit(1); } ALOGI("Registering NetdNativeService: %.1fms", subTime.getTimeAndReset()); /* * Now that we're up, we can respond to commands. Starting the listener also tells * NetworkManagementService that we are up and that our binder interface is ready. */ if (cl.startListener()) { ALOGE("Unable to start CommandListener (%s)", strerror(errno)); exit(1); } ALOGI("Starting CommandListener: %.1fms", subTime.getTimeAndReset()); write_pid_file(); // Now that netd is ready to process commands, advertise service // availability for HAL clients. NetdHwService mHwSvc; if ((ret = mHwSvc.start()) != android::OK) { ALOGE("Unable to start NetdHwService: %d", ret); exit(1); } ALOGI("Registering NetdHwService: %.1fms", subTime.getTimeAndReset()); ALOGI("Netd started in %dms", static_cast<int>(s.timeTaken())); IPCThreadState::self()->joinThreadPool(); ALOGI("Netd exiting"); remove_pid_file(); exit(0); }
CommandListener
用于接收处理来自上层NetworkManagementService
指令, 在netd
启动时, 会监听netd
这个socket, 并允许最多4个客户端请求的处理,netd启动完成后, 就可以处理来自中间层的指令请求以及与内核进行交互了。
2.netd与NetworkManagerService的交互
SystemServer
进程启动时, 创建NetworkManagementService
(以下简称(NMS
)), 此时NMS
会主动与netd
建立socket链接:
// SystemServer.java if (!disableNetwork) { traceBeginAndSlog("StartNetworkManagementService"); try { networkManagement = NetworkManagementService.create(context); ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement); } catch (Throwable e) { reportWtf("starting NetworkManagement Service", e); } traceEnd(); }
创建NMS时, 启动一个新的线程用于与netd通信,
static NetworkManagementService create(Context context, String socket) throws InterruptedException { final NetworkManagementService service = new NetworkManagementService(context, socket); final CountDownLatch connectedSignal = service.mConnectedSignal; if (DBG) Slog.d(TAG, "Creating NetworkManagementService"); service.mThread.start(); if (DBG) Slog.d(TAG, "Awaiting socket connection"); connectedSignal.await(); service.connectNativeNetdService(); return service; } private NetworkManagementService(Context context, String socket) { mContext = context; // make sure this is on the same looper as our NativeDaemonConnector for sync purposes mFgHandler = new Handler(FgThread.get().getLooper()); // Don't need this wake lock, since we now have a time stamp for when // the network actually went inactive. (It might be nice to still do this, // but I don't want to do it through the power manager because that pollutes the // battery stats history with pointless noise.) //PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); PowerManager.WakeLock wl = null; //pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, NETD_TAG); mConnector = new NativeDaemonConnector( new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160, wl, FgThread.get().getLooper()); mThread = new Thread(mConnector, NETD_TAG); mDaemonHandler = new Handler(FgThread.get().getLooper()); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); LocalServices.addService(NetworkManagementInternal.class, new LocalService()); synchronized (mTetheringStatsProviders) { mTetheringStatsProviders.put(new NetdTetheringStatsProvider(), "netd"); } }
NMS通过NativeDaemonConnector
与netd
建立socket通信, NativeDaemonConnector
主要做两个事情:
- 与
netd
建立一个数据链接 - 不断读取socket中的数据流: 一种是
netd
主动上报的命令, 一种是NMS发送给netd
后的指令的响应@Override public void run() { mCallbackHandler = new Handler(mLooper, this); while (true) { try { listenToSocket(); } catch (Exception e) { loge("Error in NativeDaemonConnector: " + e); SystemClock.sleep(5000); } } } private void listenToSocket() throws IOException { LocalSocket socket = null; try { socket = new LocalSocket(); LocalSocketAddress address = determineSocketAddress(); socket.connect(address); InputStream inputStream = socket.getInputStream(); synchronized (mDaemonLock) { mOutputStream = socket.getOutputStream(); } mCallbacks.onDaemonConnected(); FileDescriptor[] fdList = null; byte[] buffer = new byte[BUFFER_SIZE]; int start = 0; while (true) { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); if (count < 0) { loge("got " + count + " reading with start = " + start); break; } fdList = socket.getAncillaryFileDescriptors(); // Add our starting point to the count and reset the start. count += start; start = 0; for (int i = 0; i < count; i++) { if (buffer[i] == 0) { // Note - do not log this raw message since it may contain // sensitive data final String rawEvent = new String( buffer, start, i - start, StandardCharsets.UTF_8); boolean releaseWl = false; try { final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(rawEvent, fdList); log("RCV <- {" + event + "}"); if (event.isClassUnsolicited()) { Message msg = mCallbackHandler.obtainMessage( event.getCode(), uptimeMillisInt(), 0, event.getRawEvent()); if (mCallbackHandler.sendMessage(msg)) { releaseWl = false; } } else { mResponseQueue.add(event.getCmdNumber(), event); } } catch (IllegalArgumentException e) { log("Problem parsing message " + e); } finally { if (releaseWl) { mWakeLock.release(); } } start = i + 1; } } // We should end at the amount we read. If not, compact then // buffer and read again. if (start != count) { final int remaining = BUFFER_SIZE - start; System.arraycopy(buffer, start, buffer, 0, remaining); start = remaining; } else { start = 0; } } } catch (IOException ex) { loge("Communications error: " + ex); throw ex; } finally { synchronized (mDaemonLock) { if (mOutputStream != null) { try { loge("closing stream for " + mSocket); mOutputStream.close(); } catch (IOException e) { loge("Failed closing output stream: " + e); } mOutputStream = null; } } try { if (socket != null) { socket.close(); } } catch (IOException ex) { loge("Failed closing socket: " + ex); } } }
socket链接建立完成之后, NMS与
netd
可以相互通信, 发送指令与数据了. NMS通过NativeDaemonConnector
执行相应的指令, 比如NMS设置网络接口的配置(打开/关闭网口):
@Override public void setInterfaceConfig(String iface, InterfaceConfiguration cfg) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); LinkAddress linkAddr = cfg.getLinkAddress(); if (linkAddr == null || linkAddr.getAddress() == null) { throw new IllegalStateException("Null LinkAddress given"); } final Command cmd = new Command("interface", "setcfg", iface, linkAddr.getAddress().getHostAddress(), linkAddr.getPrefixLength()); for (String flag : cfg.getFlags()) { cmd.appendArg(flag); } try { mConnector.execute(cmd); } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); }
NativeDaemonConnector
会将每个指令都指定一个唯一的序列, 并将其响应放到一个阻塞队列, 等待netd
返回指令的结果, 如果超过指定的超时时间, 则抛出一个超时的异常.
在第一部分时, 讲到SocketListener
拿到上层发过来的指令后, 会将其分发给对应的指令类进行处理(看SocketListener
的子类FrameworkListener
):
3.NETD与内核进行交互
NETD通过netlink
事件与内核进行消息的交换.在第一部分时看到, netd
启动时, 会配置socket与内核进行通信:
- netlink事件
NETLINK_KOBJECT_UEVENT
: 用于内核向netd
发生消息, 如网口的状态变化; - netlink事件
NETLINK_ROUTE
:用于接收路由信息, 如路由表的更新与删除; - netlink事件
NETLINK_NFLOG
:用于接收数据流量使用配额的消息, 如数据使用超限; - netlink事件
NETLINK_NETFILTER
用于接收包过滤(netfilter)的消息;int NetlinkManager::start() { if ((mUeventHandler = setupSocket(&mUeventSock, NETLINK_KOBJECT_UEVENT, 0xffffffff, NetlinkListener::NETLINK_FORMAT_ASCII, false)) == NULL) { return -1; } if ((mRouteHandler = setupSocket(&mRouteSock, NETLINK_ROUTE, RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE | (1 << (RTNLGRP_ND_USEROPT - 1)), NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) { return -1; } if ((mQuotaHandler = setupSocket(&mQuotaSock, NETLINK_NFLOG, NFLOG_QUOTA_GROUP, NetlinkListener::NETLINK_FORMAT_BINARY, false)) == NULL) { ALOGW("Unable to open qlog quota socket, check if xt_quota2 can send via UeventHandler"); // TODO: return -1 once the emulator gets a new kernel. } if ((mStrictHandler = setupSocket(&mStrictSock, NETLINK_NETFILTER, 0, NetlinkListener::NETLINK_FORMAT_BINARY_UNICAST, true)) == NULL) { ALOGE("Unable to open strict socket"); // TODO: return -1 once the emulator gets a new kernel. } return 0; }
每一个netlink的socket都会新建一个
NetlinkHandler
, 用于处理内核的消息, 并将该消息广播给上层:void NetlinkHandler::onEvent(NetlinkEvent *evt) { const char *subsys = evt->getSubsystem(); if (!subsys) { ALOGW("No subsystem found in netlink event"); return; } if (!strcmp(subsys, "net")) { NetlinkEvent::Action action = evt->getAction(); const char *iface = evt->findParam("INTERFACE"); if (action == NetlinkEvent::Action::kAdd) { notifyInterfaceAdded(iface); } else if (action == NetlinkEvent::Action::kRemove) { notifyInterfaceRemoved(iface); } else if (action == NetlinkEvent::Action::kChange) { evt->dump(); notifyInterfaceChanged("nana", true); } else if (action == NetlinkEvent::Action::kLinkUp) { notifyInterfaceLinkChanged(iface, true); } else if (action == NetlinkEvent::Action::kLinkDown) { notifyInterfaceLinkChanged(iface, false); } else if (action == NetlinkEvent::Action::kAddressUpdated || action == NetlinkEvent::Action::kAddressRemoved) { const char *address = evt->findParam("ADDRESS"); const char *flags = evt->findParam("FLAGS"); const char *scope = evt->findParam("SCOPE"); if (action == NetlinkEvent::Action::kAddressRemoved && iface && address) { // Note: if this interface was deleted, iface is "" and we don't notify. SockDiag sd; if (sd.open()) { char addrstr[INET6_ADDRSTRLEN]; strncpy(addrstr, address, sizeof(addrstr)); char *slash = strchr(addrstr, '/'); if (slash) { *slash = ' '; } int ret = sd.destroySockets(addrstr); if (ret < 0) { ALOGE("Error destroying sockets: %s", strerror(ret)); } } else { ALOGE("Error opening NETLINK_SOCK_DIAG socket: %s", strerror(errno)); } } if (iface && iface[0] && address && flags && scope) { notifyAddressChanged(action, address, iface, flags, scope); } } else if (action == NetlinkEvent::Action::kRdnss) { const char *lifetime = evt->findParam("LIFETIME"); const char *servers = evt->findParam("SERVERS"); if (lifetime && servers) { notifyInterfaceDnsServers(iface, lifetime, servers); } } else if (action == NetlinkEvent::Action::kRouteUpdated || action == NetlinkEvent::Action::kRouteRemoved) { const char *route = evt->findParam("ROUTE"); const char *gateway = evt->findParam("GATEWAY"); const char *iface = evt->findParam("INTERFACE"); if (route && (gateway || iface)) { notifyRouteChange(action, route, gateway, iface); } } } else if (!strcmp(subsys, "qlog") || !strcmp(subsys, "xt_quota2")) { const char *alertName = evt->findParam("ALERT_NAME"); const char *iface = evt->findParam("INTERFACE"); notifyQuotaLimitReached(alertName, iface); } else if (!strcmp(subsys, "strict")) { const char *uid = evt->findParam("UID"); const char *hex = evt->findParam("HEX"); notifyStrictCleartext(uid, hex); } else if (!strcmp(subsys, "xt_idletimer")) { const char *label = evt->findParam("INTERFACE"); const char *state = evt->findParam("STATE"); const char *timestamp = evt->findParam("TIME_NS"); const char *uid = evt->findParam("UID"); if (state) notifyInterfaceClassActivity(label, !strcmp("active", state), timestamp, uid); } }
监听内核网络状态并回调:
private void listenToSocket() throws IOException { LocalSocket socket = null; try { socket = new LocalSocket(); LocalSocketAddress address = determineSocketAddress(); socket.connect(address); InputStream inputStream = socket.getInputStream(); synchronized (mDaemonLock) { mOutputStream = socket.getOutputStream(); } mCallbacks.onDaemonConnected(); FileDescriptor[] fdList = null; byte[] buffer = new byte[BUFFER_SIZE]; int start = 0; while (true) { int count = inputStream.read(buffer, start, BUFFER_SIZE - start); if (count < 0) { loge("got " + count + " reading with start = " + start); break; } fdList = socket.getAncillaryFileDescriptors(); // Add our starting point to the count and reset the start. count += start; start = 0; for (int i = 0; i < count; i++) { if (buffer[i] == 0) { // Note - do not log this raw message since it may contain // sensitive data final String rawEvent = new String( buffer, start, i - start, StandardCharsets.UTF_8); boolean releaseWl = false; try { final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(rawEvent, fdList); log("RCV <- {" + event + "}"); if (event.isClassUnsolicited()) { // TODO: migrate to sending NativeDaemonEvent instances if (mCallbacks.onCheckHoldWakeLock(event.getCode()) && mWakeLock != null) { mWakeLock.acquire(); releaseWl = true; } Message msg = mCallbackHandler.obtainMessage( event.getCode(), uptimeMillisInt(), 0, event.getRawEvent()); if (mCallbackHandler.sendMessage(msg)) { releaseWl = false; } } else { mResponseQueue.add(event.getCmdNumber(), event); } } catch (IllegalArgumentException e) { log("Problem parsing message " + e); } finally { if (releaseWl) { mWakeLock.release(); } } start = i + 1; } } if (start == 0) { log("RCV incomplete"); } // We should end at the amount we read. If not, compact then // buffer and read again. if (start != count) { final int remaining = BUFFER_SIZE - start; System.arraycopy(buffer, start, buffer, 0, remaining); start = remaining; } else { start = 0; } } } catch (IOException ex) { loge("Communications error: " + ex); throw ex; } finally { synchronized (mDaemonLock) { if (mOutputStream != null) { try { loge("closing stream for " + mSocket); mOutputStream.close(); } catch (IOException e) { loge("Failed closing output stream: " + e); } mOutputStream = null; } } try { if (socket != null) { socket.close(); } } catch (IOException ex) { loge("Failed closing socket: " + ex); } } }
回调到NetworkManagementService.java中的onEvent方法并通知之前创建好的Observers
@Override public boolean onEvent(int code, String raw, String[] cooked) { String errorMessage = String.format("Invalid event from daemon (%s)", raw); switch (code) { case NetdResponseCode.InterfaceChange: /* * a network interface change occured * Format: "NNN Iface added <name>" * "NNN Iface removed <name>" * "NNN Iface changed <name> <up/down>" * "NNN Iface linkstatus <name> <up/down>" */ if (cooked.length < 4 || !cooked[1].equals("Iface")) { throw new IllegalStateException(errorMessage); } if (cooked[2].equals("added")) { notifyInterfaceAdded(cooked[3]); return true; } else if (cooked[2].equals("removed")) { notifyInterfaceRemoved(cooked[3]); return true; } else if (cooked[2].equals("changed") && cooked.length == 5) { notifyInterfaceStatusChanged(cooked[3], cooked[4].equals("up")); return true; } else if (cooked[2].equals("linkstate") && cooked.length == 5) { notifyInterfaceLinkStateChanged(cooked[3], cooked[4].equals("up")); return true; } throw new IllegalStateException(errorMessage); // break; case NetdResponseCode.BandwidthControl: /* * Bandwidth control needs some attention * Format: "NNN limit alert <alertName> <ifaceName>" */ if (cooked.length < 5 || !cooked[1].equals("limit")) { throw new IllegalStateException(errorMessage); } if (cooked[2].equals("alert")) { notifyLimitReached(cooked[3], cooked[4]); return true; } throw new IllegalStateException(errorMessage); // break; case NetdResponseCode.InterfaceClassActivity: /* * An network interface class state changed (active/idle) * Format: "NNN IfaceClass <active/idle> <label>" */ if (cooked.length < 4 || !cooked[1].equals("IfaceClass")) { throw new IllegalStateException(errorMessage); } long timestampNanos = 0; int processUid = -1; if (cooked.length >= 5) { try { timestampNanos = Long.parseLong(cooked[4]); if (cooked.length == 6) { processUid = Integer.parseInt(cooked[5]); } } catch(NumberFormatException ne) {} } else { timestampNanos = SystemClock.elapsedRealtimeNanos(); } boolean isActive = cooked[2].equals("active"); notifyInterfaceClassActivity(Integer.parseInt(cooked[3]), isActive ? DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH : DataConnectionRealTimeInfo.DC_POWER_STATE_LOW, timestampNanos, processUid, false); return true; // break; case NetdResponseCode.InterfaceAddressChange: /* * A network address change occurred * Format: "NNN Address updated <addr> <iface> <flags> <scope>" * "NNN Address removed <addr> <iface> <flags> <scope>" */ if (cooked.length < 7 || !cooked[1].equals("Address")) { throw new IllegalStateException(errorMessage); } String iface = cooked[4]; LinkAddress address; try { int flags = Integer.parseInt(cooked[5]); int scope = Integer.parseInt(cooked[6]); address = new LinkAddress(cooked[3], flags, scope); } catch(NumberFormatException e) { // Non-numeric lifetime or scope. throw new IllegalStateException(errorMessage, e); } catch(IllegalArgumentException e) { // Malformed/invalid IP address. throw new IllegalStateException(errorMessage, e); } if (cooked[2].equals("updated")) { notifyAddressUpdated(iface, address); } else { notifyAddressRemoved(iface, address); } return true; // break; case NetdResponseCode.InterfaceDnsServerInfo: /* * Information about available DNS servers has been received. * Format: "NNN DnsInfo servers <interface> <lifetime> <servers>" */ long lifetime; // Actually a 32-bit unsigned integer. if (cooked.length == 6 && cooked[1].equals("DnsInfo") && cooked[2].equals("servers")) { try { lifetime = Long.parseLong(cooked[4]); } catch (NumberFormatException e) { throw new IllegalStateException(errorMessage); } String[] servers = cooked[5].split(","); notifyInterfaceDnsServerInfo(cooked[3], lifetime, servers); } return true; // break; case NetdResponseCode.RouteChange: /* * A route has been updated or removed. * Format: "NNN Route <updated|removed> <dst> [via <gateway] [dev <iface>]" */ if (!cooked[1].equals("Route") || cooked.length < 6) { throw new IllegalStateException(errorMessage); } String via = null; String dev = null; boolean valid = true; for (int i = 4; (i + 1) < cooked.length && valid; i += 2) { if (cooked[i].equals("dev")) { if (dev == null) { dev = cooked[i+1]; } else { valid = false; // Duplicate interface. } } else if (cooked[i].equals("via")) { if (via == null) { via = cooked[i+1]; } else { valid = false; // Duplicate gateway. } } else { valid = false; // Unknown syntax. } } if (valid) { try { // InetAddress.parseNumericAddress(null) inexplicably returns ::1. InetAddress gateway = null; if (via != null) gateway = InetAddress.parseNumericAddress(via); RouteInfo route = new RouteInfo(new IpPrefix(cooked[3]), gateway, dev); notifyRouteChange(cooked[2], route); return true; } catch (IllegalArgumentException e) {} } throw new IllegalStateException(errorMessage); // break; case NetdResponseCode.StrictCleartext: final int uid = Integer.parseInt(cooked[1]); final byte[] firstPacket = HexDump.hexStringToByteArray(cooked[2]); try { ActivityManager.getService().notifyCleartextNetwork(uid, firstPacket); } catch (RemoteException ignored) { } break; default: break; } return false; } }
三、Netd测试工具ndc
ndc的原理其实就是通过socket连接上netd进行交互,这部分可以从源代码体现:
ndc.c
int main(int argc, char **argv) { //argv[1]可以是socket name. if ((sock = socket_local_client(argv[1], ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)) < 0) { //如果不传,那么默认就是name为"netd"的socket if ((sock = socket_local_client("netd", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM)) < 0) { fprintf(stderr, "Error connecting (%s) ", strerror(errno)); exit(4); } } exit(do_cmd(sock, argc-cmdOffset, &(argv[cmdOffset]))); }
static int do_cmd(int sock, int argc, char **argv) { //命令参数最终通过socket发送给netd服务进程处理 if (write(sock, final_cmd, strlen(final_cmd) + 1) < 0) { int res = errno; perror("write"); free(final_cmd); return res; } }
监听:
查看可用命令表:
console:/ # ndc interface list
110 0 dummy0
110 0 eth0
110 0 ip6_vti0
110 0 ip6tnl0
110 0 ip_vti0
110 0 lo
200 0 Interface list completed
例如: $ adb shell ndc interface list
interface | list |
readrxcounter| readtxcounter | |
getthrottle<iface><”rx|tx”> | |
setthrottle<iface><rx_kbps|tx_kbps> | |
driver<iface><cmd><args> | |
route<add|remove> <iface> <”default|secondary”><dst> <prefix> <gateway> | |
list_ttys | |
ipfwd | status |
enable|disable | |
tether | status |
start-reverse|stop-reverse | |
stop< | |
start<addr_1 addr_2 addr_3 addr_4 [addr_2n]> | |
interface<add|remove|list> | |
dnslist | |
dnsset <addr_1> < addr_2> | |
nat | <enable|disable><iface><extface><addrcnt><nated-ipaddr/prelength> |
pppd | attach<tty> <addr_local> <add_remote> <dns_1><dns_2> |
detach<tty> | |
softap | startap|stopap |
fwreload<iface> <AP|P2P> | |
clients | |
status | |
set<iface> <SSID> <wpa-psk|wpa2-psk|open> [<key><channel> <preamble><max SCB>] | |
resolver | setdefaultif<iface> |
setifdns<iface><dns_1><dns_2> | |
flushdefaultif | |
flushif<iface> | |
bandwith | enable|disable |
removequota|rq | |
getquota|gq | |
getiquota|giq<iface> | |
setquota|sq<bytes> <iface> | |
removequota|rqs<iface> | |
removeiiquota|riq<iface> | |
setiquota|sq<interface><bytes> | |
addnaughtyapps|ana<appUid> | |
removenaughtyapps|rna<appUid> | |
setgolbalalert|sga<bytes> | |
debugsettetherglobalalert|dstga<iface0><iface1> | |
setsharedalert|ssa<bytes> | |
removesharedalert|rsa | |
setinterfacealert|sia<iface><bytes> | |
removeinterfacealert|ria<iface> | |
gettetherstats|gts<iface0><iface1> | |
idletimer | enable|disable |
add|remove<iface><timeout><classLabel> | |
firewall | enable|disable|is_enabled |
set_interface_rule<rmnet0><allow|deny> | |
set_egress_source_rule<ip_addr><allow|deny> | |
set_egress_dest_rule<ip_addr><port><allow|deny> | |
set_uid_rule<uid><allow|deny> | |
clatd | stop|status|start<iface> |