udhcp源碼剖析(六)——DHCP客戶端的superloop

在client中,通過一個無限的for循環進入super loop,super loop中代碼流程也和服務器的類似,先是建立socket監聽,然後通過select+ FD_SET設置,同時對socket和pipe信號進行超時監聽,隨後根據state對接收到的packet進行處理和響應。

3.3.1 建立socket和pipe,並進行監聽

初始化超時時間和rfds根據listen_mode開啓socket監聽設置rfds綁定socket fd和signal_pipe[0]select超時等待rfds下注冊信號的發生。

tv.tv_sec = timeout - monotonic_sec();
jump_in:
    tv.tv_usec = 0;
    if (listen_mode != LISTEN_NONE && sockfd < 0) {
        if (listen_mode == LISTEN_KERNEL)
            sockfd = listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
        else
            sockfd = raw_socket(client_config.ifindex);
    }
    max_fd = udhcp_sp_fd_set(&rfds, sockfd);

    retval = 0; /* If we already timed out, fall through, else... */
    if (tv.tv_sec > 0) {
        DEBUG("Waiting on select...");
        retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
    }

    now = monotonic_sec();
    if (retval < 0) {
        /* EINTR? signal was caught, don't panic */
        if (errno != EINTR) {
            /* Else: an error occured, panic! */
            bb_perror_msg_and_die("select");
        }
    } else if (retval == 0) {

retval是select函數的返回值,表示在定時tv時間內,信號集合rfds下注冊fd中收到接收信號量的fd的個數,若retval爲0,則表示在定時時間內沒有fd收到接收信號量。或者,tv.tv_sec不大於0,即time_out<=time(0),已經超時,則也設置retval爲0.

3.3.2 超時未收到信號

如果retval=0,則表示在定時期限內,沒有收到socket和signal信號,則根據DHCP協議中對客戶端行爲的定義,依據狀態執行操作。

/* timeout dropped to zero */
    switch (state) {
    case INIT_SELECTING:
        if (packet_num < client_config.retries) {
            if (packet_num == 0)
                xid = random_xid();

            /* send discover packet */
            send_discover(xid, requested_ip); /* broadcast */

            timeout = now + client_config.timeout;
            packet_num++;
        } else {
            udhcp_run_script(NULL, "leasefail");
            if (client_config.background_if_no_lease) {
                bb_info_msg("No lease, forking to background");
                client_background();
            } else if (client_config.abort_if_no_lease) {
                bb_info_msg("No lease, failing");
                retval = 1;
                goto ret;
            }
            /* wait to try again */
            packet_num = 0;
            timeout = now + 60;
        }
        break;
    case RENEW_REQUESTED:
    case REQUESTING:
        if (packet_num < client_config.retries) {
            /* send request packet */
            if (state == RENEW_REQUESTED)
                send_renew(xid, server_addr, requested_ip); /* unicast */
            else send_selecting(xid, server_addr, requested_ip); /* broadcast */

            timeout = now + ((packet_num == 2) ? 10 : 2);
            packet_num++;
        } else {
            /* timed out, go back to init state */
            if (state == RENEW_REQUESTED)
                udhcp_run_script(NULL, "deconfig");
            state = INIT_SELECTING;
            timeout = now;
            packet_num = 0;
            change_mode(LISTEN_RAW);
        }
        break;
    case BOUND:
        /* Lease is starting to run out, time to enter renewing state */
        state = RENEWING;
        change_mode(LISTEN_KERNEL);
        DEBUG("Entering renew state");
        /* fall right through */
    case RENEWING:
        /* Either set a new T1, or enter REBINDING state */
        if ((t2 - t1) <= (lease / 14400 + 1)) {
            /* timed out, enter rebinding state */
            state = REBINDING;
            timeout = now + (t2 - t1);
            DEBUG("Entering rebinding state");
        } else {
            /* send a request packet */
            send_renew(xid, server_addr, requested_ip); /* unicast */
            t1 = (t2 - t1) / 2 + t1;
            timeout = start + t1;
        }
        break;
    case REBINDING:
        /* Either set a new T2, or enter INIT state */
        if ((lease - t2) <= (lease / 14400 + 1)) {
            /* timed out, enter init state */
            state = INIT_SELECTING;
            bb_info_msg("Lease lost, entering init state");
            udhcp_run_script(NULL, "deconfig");
            timeout = now;
            packet_num = 0;
            change_mode(LISTEN_RAW);
        } else {
            /* send a request packet */
            send_renew(xid, 0, requested_ip); /* broadcast */
            t2 = (lease - t2) / 2 + t2;
            timeout = start + t2;
        }
        break;
    case RELEASED:
        /* yah, I know, *you* say it would never happen */
        timeout = INT_MAX;
        break;
    }
} 

如果(retval > 0 && listen_mode != LISTEN_NONE && FD_ISSET(fd, &rfds)),則表示在定時期限內,socket監聽端口收到packet。
處理流程:獲取packet校驗xid提取message根據state做出響應。

else if (listen_mode != LISTEN_NONE && FD_ISSET(sockfd, &rfds)) {
    /* a packet is ready, read it */

    if (listen_mode == LISTEN_KERNEL)
        len = udhcp_get_packet(&packet, sockfd);
    else len = get_raw_packet(&packet, sockfd);

    if (len == -1 && errno != EINTR) {
        DEBUG("error on read, %s, reopening socket", strerror(errno));
        change_mode(listen_mode); /* just close and reopen */
    }
    if (len < 0) continue;

    if (packet.xid != xid) {
        DEBUG("Ignoring XID %x (our xid is %x)",
            (unsigned)packet.xid, (unsigned)xid);
        continue;
    }

    /* Ignore packets that aren't for us */
    if (memcmp(packet.chaddr, client_config.arp, 6)) {
        DEBUG("Packet does not have our chaddr - ignoring");
        continue;
    }
    message = get_option(&packet, DHCP_MESSAGE_TYPE);
    if (message == NULL) {
        bb_error_msg("cannot get option from packet - ignoring");
        continue;
    }
    switch (state) {
        case INIT_SELECTING:
            /* Must be a DHCPOFFER to one of our xid's */
            if (*message == DHCPOFFER) {
                temp = get_option(&packet, DHCP_SERVER_ID);
                if (temp) {
                    /* can be misaligned, thus memcpy */
                    memcpy(&server_addr, temp, 4);
                    xid = packet.xid;
                    requested_ip = packet.yiaddr;

                    /* enter requesting state */
                    state = REQUESTING;
                    timeout = now;
                    packet_num = 0;
                } else {
                    bb_error_msg("no server ID in message");
                }
            }
        break;
        case RENEW_REQUESTED:
        case REQUESTING:
        case RENEWING:
        case REBINDING:
            if (*message == DHCPACK) {
                temp = get_option(&packet, DHCP_LEASE_TIME);
                if (!temp) {
                    bb_error_msg("no lease time with ACK, using 1 hour lease");
                    lease = 60 * 60;
                } else {
                    /* can be misaligned, thus memcpy */
                    memcpy(&lease, temp, 4);
                    lease = ntohl(lease);
                }

                /* enter bound state */
                t1 = lease / 2;

                /* little fixed point for n * .875 */
                t2 = (lease * 7) >> 3;
                temp_addr.s_addr = packet.yiaddr;
                bb_info_msg("Lease of %s obtained, lease time %u",
                    inet_ntoa(temp_addr), (unsigned)lease);
                start = now;
                timeout = start + t1;
                requested_ip = packet.yiaddr;
                udhcp_run_script(&packet, ((state == RENEWING || state == REBINDING) ? "renew" : "bound"));

                state = BOUND;
                change_mode(LISTEN_NONE);
                if (client_config.quit_after_lease) {
                    if (client_config.release_on_quit)
                        perform_release();
                    goto ret0;
                }
                if (!client_config.foreground)
                    client_background();

            } else if (*message == DHCPNAK) {
                /* return to init state */
                bb_info_msg("Received DHCP NAK");
                udhcp_run_script(&packet, "nak");
                if (state != REQUESTING)
                    udhcp_run_script(NULL, "deconfig");
                state = INIT_SELECTING;
                timeout = now;
                requested_ip = 0;
                packet_num = 0;
                change_mode(LISTEN_RAW);
                sleep(3); /* avoid excessive network traffic */
            }
            break;
        /* case BOUND, RELEASED: - ignore all packets */
        }
    } 

如果(retval > 0 && FD_ISSET(signal_pipe[0], &rfds)),則表示在定時期限內,收到signal信號量。該signal可能是其他進程或管理員發送的,用於管理DHCP客戶端,或者控制其狀態。SIGUSR1對應控制客戶端進入RENEW狀態,SIGUSR2控制客戶端執行發送release報文,SIGTERM控制客戶端退出。

else {
        int signo = udhcp_sp_read(&rfds);
        switch (signo) {
        case SIGUSR1:
            perform_renew();
        break;
        case SIGUSR2:
            perform_release();
        break;
        case SIGTERM:
            bb_info_msg("Received SIGTERM");
            if (client_config.release_on_quit)
                perform_release();
            goto ret0;
        }
    }
} /* for (;;) */
 ret0:
    retval = 0;
 ret:
    /*if (client_config.pidfile) - remove_pidfile has it's own check */
        remove_pidfile(client_config.pidfile);
    return retval;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章