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