一、DHCP流程
分析netd之前先了解一下網絡自動獲取IP流程,借鑒下圖流程查看代碼:
(1)WIFI掃描到可用網絡后進行連接,代碼路徑:\frameworks\opt\net\wifi\service\java\com\android\server\wifi\WifiStateMachine.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 = '\0'; } 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)\n", 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> |