Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Source file dhcp_client.ml
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239(* a variant type representing the current [state] of the client transaction.
Represented states differ from the diagram presented in RFC2131 in the
following ways:
The earliest state is `Selecting`. There is no representation of INIT-REBOOT,
REBOOTING, or INIT. Calls to `create` will generate a client in state
`Selecting` with the corresponding `DHCPDISCOVER` recorded, and that packet
is exposed to the caller of `create`, who is responsible for sending it.
There is no REBINDING state. Clients which do not re-enter the `Bound` state
from `Renewing` do not halt their network and re-enter the `Selecting` state.
*)typestate=|SelectingofDhcp_wire.pkt(* dhcpdiscover sent *)|Requestingof(Dhcp_wire.pkt*Dhcp_wire.pkt)(* dhcpoffer input * dhcprequest sent *)|BoundofDhcp_wire.pkt(* dhcpack received *)|Renewingof(Dhcp_wire.pkt*Dhcp_wire.pkt)(* dhcpack received, dhcprequest sent *)(* `srcmac` will be used as the source of Ethernet frames,
as well as the client identifier whenever one is required (e.g. padded with
0x00 in the `chaddr` field of the BOOTP message).
`request_options` will be sent in DHCPDISCOVER and DHCPREQUEST packets. *)typet={srcmac:Macaddr.t;request_options:Dhcp_wire.option_codelist;state:state;}(* constant fields are represented here for convenience.
This module can then be locally opened where required *)moduleConstants=structopenDhcp_wirelethtype=Ethernet_10mblethlen=6(* length of a mac address in bytes *)lethops=0letsname=""letfile=""end(* This are the options that Windows 10 uses in the PRL implement RFC7844.
They are ordered by code number.
TODO: There should be a variable in the configuration where the user
specifies to use the Anonymity Profiles, and ignore any other option that
would modify this static PRL.
This PRL could be also reverted to the minimal one and be used only when
using Anonymity Profiles.
*)(* if the caller of `Dhcp_client.create` has not requested their own list of
Dhcp_wire.option_code , provide a default one with the minimum set of things
usually required for a working network connection in MirageOS. *)letdefault_requests=Dhcp_wire.([SUBNET_MASK;ROUTERS;DNS_SERVERS;DOMAIN_NAME;PERFORM_ROUTER_DISC;STATIC_ROUTES;VENDOR_SPECIFIC;NETBIOS_NAME_SERVERS;NETBIOS_NODE;NETBIOS_SCOPE;CLASSLESS_STATIC_ROUTE;PRIVATE_CLASSLESS_STATIC_ROUTE;WEB_PROXY_AUTO_DISC;])(* a pretty-printer for the client, useful for debugging and logging. *)letppfmtp=letpp_statefmt=function|Selectingpkt->Format.fprintffmt"SELECTING. Generated %a"Dhcp_wire.pp_pktpkt|Requesting(received,sent)->Format.fprintffmt"REQUESTING. Received %a, and generated response %a"Dhcp_wire.pp_pktreceivedDhcp_wire.pp_pktsent|Boundpkt->Format.fprintffmt"BOUND. Received %a"Dhcp_wire.pp_pktpkt|Renewing(ack,request)->Format.fprintffmt"RENEWING. Have lease %a, generated request %a"Dhcp_wire.pp_pktackDhcp_wire.pp_pktrequestinFormat.fprintffmt"%a: %a"Macaddr.ppp.srcmacpp_statep.state(* the lease function lets callers know whether the abstract (to them) lease
object carries a usable network configuration. *)letlease{state;_}=matchstatewith|Bounddhcpack|Renewing(dhcpack,_)->Somedhcpack|Requesting_|Selecting_->None(* a convenience function for retrieving the most recently used transaction id.
I don't know why this is needed or useful for anyone; it should probaby be
removed. *)letxid{state;_}=letopenDhcp_wireinmatchstatewith|Selectingp->p.xid|Requesting(_i,o)->o.xid|Bounda->a.xid|Renewing(_i,o)->o.xid(* given a set of information, assemble a DHCPREQUEST packet from the Constants
module and other constants defined in Dhcp_wire. *)letmake_request?(ciaddr=Ipaddr.V4.any)~xid~chaddr~srcmac~siaddr~options()=letopenDhcp_wireinConstants.({htype;hlen;hops;sname;file;xid;chaddr;srcport=Dhcp_wire.client_port;dstport=Dhcp_wire.server_port;srcmac;srcip=Ipaddr.V4.any;(* destinations should still be broadcast,
* even though we have the necessary information to send unicast,
* because there might be >1 DHCP server on the network.
* those who we're not responding to should know that we're in a
* transaction to accept another lease. *)dstmac=Macaddr.broadcast;dstip=Ipaddr.V4.broadcast;op=BOOTREQUEST;options;secs=0;flags=Broadcast;ciaddr;yiaddr=Ipaddr.V4.any;siaddr;giaddr=Ipaddr.V4.any;})(* respond to an incoming DHCPOFFER. *)letoffert~xid~chaddr~server_ip~request_ip~offer_options:_=letopenDhcp_wirein(* TODO: make sure the offer contains everything we expect before we accept it *)letoptions=[Message_typeDHCPREQUEST;Request_iprequest_ip;Server_identifierserver_ip;]inletoptions=matcht.request_optionswith|[]->options(* if this is the case, the user explicitly requested it; honor that *)|_::_->(Parameter_requestst.request_options)::optionsinmake_request~xid~chaddr~srcmac:t.srcmac~siaddr:server_ip~options:options()(* make a new DHCP client. allow the user to request a specific xid, any
requests, and the MAC address to use as the source for Ethernet messages and
the chaddr in the fixed-length part of the message *)letcreate?requestsxidsrcmac=letopenConstantsinletopenDhcp_wireinletrequests=matchrequestswith|None|Some[]->default_requests|Somerequests->requestsinletpkt={htype;hlen;hops;sname;file;srcmac;dstmac=Macaddr.broadcast;srcip=Ipaddr.V4.any;dstip=Ipaddr.V4.broadcast;srcport=client_port;dstport=server_port;op=BOOTREQUEST;xid;secs=0;flags=Broadcast;ciaddr=Ipaddr.V4.any;yiaddr=Ipaddr.V4.any;siaddr=Ipaddr.V4.any;giaddr=Ipaddr.V4.any;chaddr=srcmac;options=[Message_typeDHCPDISCOVER;Client_id(Hwaddrsrcmac);Parameter_requestsrequests;];}in{srcmac;request_options=requests;state=Selectingpkt},pkt(* for a DHCP client, figure out whether an incoming packet should modify the
state, and if a response message is warranted, generate it.
Defined transitions are:
Selecting -> DHCPOFFER -> Requesting
Requesting -> DHCPACK -> Bound
Requesting -> DHCPNAK -> Selecting
Renewing -> DHCPACK -> Bound
Renewing -> DHCPNAK -> Selecting
*)letinputtbuf=letopenDhcp_wireinmatchpkt_of_bufbuf(Cstruct.lengthbuf)with|Error_->`Noop|Okincoming->(* RFC2131 4.4.1: respond only to messages for our xid *)ifcompareincoming.xid(xidt)=0thenbeginmatchfind_message_typeincoming.options,t.statewith|None,_->`Noop|SomeDHCPOFFER,Selectingdhcpdiscover->(* "the mechanism used to select one DHCPOFFER [is] implementation
dependent" (RFC2131) so just take the first one *)letdhcprequest=offert~server_ip:incoming.siaddr~request_ip:incoming.yiaddr~offer_options:incoming.options~xid:dhcpdiscover.xid~chaddr:dhcpdiscover.chaddrin`Response({twithstate=Requesting(incoming,dhcprequest)},dhcprequest)|SomeDHCPOFFER,_->(* DHCPOFFER is irrelevant when we're not selecting *)`Noop|SomeDHCPACK,Renewing_|SomeDHCPACK,Requesting_->`New_lease({twithstate=Boundincoming},incoming)|SomeDHCPNAK,Requesting_|SomeDHCPNAK,Renewing_->`Response(create~requests:t.request_options(xidt)t.srcmac)|SomeDHCPACK,Selecting_(* too soon *)|SomeDHCPACK,Bound_->(* too late *)`Noop|SomeDHCPDISCOVER,_|SomeDHCPDECLINE,_|SomeDHCPRELEASE,_|SomeDHCPINFORM,_|SomeDHCPREQUEST,_->(* we don't need to care about these client messages *)`Noop|SomeDHCPNAK,Selecting_|SomeDHCPNAK,Bound_->`Noop(* irrelevant *)|SomeDHCPLEASEQUERY,_|SomeDHCPLEASEUNASSIGNED,_|SomeDHCPLEASEUNKNOWN,_|SomeDHCPLEASEACTIVE,_|SomeDHCPBULKLEASEQUERY,_|SomeDHCPLEASEQUERYDONE,_->(* these messages are for relay agents to extract information from servers;
* our client does not care about them and shouldn't reply *)`Noop|SomeDHCPFORCERENEW,_->`Noop(* unsupported *)endelse`Noop(* try to renew the lease, probably because some time has elapsed. *)letrenewt=matcht.statewith|Selecting_|Requesting_->`Noop|Renewing(_lease,request)->`Response(t,request)|Boundlease->letopenDhcp_wireinletrequest=offert~xid:lease.xid~chaddr:lease.chaddr~server_ip:lease.siaddr~request_ip:lease.yiaddr~offer_options:lease.optionsinletstate=Renewing(lease,request)in`Response({twithstate=state},request)