(八十七) WiFi & DHCP

参考:

1)DHCP的一些解释

2)DHCP详细工作过程

1.wifi的连接过程

WiFi的连接过程直到现在还没完全梳理清楚,大致知道其中几块,app到framework的流程大致梳理过,后续是到wpa_supplicant的四次握手,这后面不知道了,接着会到DhcpClient的ip地址获取。本文主要学习下WiFi连接过程中的dhcp。(有时间好好梳理下)

 

 

2.WiFi连接过程中的DHCP

2.1 DHCP基础知识

以下参考自DHCP的一些解释,谢谢分享,细节可以参考DHCP详细工作过程。

2.1.1 DHCP

DHCP,动态主机配置协议,前身是BOOTP协议,是一个局域网的网络协议,使用UDP协议工作,常用的2个端口:67(DHCP server),68(DHCP client)。DHCP通常被用于局域网环境,主要作用是集中的管理、分配IP地址,使client动态的获得IP地址、Gateway地址、DNS服务器地址等信息,并能够提升地址的使用率。简单来说,DHCP就是一个不需要账号密码登录的、自动给内网机器分配IP地址等信息的协议。

2.1.2 DHCP协议中的报文

  • DHCP DISCOVER :客户端开始DHCP过程发送的包,是DHCP协议的开始
  • DHCP OFFER :服务器接收到DHCP DISCOVER之后做出的响应,它包括了给予客户端的IP(yiaddr)、客户端的MAC地址、租约过期时间、服务器的识别符以及其他信息
  • DHCP REQUEST :客户端对于服务器发出的DHCP OFFER所做出的响应。在续约租期的时候同样会使用。
  • DHCP ACK :服务器在接收到客户端发来的DHCP REQUEST之后发出的成功确认的报文。在建立连接的时候,客户端在接收到这个报文之后才会确认分配给它的IP和其他信息可以被允许使用。
  • DHCP NAK :DHCP ACK的相反的报文,表示服务器拒绝了客户端的请求。
  • DHCP RELEASE :一般出现在客户端关机、下线等状况。这个报文将会使DHCP服务器释放发出此报文的客户端的IP地址
  • DHCP INFORM :客户端发出的向服务器请求一些信息的报文
  • DHCP DECLINE :当客户端发现服务器分配的IP地址无法使用(如IP地址冲突时),将发出此报文,通知服务器禁止
    使用该IP地址。

DHCP工作流程:

(八十七) WiFi & DHCP_第1张图片

2.1.3 DHCP流程

  • 静态租约表:对应一个静态租约存储文件,server运行时从文件中读取静态租约表。
  • 动态租约表:对应一个周期存储文件,server周期性将租约表存进该文件,在程序开始时将会读取上次存放的租约表。(租约表记录了当前所有分配的租约,包括静态链接的)。

原则上DHCP SERVER是一直处在被动接受请求的状态,当有客户端请求时,服务器会读取获得客户端当前所在的状态以及客户端的信息,并在静态租约表和动态租约表中进行检索找到相应的表项,再根据客户端的状态执行不同的回复。当收到客户端的首次请求时,DHCP服务器先查找静态租约表;若存在请求的表项,返回这个客户的静态IP地址;否则,从IP地址池中选择可用的IP分配给客户,并添加信息到动态数据库中。此外,服务器将会周期性的刷新租约表写入文件存档,在这个过程中会顺便对动态租约表进行租期检查。

执行回复动作:

  • DHCPOFFER:
    • 静态租用:首先匹配MAC地址,看是否能在静态租约表中找到对应的项,若能找到就把IP分配给他。静态表中的IP不能被其他客户使用。   
    • 动态租用:
      • 1.server试图分配给client上次分配过的IP,在这之前检查这个IP是否正在使用。
      • 2.discover中含有request ip 时,检查该IP是否在地址池范围,是否正在使用,是否到期,是否是静态IP,网络上是否已经存在。
      • 3.discover不含request ip,从地址池上寻找一个最小的可用IP分配。
  • DHCPACK: 根据是否含有request ip和server ip识别客户端现在init_reboot,selecting,renewing/rebinding中的哪个状态,并根据以下规则执行DHCPACK回复:
    • 1.若client处于selecting状态,验证request ip和server ip是否同服务器中的匹配。
    • 2.若client处于init_reboot状态,验证request ip是否符合租约记录。
    • 3.若client处于renewing/rebinding状态,验证client ip address是否符合租约记录。
  • DHNAK:
    • 1.请求的IP是静态IP,但是MAC地址无法与其对应。
    • 2.上面DHCPACK中验证失败。  
  • 服务器还可能会收到其他包:
    • DHCPDECLINE:server会把租约表中相关client硬件地址置空,并保存这个地址一段时间。
    • DHCPRELEASE:清空租期回收IP。
    • DHCPINFORM:回复DHCPACK,数据包含有关于server的信息。
    • (八十七) WiFi & DHCP_第2张图片

 

2.2 抓取相关log

用小米 mix2 8.0抓取了如下WiFi连接过程中Dhcp log

09-01 09:29:37.136  1561  2458 D DhcpClient: doQuit
09-01 09:29:37.150  1561 26809 D DhcpClient: Receive thread stopped
09-01 09:29:37.152  1561 26806 D DhcpClient: onQuitting
//断开WiFi


09-01 09:29:43.516  1561 27101 D DhcpClient: Receive thread started
09-01 09:29:43.519  1561 27099 D DhcpClient: Broadcasting DHCPDISCOVER
09-01 09:29:43.523  1561 27101 D DhcpClient: Received packet: e4:46:da:6b:f5:50 OFFER, ip /192.168.0.105, mask /255.255.255.0, DNS servers: /192.168.0.1 , gateways [/192.168.0.1] lease time 7200, domain null
09-01 09:29:43.524  1561 27099 D DhcpClient: Got pending lease: IP address 192.168.0.105/24 Gateway 192.168.0.1  DNS servers: [ 192.168.0.1 ] Domains  DHCP server /192.168.0.1 Vendor info null lease 7200 seconds
09-01 09:29:43.524  1561 27099 D DhcpClient: Broadcasting DHCPREQUEST ciaddr=0.0.0.0 request=192.168.0.105 serverid=192.168.0.1
09-01 09:29:43.528  1561 27101 D DhcpClient: Received packet: e4:46:da:6b:f5:50 ACK: your new IP /192.168.0.105, netmask /255.255.255.0, gateways [/192.168.0.1] DNS servers: /192.168.0.1 , lease time 7200
09-01 09:29:43.528  1561 27099 D DhcpClient: Confirmed lease: IP address 192.168.0.105/24 Gateway 192.168.0.1  DNS servers: [ 192.168.0.1 ] Domains  DHCP server /192.168.0.1 Vendor info null lease 7200 seconds
//连接WiFi

光从log来看dhcp分为四步

  1. client广播DHCPDISCOVER
  2. server(AP) 发送packet offer,client接收
  3. client基于之前收到的packet offer广播DHCPREQUEST
  4. server(AP) 发送packet ack,client接收

 

 

2.3 相关源码分析

DhcpClient

首先看到有Receive thread started的打印,看下逻辑

   class ReceiveThread extends Thread {

        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
        private volatile boolean mStopped = false;

        public void halt() {
            mStopped = true;
            closeSockets();  // Interrupts the read() call the thread is blocked in.
        }

        @Override
        public void run() {
            if (DBG) Log.d(TAG, "Receive thread started");
            while (!mStopped) {
                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
                try {
                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
                    DhcpPacket packet = null;
                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
                    if (DBG) Log.d(TAG, "Received packet: " + packet);
                    sendMessage(CMD_RECEIVED_PACKET, packet);
                } catch (IOException|ErrnoException e) {
                    if (!mStopped) {
                        Log.e(TAG, "Read error", e);
                        logError(DhcpErrorEvent.RECEIVE_ERROR);
                    }
                } catch (DhcpPacket.ParseException e) {
                    Log.e(TAG, "Can't parse packet: " + e.getMessage());
                    if (PACKET_DBG) {
                        Log.d(TAG, HexDump.dumpHexString(mPacket, 0, length));
                    }
                    if (e.errorCode == DhcpErrorEvent.DHCP_NO_COOKIE) {
                        int snetTagId = 0x534e4554;
                        String bugId = "31850211";
                        int uid = -1;
                        String data = DhcpPacket.ParseException.class.getName();
                        EventLog.writeEvent(snetTagId, bugId, uid, data);
                    }
                    logError(e.errorCode);
                }
            }
            if (DBG) Log.d(TAG, "Receive thread stopped");
        }
    }

    class DhcpState extends State {
        @Override
        public void enter() {
            clearDhcpState();
            if (initInterface() && initSockets()) {
                mReceiveThread = new ReceiveThread();
                mReceiveThread.start();
            } else {
                notifyFailure();
                transitionTo(mStoppedState);
            }
        }

        @Override
        public void exit() {
            if (mReceiveThread != null) {
                mReceiveThread.halt();  // Also closes sockets.
                mReceiveThread = null;
            }
            clearDhcpState();
        }

        @Override
        public boolean processMessage(Message message) {
            super.processMessage(message);
            switch (message.what) {
                case CMD_STOP_DHCP:
                    transitionTo(mStoppedState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
        }
    }

DhcpClient中也包含了一个状态机,当进入DhcpState中的时候会将ReceiveThread线程启动起来,这个线程包含了一个while循环调用Os.read并解析packet的逻辑,当退出时会将mStopped置为true,线程while循环停止。

顺便看下这个类包含的状态机

    // TODO: Take an InterfaceParams instance instead of an interface name String.
    private DhcpClient(Context context, StateMachine controller, String iface) {
        super(TAG, controller.getHandler());

        mContext = context;
        mController = controller;
        mIfaceName = iface;

        addState(mStoppedState);
        addState(mDhcpState);
            addState(mDhcpInitState, mDhcpState);
            addState(mWaitBeforeStartState, mDhcpState);
            addState(mDhcpSelectingState, mDhcpState);
            addState(mDhcpRequestingState, mDhcpState);
            addState(mDhcpHaveLeaseState, mDhcpState);
                addState(mConfiguringInterfaceState, mDhcpHaveLeaseState);
                addState(mDhcpBoundState, mDhcpHaveLeaseState);
                addState(mWaitBeforeRenewalState, mDhcpHaveLeaseState);
                addState(mDhcpRenewingState, mDhcpHaveLeaseState);
                addState(mDhcpRebindingState, mDhcpHaveLeaseState);
            addState(mDhcpInitRebootState, mDhcpState);
            addState(mDhcpRebootingState, mDhcpState);

        setInitialState(mStoppedState);

(八十七) WiFi & DHCP_第3张图片

接着看下Broadcasting DHCPDISCOVER

    private boolean sendDiscoverPacket() {
        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
                DO_UNICAST, REQUESTED_PARAMS);
        return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
    }

    // DHCP parameters that we request.
    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
        DHCP_SUBNET_MASK,
        DHCP_ROUTER,
        DHCP_DNS_SERVER,
        DHCP_DOMAIN_NAME,
        DHCP_MTU,
        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
        DHCP_LEASE_TIME,
        DHCP_RENEWAL_TIME,
        DHCP_REBINDING_TIME,
        DHCP_VENDOR_INFO,
    };

先看下这个状态类

 /**
     * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
     * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm. If a subclass
     * sets mTimeout to a positive value, then timeout() is called by an AlarmManager alarm mTimeout
     * milliseconds after entering the state. Kicks and timeouts are cancelled when leaving the
     * state.
     *
     * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
     * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
     * sent by the receive thread. They may also set mTimeout and implement timeout.
     */
    abstract class PacketRetransmittingState extends LoggingState {

        private int mTimer;
        protected int mTimeout = 0;

        @Override
        public void enter() {
            super.enter();
            initTimer();
            maybeInitTimeout();
            sendMessage(CMD_KICK);
        }

        @Override
        public boolean processMessage(Message message) {
            super.processMessage(message);
            switch (message.what) {
                case CMD_KICK:
                    sendPacket();
                    scheduleKick();
                    return HANDLED;
                case CMD_RECEIVED_PACKET:
                    receivePacket((DhcpPacket) message.obj);
                    return HANDLED;
                case CMD_TIMEOUT:
                    timeout();
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
        }

        @Override
        public void exit() {
            super.exit();
            mKickAlarm.cancel();
            mTimeoutAlarm.cancel();
        }

        abstract protected boolean sendPacket();
        abstract protected void receivePacket(DhcpPacket packet);
        protected void timeout() {}

        protected void initTimer() {
            mTimer = FIRST_TIMEOUT_MS;
        }

        protected int jitterTimer(int baseTimer) {
            int maxJitter = baseTimer / 10;
            int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
            return baseTimer + jitter;
        }

        protected void scheduleKick() {
            long now = SystemClock.elapsedRealtime();
            long timeout = jitterTimer(mTimer);
            long alarmTime = now + timeout;
            mKickAlarm.schedule(alarmTime);
            mTimer *= 2;
            if (mTimer > MAX_TIMEOUT_MS) {
                mTimer = MAX_TIMEOUT_MS;
            }
        }

        protected void maybeInitTimeout() {
            if (mTimeout > 0) {
                long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
                mTimeoutAlarm.schedule(alarmTime);
            }
        }
    }

scheduleKick的算法好像有点意思,timer默认是2s,经过jitterTimer后是1.8-2.2s,即附加了10%的抖动,然后以这个随机值作为timeout。kick命令在timeout之后发出。接着timer*2的间隙进行重发,用注释的意思来说就是指数抖动退避,上限是128s,即2^7。

这个状态类一进入就开始触发CMD_KICK进行sendPacket和sheduleKick的操作。

 

而之前一直while循环的ReceiveThread线程会触发receivepacket

    class ReceiveThread extends Thread {

        private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
        private volatile boolean mStopped = false;

        public void halt() {
            mStopped = true;
            closeSockets();  // Interrupts the read() call the thread is blocked in.
        }

        @Override
        public void run() {
            if (DBG) Log.d(TAG, "Receive thread started");
            while (!mStopped) {
                int length = 0;  // Or compiler can't tell it's initialized if a parse error occurs.
                try {
                    length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
                    DhcpPacket packet = null;
                    packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
                    if (DBG) Log.d(TAG, "Received packet: " + packet);
                    sendMessage(CMD_RECEIVED_PACKET, packet);

从初始状态StoppedState切换到DhcpInitState就会触发之前分析的sendpacket,然后如果ReceiveThread发送CMD_RECEIVED_PACKET则会触发receivePacket.

    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);
            }
        }
    }
    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;
            }
        }
    }

对应log:

09-01 09:29:43.519  1561 27099 D DhcpClient: Broadcasting DHCPDISCOVER
09-01 09:29:43.523  1561 27101 D DhcpClient: Received packet: e4:46:da:6b:f5:50 OFFER, ip /192.168.0.105, mask /255.255.255.0, DNS servers: /192.168.0.1 , gateways [/192.168.0.1] lease time 7200, domain null
09-01 09:29:43.524  1561 27099 D DhcpClient: Got pending lease: IP address 192.168.0.105/24 Gateway 192.168.0.1  DNS servers: [ 192.168.0.1 ] Domains  DHCP server /192.168.0.1 Vendor info null lease 7200 seconds

对应发出的packet:

    private boolean sendDiscoverPacket() {
        ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
                DhcpPacket.ENCAP_L2, mTransactionId, getSecs(), mHwAddr,
                DO_UNICAST, REQUESTED_PARAMS);
        return transmitPacket(packet, "DHCPDISCOVER", DhcpPacket.ENCAP_L2, INADDR_BROADCAST);
    }

    // DHCP parameters that we request.
    /* package */ static final byte[] REQUESTED_PARAMS = new byte[] {
        DHCP_SUBNET_MASK,
        DHCP_ROUTER,
        DHCP_DNS_SERVER,
        DHCP_DOMAIN_NAME,
        DHCP_MTU,
        DHCP_BROADCAST_ADDRESS,  // TODO: currently ignored.
        DHCP_LEASE_TIME,
        DHCP_RENEWAL_TIME,
        DHCP_REBINDING_TIME,
        DHCP_VENDOR_INFO,
    };

继而进入到DhcpRequestingState状态

    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);
            }
        }

        @Override
        protected void timeout() {
            // After sending REQUESTs unsuccessfully for a while, go back to init.
            transitionTo(mDhcpInitState);
        }
    }

观察到DhcpRequestingState状态有timeout限制

    // This is not strictly needed, since the client is asynchronous and implements exponential
    // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
    // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
    // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
    private static final int DHCP_TIMEOUT_MS    =  36 * SECONDS;

即这个状态18s会触发timeout,2^4+2,即timeout时限内允许发送5次packet。在时限内收到包,如果是DhcpAckPacket,则切换到ConfiguringInterfaceState,否则如果是DhcpNakPacket切换至DhcpInitState。

    class ConfiguringInterfaceState extends LoggingState {
        @Override
        public void enter() {
            super.enter();
            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
        }

        @Override
        public boolean processMessage(Message message) {
            super.processMessage(message);
            switch (message.what) {
                case EVENT_LINKADDRESS_CONFIGURED:
                    transitionTo(mDhcpBoundState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
        }
    }

继续梳理把,mController是IpClient的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;
                }

设置玩ipv4 地址后DhcpClient再发送EVENT_LINKADDRESS_CONFIGURED切换到DhcpBoundState状态

    class ConfiguringInterfaceState extends LoggingState {
        @Override
        public void enter() {
            super.enter();
            mController.sendMessage(CMD_CONFIGURE_LINKADDRESS, mDhcpLease.ipAddress);
        }

        @Override
        public boolean processMessage(Message message) {
            super.processMessage(message);
            switch (message.what) {
                case EVENT_LINKADDRESS_CONFIGURED:
                    transitionTo(mDhcpBoundState);
                    return HANDLED;
                default:
                    return NOT_HANDLED;
            }
        }
    }
  class DhcpBoundState extends LoggingState {
        @Override
        public void enter() {
            super.enter();
            if (mDhcpLease.serverAddress != null && !connectUdpSock(mDhcpLease.serverAddress)) {
                // There's likely no point in going into DhcpInitState here, we'll probably
                // just repeat the transaction, get the same IP address as before, and fail.
                //
                // NOTE: It is observed that connectUdpSock() basically never fails, due to
                // SO_BINDTODEVICE. Examining the local socket address shows it will happily
                // return an IPv4 address from another interface, or even return "0.0.0.0".
                //
                // TODO: Consider deleting this check, following testing on several kernels.
                notifyFailure();
                transitionTo(mStoppedState);
            }

            scheduleLeaseTimers();
            logTimeToBoundState();
        }

通过获取的服务器地址进行udp sock的连接。

    private boolean connectUdpSock(Inet4Address to) {
        try {
            Os.connect(mUdpSock, to, DhcpPacket.DHCP_SERVER);
            return true;
        } catch (SocketException|ErrnoException e) {
            Log.e(TAG, "Error connecting UDP socket", e);
            return false;
        }
    }

先到这里吧。。。

 

3. 总结

(八十七) WiFi & DHCP_第4张图片

你可能感兴趣的:(Wifi,Network)