www.pudn.com > test11.rar > plugs_dhcp.c, change:2008-07-31,size:19035b


 
 
#define P(x) printf("%s\n",#x) 
 
#include "excalibur.h" 
#include "plugs.h" 
#include "plugs_private.h" 
 
#include <stdarg.h> 
 
#define k_initial_timeout_msec 1000 
#define k_retry_count 60 
 
// | 
// | DHCP is "Dynamic Host Configuration Protocol". 
// | 
// | It is described in: 
// |     RFC 1533 
// |     RFC 2131 
// | 
 
// | 
// |           Value   Message Type 
// |           -----   ------------ 
// |             1     DHCPDISCOVER 
// |             2     DHCPOFFER 
// |             3     DHCPREQUEST 
// |             4     DHCPDECLINE 
// |             5     DHCPACK 
// |             6     DHCPNAK 
// |             7     DHCPRELEASE 
 
enum 
    { 
    ne_plugs_udp_dhcp_server = 67, 
    ne_plugs_udp_dhcp_client  = 68, 
    ne_plugs_dhcp_signature  = (0x63825363), 
    }; 
 
#define nm_packed __attribute__ ((packed,aligned(2))) 
 
typedef struct 
    { 
    net_8 opcode;                      // | 1: request, 2: reply 
    net_8 hardware_type; 
    net_8 hardware_address_size; 
    net_8 hop_count; 
    net_32 transaction_id; 
    net_16 number_of_seconds; 
    net_16 unused_1; 
    net_32 client_ip_address;          // | 0 if unknown 
    net_32 your_ip_address;            // | address assigned by server 
    net_32 server_ip_address;          // | 0 if unknown 
    net_32 gateway_ip_address;         // | 0 if unknown 
    net_48 client_ethernet_address;    // | (first 6 bytes of 16) 
    char unused_2[10];                 // | since we only do ethernet 
    char server_hostname[64]; 
    char boot_filename[128]; 
    net_32 dhcp_signature;             // | 0x63825363, network byte ordering 
    char vendor_options[60]; 
    } nm_packed ns_plugs_dhcp_packet;  // | payload of a udp packet 
 
 
enum 
    { 
    e_dhcp_state_nada = 0,            // just starting 
    e_dhcp_state_wf_offer,            // sent "discover", waiting for an offer 
    e_dhcp_state_got_offer,           // set by receive-proc, if wf_offer and one comes in 
    e_dhcp_state_wf_ack,              // got offer, sent request, waiting for ack 
    e_dhcp_state_got_ack,             // set by receive-proc, if wf_ack and one comes in 
    e_dhcp_state_happy,               // got some settings, all done 
    e_dhcp_state_failed,              // too many retries and timeouts. Forget it. 
 
    e_dhcp_state_null,                // waiting for a timeout 
    }; 
 
 
 
// +------------------------ 
// | s_dhcp_offer 
// | 
// | the various fields that define an offer 
// | from a dhcp server that we might accept. 
// | 
// | When we get one we like, we stash this whole 
// | offer, and make sure that future interactions are 
// | performed only with the server from which we 
// | wish to accept an offer. 
// | 
 
typedef struct 
    { 
    ns_plugs_network_settings ns;   // settings offered from server 
    net_32 server_ip_address; 
    long ip_lease_time; 
    } s_dhcp_offer; 
 
typedef struct 
    { 
    int state;               // from enum above, e_dhcp_state_x 
    int adapter_index;       // which adapter we're using 
    net_48 ethernet_address; // our own ethernet address, for safe keeping 
 
    s_dhcp_offer offer;      // properties of a received offer 
 
    long retry;              // number of the retry 
    unsigned long timeout;   // how long to wait after we send packet 
    long last_time;          // msec of last state change, for timeout 
 
    int plug_handle; 
    } s_dhcp_state; 
 
static int r_dhcp_new(s_dhcp_state *ds,int adapter_index); 
static int r_dhcp_idle(s_dhcp_state *ds,s_dhcp_offer *offer_out); 
static int r_dhcp_free(s_dhcp_state *ds); 
 
#if PLUGS_DEBUG 
 
static void r_dhcp_print_offer(s_dhcp_offer *offer_p,char *note) 
    { 
    printf("%s from server ",note); 
    nr_plugs_print_ip_address(offer_p->server_ip_address); 
 
    printf(", for ip address "); 
    nr_plugs_print_ip_address(offer_p->ns.ip_address); 
 
    printf(", lease time %ld\n",offer_p->ip_lease_time); 
    } 
 
#endif 
 
 
 
// +----------------------------- 
// | r_read_net_32 
// | 
// | This is a stupid routine for copying 
// | four bytes from a char * to a long.  
// | 
// | (because if you just cast it, it doesnt 
// | work if the low two address bits are 
// | 1, 2, or 3. la la la.) 
// | 
 
net_32 r_read_net_32(unsigned char *w) 
    { 
    net_32 x; 
    unsigned char *xp = (unsigned char *)&x; 
 
    *xp++ = *w++; 
    *xp++ = *w++; 
    *xp++ = *w++; 
    *xp = *w; 
 
    return x; 
    } 
 
static int dhcp_callback_proc 
        ( 
        int plug_handle, 
        void *context, 
        ns_plugs_packet *p, 
        void *payload, 
        int payload_length 
        ) 
    { 
    s_dhcp_state *ds = context; 
    ns_plugs_dhcp_packet *dp = payload; 
    ns_plugs_udp_packet *udpp = p[ne_plugs_udp].header; 
    unsigned char *w; 
    s_dhcp_offer offer; 
 
    int message_type = 0; // we care about "offer"=2, "ack"=5, "nak"=6 
 
    nr_zerorange((char *)&offer,sizeof(s_dhcp_offer)); 
 
    // | 
    // | do some misc checks that bail out if we think 
    // | we dont want the packet 
    // | 
 
    if(udpp->source_port != nm_h2n16(ne_plugs_udp_dhcp_server)) 
        { 
        goto go_home; 
        } 
    if(udpp->destination_port != nm_h2n16(ne_plugs_udp_dhcp_client)) 
        { 
        goto go_home; 
        } 
    if(dp->client_ethernet_address.u32 != ds->ethernet_address.u32) 
        { 
        goto go_home; 
        } 
    if(dp->client_ethernet_address.l16 != ds->ethernet_address.l16) 
        { 
        goto go_home; 
        } 
 
    // | 
    // | ok the packet is for us 
    // | so now we scan the vendor options 
    // | 
 
    w = dp->vendor_options; 
 
    // | 
    // | All these constants come from RFC 1533 
    while(*w != 0xff) 
        { 
        int option_code = *w++; 
        int option_length = *w++; 
 
        if(option_code == 53) 
            { 
            message_type = w[0]; 
 
            if(message_type == 2) // "offer" 
                offer.ns.ip_address = dp->your_ip_address; 
            } 
        else if(option_code == 54) 
            { 
            offer.server_ip_address = r_read_net_32(w);  
            } 
        else if(option_code == 51) 
            { 
            offer.ip_lease_time = nr_n2h32(r_read_net_32(w)); 
            } 
        else if(option_code == 1) 
            { 
            offer.ns.subnet_mask = r_read_net_32(w); 
            } 
        else if(option_code == 3) 
            { 
            offer.ns.gateway_ip_address = r_read_net_32(w); 
            } 
        else if(option_code == 6) 
            { 
            offer.ns.nameserver_ip_address = r_read_net_32(w); 
            } 
 
        w += option_length; 
        } 
 
    // | 
    // | Now, by the message_type, was it the kind of message we were waiting for? 
    // | 
 
    if(message_type == 2) 
        { 
        if(ds->state == e_dhcp_state_wf_offer) 
            { 
            ds->offer = offer; 
            ds->state = e_dhcp_state_got_offer; 
#if PLUGS_DEBUG 
            r_dhcp_print_offer(&ds->offer,"[accepting offer]"); 
#endif 
            } 
        else 
            { 
#if PLUGS_DEBUG 
            r_dhcp_print_offer(&offer,"[ ignoring offer]"); 
#endif 
            } 
        } 
    else if(message_type == 5 && ds->state == e_dhcp_state_wf_ack) 
        { 
        // | 
        // | we are waiting for an ACK on our accepted offer, 
        // | and this packet is indeed an ACK, but is it from 
        // | the server we want? If so, we are all done. 
        // | 
 
        if(offer.server_ip_address == ds->offer.server_ip_address) 
            { 
            ds->state = e_dhcp_state_got_ack; 
            } 
        // else, ignore this ACK 
        } 
 
go_home: 
 
    return 0; 
    } 
 
 
 
 
// +--------------------------------------------------------- 
// | append_dhcp_message(dhcp_packet, option code, [args*]) 
// | 
// | add another "vendor options" entry to a dhcp packet 
// | 
// | the args* list is interpreted like so: 
// | 
// |                     0-255: add a byte 
// |                        -1: end of list 
// | any other negative number: treat next arg as char *, and add that many bytes 
// | (there is no way to say a pointer to one byte.) 
// | 
 
 
static void append_dhcp_message(ns_plugs_dhcp_packet *dp,int option_code, ...) 
    { 
    va_list args; 
    unsigned char *w; 
    int v; 
    int option_length = 0; 
    char *option_length_p;   // second byte of section 
 
    // | 
    // | First, find the current end-of-options 
    // | 
 
    w = dp->vendor_options; 
    while(*w != 0xff) 
        w++;                // w now hovering over the old 0xff end-marker 
 
    option_length_p = w + 1;   // second byte of section 
 
    *w++ = option_code;  // first byte is dhcp message tag thingie 
    w++;                  // we fill in the length later 
 
    va_start(args,option_code); 
 
    while((v = va_arg(args,int)) != -1) 
        { 
        if(v < 0) 
            { 
            // | next arg is pointer with some bytes for us... 
            unsigned char *p = va_arg(args,unsigned char *); 
            v = -v; 
 
            while(v-- > 0) 
                { 
                *w++ = *p++; 
                option_length++; 
                } 
            } 
        else 
            { 
            option_length++; 
            *w++ = v; 
            } 
        } 
 
    *option_length_p = option_length;  // fill in the size 
 
    *w++ = 0xff;         // re-mark the "end of options" 
    } 
 
 
 
 
// +---------------------------------- 
// | r_dhcp_new 
// | 
// | initialize plugs to a neutral and safe setting, 
// | and mark our state as "nada" 
 
static int r_dhcp_new(s_dhcp_state *ds,int adapter_index) 
    { 
    int result; 
 
    // we use the plugs globals "force_timeout" member 
    // to allow a public API that halts the synchronous 
    // activity 
 
    ng_plugs_globals.force_timeout = 0; 
 
    nr_zerorange((char *)ds,sizeof(s_dhcp_state)); // clear out the state 
    ds->adapter_index = adapter_index; 
 
        { 
        ns_plugs_network_settings ns; 
        result = nr_plugs_get_settings(ds->adapter_index,&ns); 
        if(result) 
            goto go_home; 
 
        ds->ethernet_address = ns.ethernet_address; 
        } 
 
    ds->state = e_dhcp_state_nada; 
    ds->timeout = k_initial_timeout_msec; 
    ds->retry = 0; 
 
    // | 
    // | create our UDP plug 
    // | 
 
    result = nr_plugs_create 
            ( 
            &ds->plug_handle, 
            ne_plugs_udp,ne_plugs_udp_dhcp_client, 
            dhcp_callback_proc,ds, 
            ne_plugs_flag_ip_all | ne_plugs_flag_ethernet_broadcast 
                    //+ ne_plugs_flag_debug_rx 
            ); 
    if(result < 0) 
        goto go_home; 
 
    // | 
    // | accept all packets 
    // | since we dont really have an IP address yet 
    // | nor do we really know who we shall be talking with 
    // | 
 
    result = nr_plugs_listen (ds->plug_handle,0,0); 
    if(result < 0) 
        goto go_home; 
 
go_home: 
    if(result) 
        nr_plugs_print_error_message("[r_dhcp_new]: ",result); 
     
    return result; 
    } 
 
// | 
// | Utility to fill out a dhcp request packet with the 
// | parts we know about. Most of the options must still 
// | be filled out by the caller. 
// | 
 
static int r_fill_out_dhcp_packet(s_dhcp_state *ds,ns_plugs_dhcp_packet *dp,int message_type) 
    { 
    int result = 0; 
    nr_zerorange((char *)dp,sizeof(ns_plugs_dhcp_packet)); 
 
    dp->opcode = 1; 
    dp->hardware_type = 1; 
    dp->hardware_address_size = 6; 
 
    dp->dhcp_signature = nm_h2n32(ne_plugs_dhcp_signature); 
    dp->vendor_options[0] = 0xff; 
 
    // | 
    // | get the ethernet address from the current network settings 
    // | 
 
    if(result < 0) 
        goto go_home; 
 
    dp->client_ethernet_address = ds->ethernet_address; 
 
    append_dhcp_message(dp,53,message_type,-1);     // dhcp message type, request 
    append_dhcp_message(dp,61,                      // client identifier, 
            1,                                      // hardware type 1 (ethernet) 
            -6,&dp->client_ethernet_address,        // 6 bytes of ethernet 
            -1); 
 
go_home: 
    return result; 
    } 
 
 
// +---------------------------------- 
// | r_dhcp_idle 
// | 
// | depending on state, we might send out a packet and set some 
// | variables and such 
// | 
 
static int r_dhcp_idle(s_dhcp_state *ds,s_dhcp_offer *offer_out) 
    { 
    int result = 0; 
    ns_plugs_dhcp_packet dhcp_packet; 
    int send_the_packet = 0; 
    int state_after_send = 0; 
    unsigned long time_now = nr_timer_milliseconds(); 
 
    // | 
    // | Has there been a forced timeout? 
    // | If so, we claim that DHCP failed 
    // | 
 
    nr_plugs_idle(); 
 
    if(ng_plugs_globals.force_timeout) 
        { 
        ds->state = e_dhcp_state_failed; 
        goto go_home; 
        } 
 
    // | 
    // | First, a cascaded if-statement to handle the various 
    // | possible "state" values. 
    // | 
    // | Why isn't this a switch() statement? I dunno, just felt 
    // | like using if/else if. 
    // | 
 
    if(ds->state == e_dhcp_state_nada) 
        { 
        // | 
        // | The Beginning: send out our dhcp "discover" message 
        // | 
 
        result = r_fill_out_dhcp_packet(ds,&dhcp_packet,1);     // "discover" 
        if(result) 
            goto go_home; 
 
        append_dhcp_message(&dhcp_packet,55,1,3,6,-1); // request list, subnet mask, routers, name servers 
         
        send_the_packet= 1; 
        state_after_send = e_dhcp_state_wf_offer; 
        } 
    else if(ds->state == e_dhcp_state_got_offer) 
        { 
        // | 
        // | We sent out a "discover", and some server 
        // | send out an "offer". 
        // | 
        // | We will now take the offer, and send out  
        // | a "request" that echoes what was in  
        // | the offer. 
        // | 
 
        r_fill_out_dhcp_packet(ds,&dhcp_packet,3);  // "request" 
 
        // | 
        // | request the ip address they offered 
        // | 
        append_dhcp_message(&dhcp_packet,50,-4,&ds->offer.ns.ip_address,-1); //"request",ip address 
 
        // | 
        // | server id -- who answered our "discover" 
        // | 
        append_dhcp_message(&dhcp_packet,54,-4,&ds->offer.server_ip_address,-1); 
 
        // | 
        // | request list (same list we asked for in "discover" 
        // | 
        append_dhcp_message(&dhcp_packet,55,1,3,6,-1); // request list, subnet mask, routers, name servers 
 
        send_the_packet = 1; 
        state_after_send = e_dhcp_state_wf_ack; 
        } 
    else if(ds->state == e_dhcp_state_got_ack) 
        { 
        // | 
        // | all done! the ack'd our inquiry 
        // | 
 
        ds->state = e_dhcp_state_happy; 
        } 
    else if((ds->state == e_dhcp_state_happy) 
            || (ds->state == e_dhcp_state_failed)) 
        { 
        // nop. stay happy or failed 
        } 
    else 
        { 
        // | 
        // | fell through to here? just check the  
        // | the time, and if too much time has passed, 
        // | reset the state to "nada" and start the whole 
        // | darned thing over. 
        // | 
        if(time_now - ds->last_time > ds->timeout) 
            { 
            ds->last_time = time_now; 
            ds->retry ++; 
#if PLUGS_DEBUG 
            printf("[dhcp] %d timing out\n",ds->retry); 
#endif 
             
            if(ds->retry < k_retry_count) 
                ds->state = e_dhcp_state_nada; 
            else 
                ds->state = e_dhcp_state_failed; 
            } 
        } 
  
    // | 
    // | depending what happened above, we might 
    // | have a packet to send, and then the need to 
    // | bump our state to some "_wf_" (waiting-for). 
    // | 
    // | (we only bump the state if the packet goes out 
    // | without an error.) 
    // | 
 
    if(send_the_packet) 
        { 
        // | 
        // | we send all our dhcp<-->server messages as  
        // | ethernet broadcast packets so that any other 
        // | servers will be in on the conversation, and know 
        // | that we have not chosen them. 
        // | 
 
        result = nr_plugs_send_to 
                ( 
                ds->plug_handle, 
                &dhcp_packet,sizeof(dhcp_packet), 
                ne_plugs_flag_ethernet_broadcast, 
                -1, 
                nm_h2n16(ne_plugs_udp_dhcp_server) 
                ); 
        if(result < 0) 
            goto go_home; 
 
        ds->state = state_after_send; 
        ds->last_time = time_now; 
        } 
         
go_home: 
    if(result < 0) 
        { 
        nr_plugs_print_error_message("[r_dhcp_idle]: ",result); 
 
        // | on an error, we reset our state to "null", which ends up 
        // | waiting for a timeout and then going to "nada" and 
        // | starting all over again. 
        ds->state = e_dhcp_state_null; 
        ds->last_time = time_now; 
        } 
    else 
        { 
 
        // | 
        // | return values: 
        // |    x < 0: dhcp has really failed 
        // |        0: whatever, keep trying 
        // | positive: duration of lease 
        // | 
 
        if(ds->state == e_dhcp_state_happy) 
            { 
            result = ds->offer.ip_lease_time; 
            if(result < 0) 
                result = 1; 
            *offer_out = ds->offer; 
            } 
        else if(ds->state == e_dhcp_state_failed) 
            { 
            result = ne_plugs_error_dhcp_failed; 
            } 
        // (else, result is already zero.) 
        } 
 
    return result; 
    } 
 
// +---------------------------------- 
// | close down the plug, and that is all 
// | 
static int r_dhcp_free(s_dhcp_state *ds) 
    { 
    int result = 0; 
 
    if(ds->plug_handle) 
        result = nr_plugs_destroy(ds->plug_handle); 
 
    return result; 
    } 
 
// +------------------------- 
// | this is the main dhcp routine. It should be called 
// | with the adapter initialized, but no support plugs 
// | (ping, dns, whatever) created. 
// | 
 
int r_plugs_dhcp(int adapter_index,ns_plugs_network_settings *settings_out) 
    { 
    int result; 
    ns_plugs_network_settings settings; 
    s_dhcp_state ds; 
    s_dhcp_offer offer; 
 
    result = r_dhcp_new(&ds,adapter_index); 
    if(result < 0) 
        goto go_home; 
 
    while(1) 
        { 
        result = r_dhcp_idle(&ds,&offer); 
        if(result != 0) 
            goto go_home; 
        } 
 
go_home: 
 
    // snatch the ethernet address before freeing the ds 
 
    offer.ns.ethernet_address = ds.ethernet_address; 
    offer.ns.flags = ne_plugs_flag_dhcp; // indicates the settings came via dhcp 
 
    r_dhcp_free(&ds);  // dont log the error from this 
#if PLUGS_DEBUG 
    nr_plugs_print_error_message("[r_plugs_dhcp] ",result); 
#endif 
 
    *settings_out = offer.ns; 
    return result; 
    } 
 
// end of file