Android 9.0 网络之netd详解

news2025/1/10 21:48:11

一、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,参考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.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通过NativeDaemonConnectornetd建立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

 

interfacelist
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
ipfwdstatus
enable|disable
tetherstatus
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>
pppdattach<tty> <addr_local> <add_remote> <dns_1><dns_2>
detach<tty>
softapstartap|stopap
fwreload<iface> <AP|P2P>
clients
status
set<iface> <SSID> <wpa-psk|wpa2-psk|open> [<key><channel> <preamble><max SCB>]
resolversetdefaultif<iface>
setifdns<iface><dns_1><dns_2>
flushdefaultif
flushif<iface>
bandwithenable|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>
idletimerenable|disable
add|remove<iface><timeout><classLabel>
firewallenable|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>
clatdstop|status|start<iface>
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/981821.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机安全学习笔记(IV):基于角色的访问控制 - RBAC

RBAC(Role-Based Access Control)基于用户在系统中设定的角色而不是用户的身份。一般来说&#xff0c;RBAC模型定义角色为组织中的一项工作职责&#xff0c;RBAC系统给角色而不是给单独的用户分配访问权。用户根据他们的职责被静态地或动态地分配给不同的角色。 RBAC模型间的关…

STM32串口DMA+空闲中断接收数据-基于CubeMX创建

串口空闲中断&#xff08;UART_IT_IDLE&#xff09;&#xff1a;串口接收数据&#xff0c;超过一个字节的时间内没有再接收到数据的时候发生的 好处&#xff1a;与传统的判断\r,\n为结束位相比&#xff0c;空闲中断判断串口数据接收完毕准确且迅速 缺点&#xff1a;串口持续不…

分布式系统常用的模式

分布式系统常用的模式 Ambassador 名称&#xff1a;“大使”模式 介绍&#xff1a;作为应用程序和其他服务的“中间人”&#xff0c;负责应用程序和其他服务之间的通信&#xff0c;包括日志、监控或重试处理等任务。 举例&#xff1a;K8S使用Envoy作为一个“大使”来简化服务…

[C语言] 结构体

结构体类型的声明 结构体初始化 结构体成员访问 结构体传参 目录 1. 结构体的声明 1.1 结构的基础知识 1.2 结构的声明 1.3 结构成员的类型 1.4 结构体变量的定义和初始化 2. 结构体成员的访问 3. 结构体传参 1. 结构体的声明 1.1 结构的基础知识 结构是一些值的集…

Windows系统管理一:操作系统概述

计算机系统的层次结构 五大基本功能 处理器管理 其工作主要是进程调度&#xff0c;在单用户单任务的情况下&#xff0c;必处理器仅为一个用户的一个任务所独占&#xff0c;进程管理的工作十分简单。但在多道程序或多用户的情况下&#xff0c;组织多个作业或任务时&#xff0c…

归并排序的递归和非递归实现

归并排序 平均时间复杂度O(n*logn),空间复杂度O(n) 递归实现 思路&#xff1a; 分治法 即先使每个子序列有序&#xff0c;再使子序列段间有序。若将两个有序表合并成一个有序表&#xff0c;称为二路归并。归并排序是一种稳定的排序方法 code&#xff1a; //递归版public st…

Linux 安装 JDK

要在Linux上安装JDK 1&#xff0c;按照以下步骤进行操作&#xff1a; 1. 下载JDK安装文件&#xff1a;首先&#xff0c;你需要找到适用于你操作系统的JDK安装文件&#xff08;tar.gz或tar.bz2格式&#xff09;。你可以从Oracle官方网站或其他可信的来源下载该文件。 2. 解压…

Unity 自制转表插件,高效便捷,无反射代码

XlsxTransverter Unity 高效转表工具 剔除了上一版本的反射用法 使用代码生成的 Decode方法来自解析 并与内存stream流保持一一对应 极大的提高了读表的访问速度 字段和字节流 保持一一对应 程序自动生成 decode方法 内存布局 Unity读表插件 https://github.com/badApple001/…

conda环境安装opencv带cuda版本

主要是cmake编译选项需要修改 以下两个选项按照自己情况修改 -D OPENCV_EXTRA_MODULES_PATH../opencv_contrib/modules \ -D CUDA_TOOLKIT_ROOT_DIR/usr/local/cuda-12.2 \ 其中/home/lixin/anaconda3/envs/stereo 改成你自己的conda环境 cmake -D CMAKE_BUILD_TYPER…

BUUCTF-WEB-[ACTF2020 新生赛]Includel

打开靶机 点击tips 利用Burp抓包&#xff0c;未见异常 但发现了响应头是 PHP/7.3.13 想到了"php://input"伪协议POST发送PHP代码 构建Payload&#xff1a;?filephp://filter/readconvert.base64-encode/resourceflag.php 这里需要注意的是使用php://filter伪协议…

PHP对接阿里云虚拟号的实现(号码隐私保护)

fastadmin 封装框架 实现功能&#xff1a;AXN隐私号绑定、解绑&#xff1b; 场景&#xff1a;为店铺手机号开通虚拟号&#xff0c;用户联系店铺展示虚拟号码&#xff1b; 官方开放文档地址&#xff1a;https://help.aliyun.com/document_detail/59655.html?spma2c4g.111742…

医院空调冷热源设计方案VR元宇宙模拟演练的独特之处

作为一个备受关注的技术-元宇宙&#xff0c;毋庸置疑的是&#xff0c;因为建筑本身契合了时尚、前卫、高端、虚拟、科幻、泛在、协作、互通等元素特征&#xff0c;因此在建筑行业更需要元宇宙&#xff0c;以居民建筑环境冷热源设计来说&#xff0c;元宇宙会打破既定的现实阻碍和…

【MySQL】MySQL 数据库基础

MySQL 数据库基础 一. 数据库的操作1. 显示当前的数据库2. 创建数据库3. 使用数据库4. 删除数据库 二. 常用数据类型1. 数据类型2. 字符串类型3. 日期类型 三. 表的操作1. 查看表结构2. 创建表4. 删除表 一. 数据库的操作 1. 显示当前的数据库 SHOW DATABASES;MySQL 不区分大…

PHP设备检验系统Dreamweaver开发mysql数据库web结构php编程计算机网页代码

一、源码特点 PHP设备检验系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 下载地址 https://download.csdn.net/download/qq_41221322/88306259 php设备检验系统1 …

海康机器人工业相机IP设置方式

文章目录 一.什么情况下需要修改相机IP二.网口相机IP地址修改方式 一.什么情况下需要修改相机IP 二.网口相机IP地址修改方式

IO多路转接poll和epoll

文章目录 pollpoll和select的区别poll的底层实现方式什么是epollepoll执行原理epoll接口struct epoll_event是什么结构 epoll的两种触发方式epoll底层实现epoll总结 poll #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);参数&#xff1a; …

大数据(八):Pandas的基础应用详解(五)

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

MySQL表的CURD

CRUD : Create(创建), Retrieve(读取)&#xff0c;Update(更新)&#xff0c;Delete&#xff08;删除&#xff09; Create 语法 INSERT [INTO] table_name [(column [, column] ...)] VALUES (value_list) [, (value_list)] ... value_list: value, [, value] ... 示例&#x…

第 2 章 线性表(学生健康登记表实现)

1. 示例代码 1) status.h /* DataStructure 预定义常量和类型头文件 */#ifndef STATUS_H #define STATUS_H/* 函数结果状态码 */ #define TRUE 1 /* 返回值为真 */ #define FALSE 0 /* 返回值为假 */ #define RET_OK 0 /* 返回值正确 */ #define INFEASI…

Datax抽取mysql的bit类型数据

背景&#xff1a;使用datax抽取mysql的一张表&#xff0c;里面有两个bit类型的字段&#xff0c;抽取出来显示如下&#xff1a; 需要在抽取reader里面进行处理配置 最终生成的datax的json文件reader的配置会转换为具体的数值 最终查询效果&#xff1a;