LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

Busybox udhcpc:自动识别ip配置以及流程解析

关键词:udhcpc、DHCP、DNS、default.script、route、ifconfig等等。

 

udhcpc来自于Busybox,基于DHCP服务,达到自动配置IP的功能。

下面分别介绍如何配置udhcpc,以及udhcpc是如何达到自动配置IP功能的。

1. 配置udhcpc

1.1 配置kernel支持DHCP

进入Networking -> Networking options -> TCP/IP networking,选择IP: DHCP support。

1.2 配置udhcpc

进入Networking Utilities -> udhcpc (DHCP client)。

1.3 启动udhcpc

在/etc/init.d/rcS中启动udhcpc。

 #
 # To config network
 #
udhcpc

1.4 udhcpc使用说明

BusyBox v1.27.2 (2020-08-25 12:39:58 CST) multi-call binary.

Usage: udhcpc [-fbqRB] [-a[MSEC]] [-t N] [-T SEC] [-A SEC/-n]
        [-i IFACE] [-s PROG] [-p PIDFILE]
        [-oC] [-r IP] [-V VENDOR] [-F NAME] [-x OPT:VAL]... [-O OPT]...

        -i,--interface IFACE    Interface to use (default eth0)
        -s,--script PROG        Run PROG at DHCP events (default /usr/share/udhcpc/default.script)
        -p,--pidfile FILE       Create pidfile
        -B,--broadcast          Request broadcast replies
        -t,--retries N          Send up to N discover packets (default 3)
        -T,--timeout SEC        Pause between packets (default 3)
        -A,--tryagain SEC       Wait if lease is not obtained (default 20)
        -n,--now                Exit if lease is not obtained
        -q,--quit               Exit after obtaining lease
        -R,--release            Release IP on exit
        -f,--foreground         Run in foreground
        -b,--background         Background if lease is not obtained
        -S,--syslog             Log to syslog too
        -a[MSEC],--arping[=MSEC] Validate offered address with ARP ping
        -r,--request IP         Request this IP address
        -o,--no-default-options Don't request any options (unless -O is given)
        -O,--request-option OPT Request option OPT from server (cumulative)
        -x OPT:VAL              Include option OPT in sent packets (cumulative)
                                Examples of string, numeric, and hex byte opts:
                                -x hostname:bbox - option 12
                                -x lease:3600 - option 51 (lease time)
                                -x 0x3d:0100BEEFC0FFEE - option 61 (client id)
        -F,--fqdn NAME          Ask server to update DNS mapping for NAME
        -V,--vendorclass VENDOR Vendor identifier (default 'udhcp VERSION')
        -C,--clientid-none      Don't send MAC as client identifier
Signals:
        USR1    Renew lease
        USR2    Release lease

2. 流程解析

整个udhcpc的框架是可执行文件udhcpc、脚本文件/usr/share/udhcpc/default.script、DNS配置文件/etc/resolv.conf

入口是udhcpc,然后udhcpc调用shell脚本default.script中的deconfig/leasefail/bound/renew/nak等选项,resolv.conf存放DNS配置文件。

2.1 Kernel DHCP相关

DHCP在内核中是IP Autocofig dispatcher一部分,入口在ip_auto_config()。

内核负责将udhcpc发送的DHCP请求通过对应的网络设备发送出去,并将接受到的数据解析返回给udhcpc。

static int __init ip_auto_config(void)
{
...
    if (ic_myaddr == NONE ||...
        ic_first_dev->next) {
#ifdef IPCONFIG_DYNAMIC
        if (ic_dynamic() < 0) {
            ic_close_devs();
...
        }
#else /* !DYNAMIC */...
#endif /* IPCONFIG_DYNAMIC */
    } else {
...
    }
...
}

static int __init ic_dynamic(void)
{
...
#ifdef IPCONFIG_BOOTP
    if (do_bootp)
        ic_bootp_init();------------------------初始化DHCP/BOOTP相关,主要是注册处理从服务器接收到的DHCP/BOOTP回复。
#endif
#ifdef IPCONFIG_RARP
    if (do_rarp)
        ic_rarp_init();
#endif
...
    for (;;) {
#ifdef IPCONFIG_BOOTP
        if (do_bootp && (d->able & IC_BOOTP))
            ic_bootp_send_if(d, jiffies - start_jiffies);---发送DHCP/BOOTP请求到d网络设备。
#endif...
        if (!d->next) {
            jiff = jiffies + timeout;
            while (time_before(jiffies, jiff) && !ic_got_reply)
                schedule_timeout_uninterruptible(1);
        }
#ifdef IPCONFIG_DHCP
        /* DHCP isn't done until we get a DHCPACK. */
        if ((ic_got_reply & IC_BOOTP) &&
            (ic_proto_enabled & IC_USE_DHCP) &&
            ic_dhcp_msgtype != DHCPACK) {
            ic_got_reply = 0;
            /* continue on device that got the reply */
            d = ic_dev;
            pr_cont(",");
            continue;
        }
#endif /* IPCONFIG_DHCP */
...
    }
...
}

ic_bootp_send_if()通过d指定的网络设备发送socket DHCP/BOOTP请求。

/*
 *  Send DHCP/BOOTP request to single interface.
 */
static void __init ic_bootp_send_if(struct ic_device *d, unsigned long jiffies_diff)
{
    struct net_device *dev = d->dev;
    struct sk_buff *skb;
    struct bootp_pkt *b;
    struct iphdr *h;
    int hlen = LL_RESERVED_SPACE(dev);
    int tlen = dev->needed_tailroom;

    /* Allocate packet */
    skb = alloc_skb(sizeof(struct bootp_pkt) + hlen + tlen + 15,
            GFP_KERNEL);--------------------------分配skb,下面逐渐填充内容。
    if (!skb)
        return;
    skb_reserve(skb, hlen);
    b = (struct bootp_pkt *) skb_put(skb, sizeof(struct bootp_pkt));
    memset(b, 0, sizeof(struct bootp_pkt));

    /* Construct IP header */
    skb_reset_network_header(skb);
    h = ip_hdr(skb);
    h->version = 4;
    h->ihl = 5;
    h->tot_len = htons(sizeof(struct bootp_pkt));
    h->frag_off = htons(IP_DF);
    h->ttl = 64;
    h->protocol = IPPROTO_UDP;
    h->daddr = htonl(INADDR_BROADCAST);
    h->check = ip_fast_csum((unsigned char *) h, h->ihl);

    /* Construct UDP header */
    b->udph.source = htons(68);
    b->udph.dest = htons(67);
    b->udph.len = htons(sizeof(struct bootp_pkt) - sizeof(struct iphdr));
    /* UDP checksum not calculated -- explicitly allowed in BOOTP RFC */

    /* Construct DHCP/BOOTP header */
    b->op = BOOTP_REQUEST;
    if (dev->type < 256) /* check for false types */
        b->htype = dev->type;
    else if (dev->type == ARPHRD_FDDI)
        b->htype = ARPHRD_ETHER;
    else {
        pr_warn("Unknown ARP type 0x%04x for device %s\n", dev->type,
            dev->name);
        b->htype = dev->type; /* can cause undefined behavior */
    }

    /* server_ip and your_ip address are both already zero per RFC2131 */
    b->hlen = dev->addr_len;
    memcpy(b->hw_addr, dev->dev_addr, dev->addr_len);
    b->secs = htons(jiffies_diff / HZ);
    b->xid = d->xid;

    /* add DHCP options or BOOTP extensions */
#ifdef IPCONFIG_DHCP
    if (ic_proto_enabled & IC_USE_DHCP)
        ic_dhcp_init_options(b->exten, d);---------构建DHCP附加内容。
    else
#endif
        ic_bootp_init_ext(b->exten);

    /* Chain packet down the line... */
    skb->dev = dev;
    skb->protocol = htons(ETH_P_IP);
    if (dev_hard_header(skb, dev, ntohs(skb->protocol),
                dev->broadcast, dev->dev_addr, skb->len) < 0) {
        kfree_skb(skb);
        printk("E");
        return;
    }

    if (dev_queue_xmit(skb) < 0)-------------------发送skb。
        printk("E");
}

2.2 udhcpc解析

和DHCP服务交互的状态如下:

/* initial state: (re)start DHCP negotiation */
#define INIT_SELECTING  0
/* discover was sent, DHCPOFFER reply received */
#define REQUESTING      1
/* select/renew was sent, DHCPACK reply received */
#define BOUND           2
/* half of lease passed, want to renew it by sending unicast renew requests */
#define RENEWING        3
/* renew requests were not answered, lease is almost over, send broadcast renew */
#define REBINDING       4
/* manually requested renew (SIGUSR1) */
#define RENEW_REQUESTED 5
/* release, possibly manually requested (SIGUSR2) */
#define RELEASED        6

udhcpc遵循DHCP协议和DHCP服务器进行交互,通过状态机来解析从DHCP服务器获取的packet。

int udhcpc_main(int argc UNUSED_PARAM, char **argv)
{
...
    if (udhcp_read_interface(client_config.interface,
            &client_config.ifindex,
            NULL,
            client_config.client_mac)---------------------根据client_config.interface获取对应的client_config.client_mac和client_config.ifindex。
    ) {
        return 1;
    }
...
    /* Goes to stdout (unless NOMMU) and possibly syslog */
    bb_error_msg("started, v"BB_VER);
    /* Set up the signal pipe */
    udhcp_sp_setup();
    /* We want random_xid to be random... */
    srand(monotonic_us());

    state = INIT_SELECTING;
    udhcp_run_script(NULL, "deconfig");--------执行CONFIG_UDHCPC_DEFAULT_SCRIPT指定脚本的deconfig选项。
    change_listen_mode(LISTEN_RAW);
...
    for (;;) {---------------------------------udhcpc的主循环体。
        int tv;
        struct pollfd pfds[2];
        struct dhcp_packet packet;
        /* silence "uninitialized!" warning */
        unsigned timestamp_before_wait = timestamp_before_wait;

        udhcp_sp_fd_set(pfds, sockfd);

        tv = timeout - already_waited_sec;
        retval = 0;
        /* If we already timed out, fall through with retval = 0, else... */
        if (tv > 0) {
            log1("waiting on select %u seconds", tv);
            timestamp_before_wait = (unsigned)monotonic_sec();
            retval = poll(pfds, 2, tv < INT_MAX/1000 ? tv * 1000 : INT_MAX);
            if (retval < 0) {
                /* EINTR? A signal was caught, don't panic */
                if (errno == EINTR) {
                    already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
                    continue;
                }
                /* Else: an error occurred, panic! */
                bb_perror_msg_and_die("select");
            }
        }

        /* If timeout dropped to zero, time to become active:
         * resend discover/renew/whatever
         */
        if (retval == 0) {
            if (udhcp_read_interface(client_config.interface,
                    &client_config.ifindex,
                    NULL,
                    client_config.client_mac)
            ) {
                goto ret0; /* iface is gone? */
            }
            if (clientid_mac_ptr)
                memcpy(clientid_mac_ptr, client_config.client_mac, 6);

            /* We will restart the wait in any case */
            already_waited_sec = 0;

            switch (state) {
            case INIT_SELECTING:
                if (!discover_retries || packet_num < discover_retries) {
                    if (packet_num == 0)
                        xid = random_xid();
                    /* broadcast */
                    send_discover(xid, requested_ip);-------------------------在网络中广播一个DHCP discover包到DHCP服务器,如果存在requested_ip则指定想要发现的ip地址。
                    timeout = discover_timeout;
                    packet_num++;
                    continue;
                }
 leasefail:
                udhcp_run_script(NULL, "leasefail");---------------------------执行default.script的leasefail命令。
#if BB_MMU /* -b is not supported on NOMMU */
                if (opt & OPT_b) { /* background if no lease */
                    bb_error_msg("no lease, forking to background");
                    client_background();
                    /* do not background again! */
                    opt = ((opt & ~OPT_b) | OPT_f);
                } else
#endif
                if (opt & OPT_n) { /* abort if no lease */
                    bb_error_msg("no lease, failing");
                    retval = 1;
                    goto ret;
                }
                /* wait before trying again */
                timeout = tryagain_timeout;
                packet_num = 0;
                continue;
            case REQUESTING:
                if (packet_num < 3) {
                    /* send broadcast select packet */
                    send_select(xid, server_addr, requested_ip);------------------广播一个DHCP request消息,表示选择requested_ip作为。
                    timeout = discover_timeout;
                    packet_num++;
                    continue;
                }
                change_listen_mode(LISTEN_RAW);
                state = INIT_SELECTING;
                goto leasefail;
            case BOUND:
                /* 1/2 lease passed, enter renewing state */
                state = RENEWING;
                client_config.first_secs = 0; /* make secs field count from 0 */
                change_listen_mode(LISTEN_KERNEL);---------------------------------切换socket监听模式为LISTEN_KERNEL。
                log1("entering renew state");
                /* fall right through */
            case RENEW_REQUESTED: /* manual (SIGUSR1) renew */
            case_RENEW_REQUESTED:
            case RENEWING:
                if (timeout > 60) {
                    send_renew(xid, server_addr, requested_ip);
                    timeout >>= 1;
                    continue;
                }
                /* Timed out, enter rebinding state */
                log1("entering rebinding state");
                state = REBINDING;
                /* fall right through */
            case REBINDING:
                /* Switch to bcast receive */
                change_listen_mode(LISTEN_RAW);
                /* Lease is *really* about to run out,
                 * try to find DHCP server using broadcast */
                if (timeout > 0) {
                    /* send a broadcast renew request */
                    send_renew(xid, 0 /*INADDR_ANY*/, requested_ip);
                    timeout >>= 1;
                    continue;
                }
                /* Timed out, enter init state */
                bb_error_msg("lease lost, entering init state");
                udhcp_run_script(NULL, "deconfig");
                state = INIT_SELECTING;
                client_config.first_secs = 0; /* make secs field count from 0 */
                /*timeout = 0; - already is */
                packet_num = 0;
                continue;
            /* case RELEASED: */
            }
            /* yah, I know, *you* say it would never happen */
            timeout = INT_MAX;
            continue; /* back to main loop */
        } /* if select timed out */

        /* select() didn't timeout, something happened */
...
        /* Is it a packet? */
        if (listen_mode == LISTEN_NONE || !pfds[1].revents)
            continue; /* no */

        {
            int len;

            /* A packet is ready, read it */
            if (listen_mode == LISTEN_KERNEL)
                len = udhcp_recv_kernel_packet(&packet, sockfd);
            else
                len = udhcp_recv_raw_packet(&packet, sockfd);--------------------------接收socket消息,保存到packet中。
            if (len == -1) {
                /* Error is severe, reopen socket */
                bb_error_msg("read error: %s, reopening socket", strerror(errno));
                sleep(discover_timeout); /* 3 seconds by default */
                change_listen_mode(listen_mode); /* just close and reopen */
            }
            /* If this packet will turn out to be unrelated/bogus,
             * we will go back and wait for next one.
             * Be sure timeout is properly decreased. */
            already_waited_sec += (unsigned)monotonic_sec() - timestamp_before_wait;
            if (len < 0)
                continue;
        }

        if (packet.xid != xid) {
            log1("xid %x (our is %x), ignoring packet",
                (unsigned)packet.xid, (unsigned)xid);
            continue;
        }

        /* Ignore packets that aren't for us */
        if (packet.hlen != 6
         || memcmp(packet.chaddr, client_config.client_mac, 6) != 0
        ) {
//FIXME: need to also check that last 10 bytes are zero
            log1("chaddr does not match, ignoring packet"); // log2?
            continue;
        }

        message = udhcp_get_option(&packet, DHCP_MESSAGE_TYPE);
        if (message == NULL) {
            bb_error_msg("no message type option, ignoring packet");
            continue;
        }

        switch (state) {--------------------------------------------------------------根据当前所处的state,对packaet进行处理。
        case INIT_SELECTING:----------------------------------------------------------处于INIT_SELECTING时,获取DHCP分配的IP地址。
            /* Must be a DHCPOFFER */
            if (*message == DHCPOFFER) {
                uint8_t *temp;
                server_addr = 0;
                temp = udhcp_get_option(&packet, DHCP_SERVER_ID);
                if (!temp) {
                    bb_error_msg("no server ID, using 0.0.0.0");
                } else {
                    /* it IS unaligned sometimes, don't "optimize" */
                    move_from_unaligned32(server_addr, temp);
                }
                /*xid = packet.xid; - already is */
                requested_ip = packet.yiaddr;

                /* enter requesting state */
                state = REQUESTING;----------------------------------------------------跳转到REQUESTING状态。
                timeout = 0;
                packet_num = 0;
                already_waited_sec = 0;
            }
            continue;
        case REQUESTING:
        case RENEWING:
        case RENEW_REQUESTED:
        case REBINDING:
            if (*message == DHCPACK) {
                unsigned start;
                uint32_t lease_seconds;
                struct in_addr temp_addr;
                uint8_t *temp;

                temp = udhcp_get_option(&packet, DHCP_LEASE_TIME);
                if (!temp) {
                    bb_error_msg("no lease time with ACK, using 1 hour lease");
                    lease_seconds = 60 * 60;
                } else {
                    /* it IS unaligned sometimes, don't "optimize" */
                    move_from_unaligned32(lease_seconds, temp);
                    lease_seconds = ntohl(lease_seconds);
                    /* paranoia: must not be too small and not prone to overflows */
                    if (lease_seconds < 0x10)
                        lease_seconds = 0x10;
                    if (lease_seconds > 0x7fffffff / 1000)
                        lease_seconds = 0x7fffffff / 1000;
                }
...
/* enter bound state */ temp_addr.s_addr = packet.yiaddr; bb_error_msg("lease of %s obtained, lease time %u", inet_ntoa(temp_addr), (unsigned)lease_seconds); requested_ip = packet.yiaddr;------------------------------------------更新requested_ip,即DHCP分配给本机的IP地址。 start = monotonic_sec(); udhcp_run_script(&packet, state == REQUESTING ? "bound" : "renew");----如果当前状态时REQUESTING,执行default.script的bound;否则执行renew already_waited_sec = (unsigned)monotonic_sec() - start; timeout = lease_seconds / 2; if ((unsigned)timeout < already_waited_sec) { /* Something went wrong. Back to discover state */ timeout = already_waited_sec = 0; } state = BOUND; change_listen_mode(LISTEN_NONE); if (opt & OPT_q) { /* quit after lease */ goto ret0; } /* future renew failures should not exit (JM) */ opt &= ~OPT_n; #if BB_MMU /* NOMMU case backgrounded earlier */ if (!(opt & OPT_f)) { client_background(); /* do not background again! */ opt = ((opt & ~OPT_b) | OPT_f); } #endif /* make future renew packets use different xid */ /* xid = random_xid(); ...but why bother? */ continue; /* back to main loop */ } if (*message == DHCPNAK) {-----------------------------------------------错误情况DHCPNAK。 /* If network has more than one DHCP server, * "wrong" server can reply first, with a NAK. * Do not interpret it as a NAK from "our" server. */ if (server_addr != 0) { uint32_t svid; uint8_t *temp; temp = udhcp_get_option(&packet, DHCP_SERVER_ID); if (!temp) { non_matching_svid: log1("received DHCP NAK with wrong" " server ID, ignoring packet"); continue; } move_from_unaligned32(svid, temp); if (svid != server_addr) goto non_matching_svid; } /* return to init state */ bb_error_msg("received %s", "DHCP NAK"); udhcp_run_script(&packet, "nak");----------------------------------执行default.script的nak脚本。 if (state != REQUESTING)-------------------------------------------如果状态时REQUESTING,则重新进行一次流程。 udhcp_run_script(NULL, "deconfig"); change_listen_mode(LISTEN_RAW); sleep(3); /* avoid excessive network traffic */ state = INIT_SELECTING; client_config.first_secs = 0; /* make secs field count from 0 */ requested_ip = 0; timeout = 0; packet_num = 0; already_waited_sec = 0; } continue; /* case BOUND: - ignore all packets */ /* case RELEASED: - ignore all packets */ } /* back to main loop */ } /* for (;;) - main loop ends */ ret0: if (opt & OPT_R) /* release on quit */ perform_release(server_addr, requested_ip); retval = 0; ret: /*if (client_config.pidfile) - remove_pidfile has its own check */ remove_pidfile(client_config.pidfile); return retval; }

udhcp_run_script()是udhcpc和default.script的接口,通过udhcp_run_script()即可调用其中一部分脚本。

/* Call a script with a par file and env vars */
static void udhcp_run_script(struct dhcp_packet *packet, const char *name)
{
    char **envp, **curr;
    char *argv[3];

    envp = fill_envp(packet);----------将options以及packet中的内容导出到shell环境变量中,下面执行shell的过程中使用。

    /* call script */
    log1("executing %s %s", client_config.script, name);
    argv[0] = (char*) client_config.script;
    argv[1] = (char*) name;
    argv[2] = NULL;
    spawn_and_wait(argv);

    for (curr = envp; *curr; curr++) {
        log2(" %s", *curr);
        bb_unsetenv_and_free(*curr);
    }
    free(envp);
}

/* put all the parameters into the environment */
static char **fill_envp(struct dhcp_packet *packet)
{
    int envc;
    int i;
    char **envp, **curr;
    const char *opt_name;
    uint8_t *temp;
    uint8_t overload = 0;

#define BITMAP unsigned
#define BBITS (sizeof(BITMAP) * 8)
#define BMASK(i) (1 << (i & (sizeof(BITMAP) * 8 - 1)))
#define FOUND_OPTS(i) (found_opts[(unsigned)i / BBITS])
    BITMAP found_opts[256 / BBITS];

    memset(found_opts, 0, sizeof(found_opts));

    /* We need 6 elements for:
     * "interface=IFACE"
     * "ip=N.N.N.N" from packet->yiaddr
     * "siaddr=IP" from packet->siaddr_nip (unless 0)
     * "boot_file=FILE" from packet->file (unless overloaded)
     * "sname=SERVER_HOSTNAME" from packet->sname (unless overloaded)
     * terminating NULL
     */
    envc = 6;
    /* +1 element for each option, +2 for subnet option: */
    if (packet) {
        /* note: do not search for "pad" (0) and "end" (255) options */
//TODO: change logic to scan packet _once_
        for (i = 1; i < 255; i++) {
            temp = udhcp_get_option(packet, i);
            if (temp) {
                if (i == DHCP_OPTION_OVERLOAD)
                    overload |= *temp;
                else if (i == DHCP_SUBNET)
                    envc++; /* for $mask */
                envc++;
                /*if (i != DHCP_MESSAGE_TYPE)*/
                FOUND_OPTS(i) |= BMASK(i);
            }
        }
    }
    curr = envp = xzalloc(sizeof(envp[0]) * envc);

    *curr = xasprintf("interface=%s", client_config.interface);
    putenv(*curr++);

    if (!packet)
        return envp;

    /* Most important one: yiaddr as $ip */
    *curr = xmalloc(sizeof("ip=255.255.255.255"));
    sprint_nip(*curr, "ip=", (uint8_t *) &packet->yiaddr);
    putenv(*curr++);
    if (packet->siaddr_nip) {
        /* IP address of next server to use in bootstrap */
        *curr = xmalloc(sizeof("siaddr=255.255.255.255"));
        sprint_nip(*curr, "siaddr=", (uint8_t *) &packet->siaddr_nip);
        putenv(*curr++);
    }
    if (!(overload & FILE_FIELD) && packet->file[0]) {
        /* watch out for invalid packets */
        *curr = xasprintf("boot_file=%."DHCP_PKT_FILE_LEN_STR"s", packet->file);
        putenv(*curr++);
    }
    if (!(overload & SNAME_FIELD) && packet->sname[0]) {
        /* watch out for invalid packets */
        *curr = xasprintf("sname=%."DHCP_PKT_SNAME_LEN_STR"s", packet->sname);
        putenv(*curr++);
    }

    /* Export known DHCP options */
    opt_name = dhcp_option_strings;
    i = 0;
    while (*opt_name) {
        uint8_t code = dhcp_optflags[i].code;
        BITMAP *found_ptr = &FOUND_OPTS(code);
        BITMAP found_mask = BMASK(code);
        if (!(*found_ptr & found_mask))
            goto next;
        *found_ptr &= ~found_mask; /* leave only unknown options */
        temp = udhcp_get_option(packet, code);
        *curr = xmalloc_optname_optval(temp, &dhcp_optflags[i], opt_name);---将optname和value导出到shell环境变量中。
        putenv(*curr++);
        if (code == DHCP_SUBNET) {
            /* Subnet option: make things like "$ip/$mask" possible */
            uint32_t subnet;
            move_from_unaligned32(subnet, temp);
            *curr = xasprintf("mask=%u", mton(subnet));
            putenv(*curr++);
        }
 next:
        opt_name += strlen(opt_name) + 1;
        i++;
    }
...
    return envp;
}

 2.3 default.script脚本解析

default.script是udhcpc的延伸,方便灵活的实现udhcpc的功能。

#!/bin/sh

# udhcpc script edited by Tim Riker <Tim@Rikers.org>

[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1

RESOLV_CONF="/etc/resolv.conf"
[ -e $RESOLV_CONF ] || touch $RESOLV_CONF
[ -n "$broadcast" ] && BROADCAST="broadcast $broadcast"
[ -n "$subnet" ] && NETMASK="netmask $subnet"

case "$1" in
    deconfig)
        /sbin/ifconfig $interface up
        /sbin/ifconfig $interface 0.0.0.0--------------------$interface是udhcpc指定的,首先up启动设备;然后配置一个默认0.0.0.0地址。

        # drop info from this interface
        # resolv.conf may be a symlink to /tmp/, so take care
        TMPFILE=$(mktemp)
        grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
        cat $TMPFILE > $RESOLV_CONF
        rm -f $TMPFILE

        if [ -x /usr/sbin/avahi-autoipd ]; then
            /usr/sbin/avahi-autoipd -k $interface
        fi
        ;;

    leasefail|nak)
        if [ -x /usr/sbin/avahi-autoipd ]; then
            /usr/sbin/avahi-autoipd -wD $interface --no-chroot
        fi
        ;;

    renew|bound)
        if [ -x /usr/sbin/avahi-autoipd ]; then
            /usr/sbin/avahi-autoipd -k $interface
        fi
        /sbin/ifconfig $interface $ip $BROADCAST $NETMASK-----------根据udhcpc获取的interface/ip/BROADCASR/NETMASK,通过ifconfig配置到interface的设备。

        if [ -n "$router" ] ; then
            echo "deleting routers"
            while route del default gw 0.0.0.0 dev $interface 2> /dev/null; do
                :
            done

            for i in $router ; do
                route add default gw $i dev $interface--------------router不为空的情况下,先删除默认路由,重新建立路由。
            done
        fi

        # drop info from this interface
        # resolv.conf may be a symlink to /tmp/, so take care
        TMPFILE=$(mktemp)
        grep -vE "# $interface\$" $RESOLV_CONF > $TMPFILE
        cat $TMPFILE > $RESOLV_CONF
        rm -f $TMPFILE

        # prefer rfc3359 domain search list (option 119) if available
        if [ -n "$search" ]; then
            search_list=$search
        elif [ -n "$domain" ]; then
            search_list=$domain
        fi

        [ -n "$search_list" ] &&
            echo "search $search_list # $interface" >> $RESOLV_CONF

        for i in $dns ; do
            echo adding dns $i
            echo "nameserver $i # $interface" >> $RESOLV_CONF--------更新dns信息到/etc/resolv.conf中。
        done
        ;;
esac

HOOK_DIR="$0.d"
for hook in "${HOOK_DIR}/"*; do
    [ -f "${hook}" -a -x "${hook}" ] || continue
    "${hook}" "${@}"
done

exit 0

 2.4 运行实例log 

 运行log如下:

[2020-08-26 19:16:08] udhcpc: started, v1.27.2
[2020-08-26 19:16:08] udhcpc: sending discover-------------------------------------发送DHCPDISCOVER到DHCP服务器,服务器会返回一个可用IP地址。
[2020-08-26 19:16:10] udhcpc: sending select for 192.168.33.184--------------------发送DHCPREQUEST到DHCP服务器,是对DPCPOFFER的响应。
[2020-08-26 19:16:10] udhcpc: lease of 192.168.33.184 obtained, lease time 86400---此时已经经过DHCP服务器分配IP,IP地址为192.168.33.184。然后调用default.script的bound或renew。
[2020-08-26 19:16:10] deleting routers---------------------------------------------default.script中删除路由。
[2020-08-26 19:16:10] adding dns 192.168.33.1--------------------------------------配置DNS。

3. DHCP协议

参考:《Dynamic Host Configuration Protocol

其中《3.1 Client-server interaction - allocating a network address》介绍了如何分配一个IP地址的流程。

   Message         Use
   -------         ---

   DHCPDISCOVER -  Client broadcast to locate available servers.

   DHCPOFFER    -  Server to client in response to DHCPDISCOVER with
                   offer of configuration parameters.

   DHCPREQUEST  -  Client message to servers either (a) requesting
                   offered parameters from one server and implicitly
                   declining offers from all others, (b) confirming
                   correctness of previously allocated address after,
                   e.g., system reboot, or (c) extending the lease on a
                   particular network address.

   DHCPACK      -  Server to client with configuration parameters,
                   including committed network address.

   DHCPNAK      -  Server to client indicating client's notion of network
                   address is incorrect (e.g., client has moved to new
                   subnet) or client's lease as expired

   DHCPDECLINE  -  Client to server indicating network address is already
                   in use.

   DHCPRELEASE  -  Client to server relinquishing network address and
                   cancelling remaining lease.

   DHCPINFORM   -  Client to server, asking only for local configuration
                   parameters; client already has externally configured
                   network address.

                          Table 2:  DHCP messages

                Server          Client          Server
            (not selected)                    (selected)

                  v               v               v
                  |               |               |
                  |     Begins initialization     |
                  |               |               |
                  | _____________/|\____________  |
                  |/DHCPDISCOVER | DHCPDISCOVER  \|
                  |               |               |
              Determines          |          Determines
             configuration        |         configuration
                  |               |               |
                  |\             |  ____________/ |
                  | \________    | /DHCPOFFER     |
                  | DHCPOFFER\   |/               |
                  |           \  |                |
                  |       Collects replies        |
                  |             \|                |
                  |     Selects configuration     |
                  |               |               |
                  | _____________/|\____________  |
                  |/ DHCPREQUEST  |  DHCPREQUEST\ |
                  |               |               |
                  |               |     Commits configuration
                  |               |               |
                  |               | _____________/|
                  |               |/ DHCPACK      |
                  |               |               |
                  |    Initialization complete    |
                  |               |               |
                  .               .               .
                  .               .               .
                  |               |               |
                  |      Graceful shutdown        |
                  |               |               |
                  |               |\ ____________ |
                  |               | DHCPRELEASE  \|
                  |               |               |
                  |               |        Discards lease
                  |               |               |
                  v               v               v
     Figure 3: Timeline diagram of messages exchanged between DHCP
               client and servers when allocating a new network address

4. 小结

整个自动配置IP功能,需要Kernel支持DHCP、udhcpc、default.script。

在rcS中启动udhcpc,默认使用default.script脚本已达到自动配置ip、route等。

posted on 2020-08-23 00:00  ArnoldLu  阅读(18858)  评论(0编辑  收藏  举报

导航