@@ 9,6 9,10 @@ constant AWAIT_REPLY = 2;
constant DHCP_BROADCAST_ADDRESS = "FF02::1:2";
+constant SOL_MAX_DELAY = 1;
+constant CNF_MAX_DELAY = 1;
+constant REQ_MAX_RC = 10;
+
string leasefile = "/var/run/dhcpv6_pd_leases";
string v6if = "net0";
string identifier;
@@ 19,13 23,16 @@ mapping lease_data = ([]);
int current_state = NOT_AWAITING;
int current_txn;
mixed time_out_id;
+mixed t1_call_out;
+mixed t2_call_out;
float last_timeout = 0.0;
+int keep_trying = 1;
Stdio.UDP dhcp;
DUID duid;
IAID iaid;
-IAPD current_iapd;
+
int current_iapd_confirmed;
int main(int argc, array argv) {
@@ 44,10 51,14 @@ int main(int argc, array argv) {
mapping ld;
mixed e = catch(ld = decode_value(leases));
if(e) werror("Unable to decode lease data. Backtrace follows.\n%s\n", describe_backtrace(e));
- else {
+ else if(ld->confirmed) {
werror("Have leases for Prefix Delegation, will attempt to reconfirm them.\n");
lease_data = ld;
+ werror("Lease data: %O\n", lease_data);
have_leases = 1;
+ } else {
+ werror("Have leases, but they are not marked as confirmed. Will attempt to solicit new leases.\n");
+ rebind_failed();
}
}
@@ 59,9 70,10 @@ int main(int argc, array argv) {
if(!have_leases) {
werror("Scheduling lease solicitation...\n");
- call_out(begin_solicit, 5, 0);
+ call_out(begin_solicit, random((float)SOL_MAX_DELAY), 0);
} else {
werror("Scheduling lease confirmation...\n");
+ call_out(begin_rebind, random((float)CNF_MAX_DELAY), 0);
}
return -1;
}
@@ 90,53 102,114 @@ void handle_advertise_message(AdvertiseM
if(is_actionable_advertise(message)) {
current_state = NOT_AWAITING;
if(time_out_id) remove_call_out(time_out_id);
-
- current_iapd = message->get_option(OPTION_IAPD);
- mapping lease_data = ([]);
- lease_data->current_iapd = current_iapd;
- lease_data->v6if = v6if;
- lease_data->identifier = identifier;
- lease_data->updated = time();
- lease_data->server_identifier = message->get_option(OPTION_SERVER_IDENTIFIER);
- lease_data->server_address = addr;
- Stdio.write_file(leasefile, encode_value(lease_data));
+
+ write_lease(message, addr);
begin_request(0);
}
+}
+
+void write_lease(DHCPMessage message, string addr, int|void confirmed) {
+ object current_iapd = message->get_option(OPTION_IAPD);
+ mapping ld = ([]);
+ ld->current_iapd = current_iapd;
+ ld->v6if = v6if;
+ ld->identifier = identifier;
+ ld->updated = time();
+ ld->server_identifier = message->get_option(OPTION_SERVER_IDENTIFIER);
+ ld->server_unicast = message->get_option(OPTION_UNICAST);
+ ld->server_address = addr;
+ ld->confirmed = confirmed;
+ lease_data = ld;
+ Stdio.write_file(leasefile, encode_value(lease_data));
}
void handle_reply_message(ReplyMessage message, string addr) {
if(is_actionable_reply(message)) {
- current_state = NOT_AWAITING;
- if(time_out_id) remove_call_out(time_out_id);
+ int have_good_lease = 0;
+ object iapd = message->get_option(OPTION_IAPD);
+
+ foreach(iapd->options;; object option) {
+ if(option->prefix && option->preferred_lifetime)
+ have_good_lease = 1;
+ }
+
+ if(have_good_lease) {
+ current_state = NOT_AWAITING;
+ if(time_out_id) remove_call_out(time_out_id);
+
+ current_iapd_confirmed = 1;
+ object old_option, new_option;
+ if(lease_data && lease_data->confirmed)
+ old_option = lease_data->current_iapd->get_option(OPTION_IA_PDOPTION);
+
+ write_lease(message, addr, 1);
+
+ if(lease_data && lease_data->confirmed)
+ new_option = lease_data->current_iapd->get_option(OPTION_IA_PDOPTION);
+ write_lease(message, addr, 1);
+ trigger_lease(new_option, old_option->eq(new_option));
+ }
+ }
+}
+
+void trigger_lease(object prefix_option, int has_changed) {
+ werror("LEASE COMPLETED: %O\n", lease_data);
+ if(t1_call_out) remove_call_out(t1_call_out);
+ if(t2_call_out) remove_call_out(t2_call_out);
- current_iapd = message->get_option(OPTION_IAPD);
- }
+ // TODO we don't handle prefix lifetimes properly; we should do that.
+ int t1 = lease_data->current_iapd->t1;
+ int t2 = lease_data->current_iapd->t2;
+
+ werror("RENEW scheduled for %d seconds from now: %s\n", t1, ctime(time() + t1));
+ werror("REBIND scheduled for %d seconds from now: %s\n", t2, ctime(time() + t2));
+
+ t1_call_out = call_out(begin_renew, t1);
+ t2_call_out = call_out(begin_rebind, t2);
+
+ call_out(do_trigger_lease, 0, prefix_option, has_changed);
+}
+
+void do_trigger_lease(IA_PDOption allocation, int has_changed) {
+werror("do_trigger_lease(%O, %O)\n", allocation, has_changed);
+}
+
+void do_trigger_abandon(IA_PDOption allocation) {
+}
+
+void rebind_failed() {
+ werror("Abandoning lease.\n");
+ if(t1_call_out) remove_call_out(t1_call_out);
+ if(t2_call_out) remove_call_out(t2_call_out);
+
+ mapping ld = lease_data;
+
+ lease_data = ([]);
+ current_iapd_confirmed = 0;
+
+ call_out(do_trigger_abandon, 0, ld->current_iapd->get_option(OPTION_IA_PDOPTION));
+ rm(leasefile);
+ call_out(begin_solicit, random((float)SOL_MAX_DELAY), 0);
}
int(0..1) is_actionable_advertise(AdvertiseMessage message) {
werror("is_actionable_advertise?\n");
werror("options: %O\n", message->options);
if(!message->has_option(OPTION_CLIENT_IDENTIFIER)) return 0;
-werror("have client identifier\n");
if(!message->has_option(OPTION_SERVER_IDENTIFIER)) return 0;
-werror("have server identifier\n");
- werror("duid: %O -> %O\n", message->get_option(OPTION_CLIENT_IDENTIFIER)->duid->duid, duid->duid);
if(message->get_option(OPTION_CLIENT_IDENTIFIER)->duid != duid) return 0;
-werror("have matching duid\n");
if(message->has_option(OPTION_STATUS_CODE) && message->get_option(OPTION_STATUS_CODE)->status_code == STATUS_NO_PREFIX_AVAILABLE) {
string message = message->get_option(OPTION_STATUS_CODE)->status_message;
if(message) werror("Received status message in advertisement: %s\n", message);
return 0;
}
if(message->has_option(OPTION_IAPD)) {
-werror("have iapd option\n");
IAPD iapd = message->get_option(OPTION_IAPD);
if(!iapd->has_option(OPTION_IA_PDOPTION)) return 0;
if(iapd->iaid != iaid) return 0;
+// TODO handling of T1 and T2 are not correct
if(!iapd->t1 || !iapd->t2) return 0;
-werror("have ia pdoption\n");
IA_PDOption pdo = iapd->get_option(OPTION_IA_PDOPTION);
- werror("pdo: %O\n", pdo);
if(!pdo->prefix) return 0;
return 1;
}
@@ 148,13 221,38 @@ int(0..1) is_actionable_reply(ReplyMessa
if(!message->has_option(OPTION_CLIENT_IDENTIFIER)) return 0;
if(!message->has_option(OPTION_SERVER_IDENTIFIER)) return 0;
if(message->get_option(OPTION_CLIENT_IDENTIFIER)->duid != duid) return 0;
+
+ // TODO: do we need to abandon the lease if we get a status code?
+ if(message->has_option(OPTION_STATUS_CODE) && message->get_option(OPTION_STATUS_CODE)->status_code == STATUS_NO_PREFIX_AVAILABLE) {
+ string message = message->get_option(OPTION_STATUS_CODE)->status_message;
+ werror("Received NO PREFIX AVAILABLE status code\n");
+ if(message) werror("Received status message in advertisement: %s\n", message);
+ return 0;
+ }
+ if(message->has_option(OPTION_STATUS_CODE) && message->get_option(OPTION_STATUS_CODE)->status_code == STATUS_NO_BINDING) {
+ string message = message->get_option(OPTION_STATUS_CODE)->status_message;
+ werror("Received NO BINDING status code\n");
+ if(message) werror("Received status message in advertisement: %s\n", message);
+ rebind_failed();
+ return 0;
+ }
+ if(message->has_option(OPTION_IAPD)) {
+ IAPD iapd = message->get_option(OPTION_IAPD);
+ if(!iapd->has_option(OPTION_IA_PDOPTION)) return 0;
+ if(iapd->iaid != iaid) return 0;
+// TODO handling of T1 and T2 are not correct
+ if(!iapd->t1 || !iapd->t2) return 0;
+ IA_PDOption pdo = iapd->get_option(OPTION_IA_PDOPTION);
+ if(!pdo->prefix) return 0;
+ return 1;
+ }
+
+ return 0;
}
void got_packet(mapping data, mixed ... args) {
werror("got_packet(%O, %O)\n", data, args);
object x = decode_message(data->data);
- werror("x: %O options: %O\n", x, x->options[0]);
- werror("o: %O\n", mkmapping(indices(x->options[0]), values(x->options[0])));
if(current_txn == x->transaction_id) {
switch(x->message_type) {
case MESSAGE_ADVERTISE:
@@ 184,7 282,7 @@ void got_packet(mapping data, mixed ...
void send(DHCPMessage message, string dest, int port) {
string m = message->encode();
- werror("sending message: %O -> %O on port %d\n", message, m, port);
+ werror("sending message: %O -> %O to %O on port %d\n", message, m, dest, port);
dhcp->send(dest, port, m);
}
@@ 200,12 298,23 @@ void begin_request(int|void since) {
call_out(send_request, 0, since);
}
+void begin_renew(int since) {
+ if(t1_call_out) remove_call_out(t1_call_out);
+ call_out(send_renew, 0, since);
+}
+
+void begin_rebind(int since) {
+ if(t1_call_out) remove_call_out(t1_call_out);
+ if(t2_call_out) remove_call_out(t2_call_out);
+ call_out(send_rebind, 0, since);
+}
+
void receive_timed_out(int since, int attempts) {
werror("Hit timeout awaiting actionable messages\n");
int old_state = current_state;
current_state = NOT_AWAITING;
- if(attempts < 3) {
+ if(attempts < 5) {
float timeout = last_timeout + random(last_timeout * 2.0);
if(old_state == AWAIT_ADVERTISE)
call_out(send_solicit, timeout, since, attempts);
@@ 213,14 322,109 @@ void receive_timed_out(int since, int at
call_out(send_request, timeout, since, attempts);
}
else {
- werror("Hit retry limit; backing off.\n");
- call_out(begin_solicit, 60, 0);
+ if(keep_trying) {
+ werror("Hit retry limit; backing off.\n");
+ call_out(begin_solicit, random((float)60), 0);
+ } else {
+ werror("Hit retry limit; giving up.\n");
+ }
+ }
+}
+
+void renew_timed_out(int since, int attempts) {
+ werror("Hit timeout awaiting actionable messages for our RENEW requests\n");
+ int old_state = current_state;
+ current_state = NOT_AWAITING;
+
+ if(attempts < 5) {
+ float timeout = last_timeout + random(last_timeout * 2.0);
+ if(old_state == AWAIT_REPLY)
+ call_out(send_renew, timeout, since, attempts);
+ }
+ else {
+ werror("Hit retry limit; backing off.\n");
+ t1_call_out = call_out(begin_renew, random((float)60), 0, 0);
+ }
+}
+
+void rebind_timed_out(int since, int attempts) {
+ werror("Hit timeout awaiting actionable messages for our REBIND requests\n");
+ int old_state = current_state;
+ current_state = NOT_AWAITING;
+
+ if(attempts < 10) {
+ float timeout = last_timeout + random(last_timeout * 2.0);
+ if(old_state == AWAIT_REPLY)
+ call_out(send_rebind, timeout, since, attempts);
+ }
+ else {
+ werror("Hit retry limit. Abandoning lease\n");
+ rebind_failed();
}
}
+void send_rebind(int since, int attempts, float timeout) {
+ if(current_state != NOT_AWAITING) {
+ throw(Error.Generic("Can't send REBIND if we're already awaiting a message. Current state = " + current_state + "\n"));
+ }
+
+ // are we a re-transmission?
+ if(!since) {
+ since = time();
+ current_txn = generate_transaction_id();
+ }
+
+ object p = RebindMessage(current_txn);
+ object id = ClientIdOption(duid);
+
+ p->options += ({id});
+ p->options += ({OptionRequestOption(({25}))});
+ p->options += ({ElapsedTimeOption(since)});
+ p->options += ({lease_data->current_iapd});
+
+ if(!timeout) timeout = 5.0;
+ last_timeout = timeout;
+ time_out_id = call_out(rebind_timed_out, timeout, since, attempts++);
+ current_state = AWAIT_REPLY;
+
+ send_broadcast(p);
+}
+
+void send_renew(int since, int attempts, float timeout) {
+ if(current_state != NOT_AWAITING) {
+ throw(Error.Generic("Can't send RENEW if we're already awaiting a message. Current state = " + current_state + "\n"));
+ }
+
+ // are we a re-transmission?
+ if(!since) {
+ since = time();
+ current_txn = generate_transaction_id();
+ }
+
+ object p = RenewMessage(current_txn);
+ object id = ClientIdOption(duid);
+ object sid = lease_data->server_identifier;
+
+ p->options += ({id});
+ p->options += ({sid});
+ p->options += ({OptionRequestOption(({25}))});
+ p->options += ({ElapsedTimeOption(since)});
+ p->options += ({lease_data->current_iapd});
+
+ if(!timeout) timeout = 5.0;
+ last_timeout = timeout;
+ time_out_id = call_out(renew_timed_out, timeout, since, attempts++);
+ current_state = AWAIT_REPLY;
+
+ if(lease_data->server_unicast)
+ send(p, lease_data->server_unicast, DHCP_SERVER_PORT);
+ else
+ send_broadcast(p);
+}
+
void send_request(int|void since, int|void attempts, float|void timeout) {
if(current_state != NOT_AWAITING) {
- throw(Error.Generic("Can't send SOLICIT if we're already awaiting a message. Current state = " + current_state + "\n"));
+ throw(Error.Generic("Can't send REQUEST if we're already awaiting a message. Current state = " + current_state + "\n"));
}
// are we a re-transmission?
@@ 231,7 435,23 @@ void send_request(int|void since, int|vo
object p = RequestMessage(current_txn);
object id = ClientIdOption(duid);
-
+ object sid = lease_data->server_identifier;
+
+ p->options += ({id});
+ p->options += ({sid});
+ p->options += ({OptionRequestOption(({25}))});
+ p->options += ({ElapsedTimeOption(since)});
+ p->options += ({lease_data->current_iapd});
+
+ if(!timeout) timeout = 5.0;
+ last_timeout = timeout;
+ time_out_id = call_out(receive_timed_out, timeout, since, attempts++);
+ current_state = AWAIT_REPLY;
+
+ if(lease_data->server_unicast)
+ send(p, lease_data->server_unicast, DHCP_SERVER_PORT);
+ else
+ send_broadcast(p);
}
void send_solicit(int|void since, int|void attempts, float|void timeout) {