M MODULE/Protocols.pmod/DHCPv6.pmod/ClientIdOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 1;
.DUID duid;
+mixed _encode() {
+ return ([ "duid": duid ]);
+}
+
+void _decode(mixed x) {
+ duid = x->duid;
+}
+
protected variant void create(.DUID _duid) {
duid = _duid;
}
M MODULE/Protocols.pmod/DHCPv6.pmod/DHCPMessage.pike +16 -1
@@ 2,6 2,7 @@ constant message_type = 0;
int transaction_id;
array(.DHCPOption) options;
+multiset(int) option_types = (<>);
inherit ADT.struct;
@@ 25,8 26,22 @@ protected void decode_body(Stdio.Buffer
object option;
while(sizeof(this)) {
option = decode_option(buf);
+ if(option) {
+ options += ({option});
+ option_types[option->option_type] = 1;
+ }
}
- if(option) options += ({option});
+}
+
+int(0..1) has_option(int option_type) {
+ return option_types[option_type];
+}
+
+.DHCPOption get_option(int option_type) {
+ foreach(options;; .DHCPOption option)
+ if(option->option_type == option_type) return option;
+
+ return 0;
}
object decode_option(Stdio.Buffer buf) {
M MODULE/Protocols.pmod/DHCPv6.pmod/DUID.pike +18 -0
@@ 13,3 13,21 @@ protected variant void create(int enterp
string encode() {
return duid;
}
+
+mixed _encode() {
+werror("_encode\n");
+ return (["duid": duid]);
+}
+
+void _decode(mixed x) {
+werror("decode: %O\n", x);
+ duid = x->duid;
+}
+
+protected int(0..1) _equal(mixed other) {
+ return objectp(other) && other->duid == duid;
+}
+
+protected int(0..1) `==(mixed other) {
+ return objectp(other) && other->duid == duid;
+}
M MODULE/Protocols.pmod/DHCPv6.pmod/ElapsedTimeOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 8;
int elapsed_time;
+mixed _encode() {
+ return ([ "elapsed_time": elapsed_time ]);
+}
+
+void _decode(mixed x) {
+ elapsed_time = x->elapsed_time;
+}
+
protected variant void create(int since) {
if(!since) elapsed_time = 0;
else {
M MODULE/Protocols.pmod/DHCPv6.pmod/IAID.pike +22 -0
@@ 1,9 1,31 @@
string iaid;
+
+protected variant void create() {}
protected variant void create(string _iaid) {
iaid = _iaid;
}
+mixed _encode() {
+ return (["iaid" : iaid]);
+}
+
+mixed _decode(mixed x) {
+ iaid = x->iaid;
+}
+
string encode() {
return sprintf("%04s", iaid);
}
+
+protected int(0..1) _equal(mixed other) {
+ return objectp(other) && other->iaid == iaid;
+}
+
+protected int(0..1) `==(mixed other) {
+ return objectp(other) && other->iaid == iaid;
+}
+
+protected int _hash() {
+ return hash(iaid);
+}
M MODULE/Protocols.pmod/DHCPv6.pmod/IAPD.pike +28 -4
@@ 1,5 1,4 @@
inherit .DHCPOption;
-constant IA_PD_OPTION = 26;
constant option_type = 25;
.IAID iaid;
@@ 7,6 6,17 @@ int t1;
int t2;
array options;
+multiset(int) option_types = (<>);
+
+mixed _encode() {
+ return (["t1": t1, "t2": t2, "iaid": (iaid)]);
+}
+
+mixed _decode(mixed x) {
+ t1 = x->t1;
+ t2 = x->t2;
+ iaid = x->iaid;
+}
protected variant void create(.IAID _iaid, int t1_secs, int t2_secs, array _options) {
iaid = _iaid;
@@ 36,9 46,12 @@ werror("parsing IAPD\n");
object option;
while(sizeof(buf)) {
- option = decode_pd_option(buf);
+ option = decode_pd_option(buf);
- if(option) options +=({option});
+ if(option) {
+ options +=({option});
+ option_types[option->option_type] = 1;
+ }
}
}
@@ 47,7 60,7 @@ object decode_pd_option(Stdio.Buffer buf
int pd_type = buf->read_int(2);
- if(pd_type != IA_PD_OPTION) {
+ if(pd_type != .OPTION_IA_PDOPTION) {
throw(Error.Generic("Received invalid IA_PD option type " + pd_type +".\n"));
}
// rk->rewind();
@@ 56,3 69,14 @@ object decode_pd_option(Stdio.Buffer buf
object option = .IA_PDOption(buf->read_hstring(2));
return option;
}
+
+int(0..1) has_option(int option_type) {
+ return option_types[option_type];
+}
+
+.DHCPOption get_option(int option_type) {
+ foreach(options;; .DHCPOption option)
+ if(option->option_type == option_type) return option;
+
+ return 0;
+}
M MODULE/Protocols.pmod/DHCPv6.pmod/IA_PDOption.pike +13 -0
@@ 10,6 10,19 @@ string address;
array options;
+mixed _encode() {
+ return (["preferred_lifetime": preferred_lifetime, "valid_lifetime": valid_lifetime,
+ "prefix": prefix, "address": address, "options": options]);
+}
+
+void _decode(mixed x) {
+ preferred_lifetime = x->preferred_lifetime;
+ valid_lifetime = x->valid_lifetime;
+ prefix = x->prefix;
+ address = x->address;
+ options = x->options;
+}
+
protected variant void create(int _preferred_lifetime, int _valid_lifetime, int _prefix, string|int(0..0) _address) {
preferred_lifetime = _preferred_lifetime;
valid_lifetime = _valid_lifetime;
M MODULE/Protocols.pmod/DHCPv6.pmod/OptionRequestOption.pike +8 -0
@@ 7,6 7,14 @@ protected void create(array(array(int))
options = _options;
}
+mixed _encode() {
+ return (["options": options]);
+}
+
+void _decode(mixed x) {
+ options = x->options;
+}
+
protected variant void create() {
options = ({});
}
M MODULE/Protocols.pmod/DHCPv6.pmod/ServerIdOption.pike +8 -0
@@ 4,6 4,14 @@ constant option_type = 2;
.DUID duid;
+mixed _encode() {
+ return ([ "duid": duid ]);
+}
+
+void _decode(mixed x) {
+ duid = x->duid;
+}
+
protected variant void create(.DUID _duid) {
duid = _duid;
}
M MODULE/Protocols.pmod/DHCPv6.pmod/StatusCodeOption.pike +9 -0
@@ 5,6 5,15 @@ constant option_type = 13;
int status_code;
string status_message;
+mixed _encode() {
+ return ([ "status_code": status_code, "status_message": status_message ]);
+}
+
+void _decode(mixed x) {
+ status_code = x->status_code;
+ status_message = x->status_message;
+}
+
protected variant void create(int code, string message) {
status_code = code;
status_message = message;
M MODULE/Protocols.pmod/DHCPv6.pmod/module.pmod +10 -0
@@ 15,6 15,16 @@ constant MESSAGE_INFORMATION_REQUEST = 1
constant MESSAGE_RELAY_FORW = 12;
constant MESSAGE_RELAY_REPL = 13;
+constant OPTION_IAPD = 25;
+constant OPTION_IA_PDOPTION = 26;
+constant OPTION_CLIENT_IDENTIFIER = 1;
+constant OPTION_SERVER_IDENTIFIER = 2;
+constant OPTION_OPTION_REQUEST = 6;
+constant OPTION_ELAPSED_TIME = 8;
+constant OPTION_STATUS_CODE = 13;
+
+constant STATUS_NO_PREFIX_AVAILABLE = 6;
+
protected void create() {
foreach(values(Protocols.DHCPv6);; mixed p) {
if(!programp(p)) continue;
M dhcpv6_pd.pike +214 -14
@@ 1,54 1,254 @@
+import Protocols.DHCPv6;
constant DHCP_CLIENT_PORT = 546;
constant DHCP_SERVER_PORT = 547;
+constant NOT_AWAITING = 0;
+constant AWAIT_ADVERTISE = 1;
+constant AWAIT_REPLY = 2;
+
constant DHCP_BROADCAST_ADDRESS = "FF02::1:2";
+string leasefile = "/var/run/dhcpv6_pd_leases";
string v6if = "net0";
string identifier;
+string localv6addr;
+int have_leases;
+mapping lease_data = ([]);
+
+int current_state = NOT_AWAITING;
+int current_txn;
+mixed time_out_id;
+float last_timeout = 0.0;
Stdio.UDP dhcp;
-Protocols.DHCPv6.DUID duid;
+DUID duid;
+IAID iaid;
+
+IAPD current_iapd;
+int current_iapd_confirmed;
int main(int argc, array argv) {
identifier = Standards.JSON.decode(Process.popen("sysinfo"))->UUID;
+ duid = DUID(0, identifier);
werror("Identifier: %O\n", identifier);
+ localv6addr = get_if_address(v6if);
+ if(!localv6addr) {
+ fatal("Unable to determine IPv6 address for " + v6if + ".");
+ }
+ iaid = IAID(Crypto.MD5.hash(v6if)[0..3]);
+
+ werror("Address: %O\n", localv6addr);
+ string leases = Stdio.read_file(leasefile);
+ if(leases) {
+ 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 {
+ werror("Have leases for Prefix Delegation, will attempt to reconfirm them.\n");
+ lease_data = ld;
+ have_leases = 1;
+ }
+ }
+
dhcp = Stdio.UDP();
- dhcp->bind(DHCP_CLIENT_PORT, "2001:558:6003:2a:79b0:ea90:48d2:3ad7");
+ dhcp->bind(DHCP_CLIENT_PORT, localv6addr);
dhcp->enable_broadcast();
dhcp->set_nonblocking();
dhcp->set_read_callback(got_packet);
-call_out(send_solicit, 5);
+
+ if(!have_leases) {
+ werror("Scheduling lease solicitation...\n");
+ call_out(begin_solicit, 5, 0);
+ } else {
+ werror("Scheduling lease confirmation...\n");
+ }
return -1;
}
+void fatal(string message, int|void retcode) {
+ werror("FATAL: %s\n", message);
+ exit(retcode||1);
+}
+
+string get_if_address(string v6if) {
+
+ mapping ifs = NetUtils.local_interfaces();
+
+ foreach(ifs; string iface; array addrs) {
+ if(iface == v6if || has_prefix(iface, v6if + ":")) {
+ foreach(addrs;; string addr) {
+ if(NetUtils.get_network_type(addr, 1) == "localhostv6") return (addr/"/")[0];
+ }
+ }
+ }
+
+ return 0;
+}
+
+void handle_advertise_message(AdvertiseMessage message, string addr) {
+ 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));
+ }
+}
+
+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);
+
+ current_iapd = message->get_option(OPTION_IAPD);
+ }
+}
+
+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;
+ 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;
+ }
+
+ return 0;
+}
+
+int(0..1) is_actionable_reply(ReplyMessage message) {
+ 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;
+}
+
void got_packet(mapping data, mixed ... args) {
werror("got_packet(%O, %O)\n", data, args);
- object x = Protocols.DHCPv6.decode_message(data->data);
+ 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:
+ if(current_state == AWAIT_ADVERTISE) {
+ werror("Got an ADVERTISE message.\n");
+ handle_advertise_message(x, data->ip);
+ } else {
+ werror("Got an ADVERTISE message at an inappropriate time. Ignoring.\n");
+ }
+ break;
+ case MESSAGE_REPLY:
+ if(current_state == AWAIT_REPLY) {
+ werror("Got a REPLY message.\n");
+ handle_reply_message(x, data->ip);
+ } else {
+ werror("Got a REPLY at an inappropriate time. Ignoring.\n");
+ }
+ break;
+ default:
+ werror("Ignoring message of type " + x->message_type + ".\n");
+ }
+
+ } else {
+ werror("Ignoring message for someone else's transaction.\n");
+ }
}
-void send(Protocols.DHCPv6.DHCPMessage message, string dest, int port) {
+void send(DHCPMessage message, string dest, int port) {
string m = message->encode();
werror("sending message: %O -> %O on port %d\n", message, m, port);
dhcp->send(dest, port, m);
}
-void send_broadcast(Protocols.DHCPv6.DHCPMessage message) {
+void send_broadcast(DHCPMessage message) {
send(message, DHCP_BROADCAST_ADDRESS, DHCP_SERVER_PORT);
}
-void send_solicit() {
- object p = Protocols.DHCPv6.SolicitMessage(Protocols.DHCPv6.generate_transaction_id());
- object id = Protocols.DHCPv6.ClientIdOption(Protocols.DHCPv6.DUID(0, identifier));
- object pd_opt = Protocols.DHCPv6.IA_PDOption(3600*24, 3600*36, 64, 0);
- object ia_ident = Protocols.DHCPv6.IAID(Crypto.MD5.hash(v6if)[0..3]);
+void begin_solicit(int|void since) {
+ call_out(send_solicit, 5, 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) {
+ float timeout = last_timeout + random(last_timeout * 2.0);
+ if(old_state == AWAIT_ADVERTISE)
+ call_out(send_solicit, timeout, since, attempts);
+ else if(old_state == AWAIT_REPLY)
+ call_out(send_request, timeout, since, attempts);
+ }
+ else {
+ werror("Hit retry limit; backing off.\n");
+ call_out(begin_solicit, 60, 0);
+ }
+}
+
+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"));
+ }
+
+ // are we a re-transmission?
+ if(!since) {
+ since = time();
+ current_txn = generate_transaction_id();
+ }
+}
+
+void send_solicit(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"));
+ }
+
+ // are we a re-transmission?
+ if(!since) {
+ since = time();
+ current_txn = generate_transaction_id();
+ }
+ object p = SolicitMessage(current_txn);
+ object id = ClientIdOption(duid);
+ object pd_opt = IA_PDOption(3600*24, 3600*36, 64, 0);
+ object ia_ident = iaid;
p->options += ({id});
- p->options += ({Protocols.DHCPv6.OptionRequestOption()});
- p->options += ({Protocols.DHCPv6.ElapsedTimeOption(0)});
- p->options += ({Protocols.DHCPv6.IAPD(ia_ident, 3600*24, 3600*36, ({pd_opt}))
+ p->options += ({OptionRequestOption()});
+ p->options += ({ElapsedTimeOption(since)});
+ p->options += ({IAPD(ia_ident, 3600*24, 3600*36, ({pd_opt}))
});
+
+ if(!timeout) timeout = 5.0;
+ last_timeout = timeout;
+ time_out_id = call_out(receive_timed_out, timeout, since, attempts++);
+ current_state = AWAIT_ADVERTISE;
send_broadcast(p);
}