一、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 {@Overridepublic 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 broadcastsetNetworkDetailedState(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 countersgetWifiLinkLayerStats();}@Overridepublic 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;@Overridepublic void enter() {ApfFilter.ApfConfiguration apfConfig = new ApfFilter.ApfConfiguration();apfConfig.apfCapabilities = mConfiguration.mApfCapabilities;apfConfig.multicastFilter = mMulticastFiltering;// Get the Configuration for ApfFilter from ContextapfConfig.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 {@Overridepublic 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;@Overridepublic void enter() {super.enter();mController.sendMessage(CMD_PRE_DHCP_ACTION);}@Overridepublic 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();}@Overridepublic 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_IDENTIFIERINADDR_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:
@Overridepublic 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/netdclass mainsocket netd stream 0660 root systemsocket dnsproxyd stream 0660 root inetsocket mdns stream 0660 root systemsocket fwmarkd stream 0660 root inetonrestart restart zygoteonrestart restart zygote_secondary
启动netd时, 会创建四个socket,用于其他进程与netd进行通信:
netd
: 主要与Framework的NetworkManagementService
交互, 用于控制网口状态, 路由表dnsproxyd
: DNS代理的控制与配置,用于私有DNS(DNS Over TLS)的请求转发mdns
: 多播DNS(Multicast DNS,参考RFCRFC 6762 - Multicast DNS), 用于基于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.javaif (!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 purposesmFgHandler = 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 datafinal 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 datafinal 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 instancesif (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
@Overridepublic 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> |