123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395modulePacket=structtypet={lladdr:Macaddr.t;dst:Ipaddr.V6.t;len:int;fn:src:Ipaddr.V6.t->Bstr.t->unit}end(* NOTE(dinosaure): There are two things to keep in mind regarding NDPv6: the
algorithm can (and will) send packets whose destination is always On-Link,
meaning that we do not have to resolve the "next hop" as soon as NDPv6 wants
to send packets: we should always have the destination MAC address.
The packets we are trying to send are always less than 1280 bytes, which is
the minimum MTU according to IPv6. We should therefore not worry about the
[`Packet_too_big] error that destinations may send us.
Finally, at this point, we still do not know the source MAC address or the
source IPv6 address. The packets are therefore encoded so that they wait for
the IPv6 address we want to use and, through currying, produce a function
that fully writes the IPv6 packet that our algorithm wants to send. *)(* Neighbor Advertisement *)moduleNA=structtypet={router:bool;solicited:bool;override:bool;target:Ipaddr.V6.t;tlla:Macaddr.toption}letppppft=Fmt.pfppf"{ @[<hov>router=@ %b;@ solicited=@ %b;@ override=@ %b;@ target=@ %a;@ \
tlla=@ %a;@] }"t.routert.solicitedt.overrideIpaddr.V6.ppt.targetFmt.(Dump.optionMacaddr.pp)t.tllaendletcs_of_len_and_protocol=lettmp=Cstruct.create8infun~len~protocol->Cstruct.BE.set_uint32tmp0(Int32.of_intlen);Cstruct.BE.set_uint32tmp4(Int32.of_intprotocol);tmpmoduleNS=structtypet={target:Ipaddr.V6.t;slla:Macaddr.toption}letppppft=Fmt.pfppf"{ @[<hov>target=@ %a;@ slla=@ %a;@] }"Ipaddr.V6.ppt.targetFmt.(Dump.optionMacaddr.pp)t.sllaletencode_into~lladdr~dstt=letpayload_len=matcht.sllawithNone->24|Some_->32inletlen=payload_len+40inletfn~srcbstr=Bstr.set_int32_bebstr00x60000000l;Bstr.set_uint16_bebstr4payload_len;Bstr.set_uint8bstr658(* ICMPv6 *);Bstr.set_uint8bstr7255(* HOP limit *);letsrc=Ipaddr.V6.to_octetssrcinBstr.blit_from_stringsrc~src_off:0bstr~dst_off:8~len:16;letdst=Ipaddr.V6.to_octetsdstinBstr.blit_from_stringdst~src_off:0bstr~dst_off:24~len:16;Bstr.set_uint8bstr40135(* NS *);Bstr.set_uint8bstr410;Bstr.set_uint16_bebstr420;Bstr.set_int32_bebstr440l;lettarget=Ipaddr.V6.to_octetst.targetinBstr.blit_from_stringtarget~src_off:0bstr~dst_off:48~len:16;beginmatcht.sllawith|None->()|Somelladdr->Bstr.set_uint8bstr641;Bstr.set_uint8bstr651;letlladdr=Macaddr.to_octetslladdrinBstr.blit_from_stringlladdr~src_off:0bstr~dst_off:66~len:6end;letcs0=Cstruct.of_bigarraybstr~off:8~len:32inletcs1=cs_of_len_and_protocol~len:payload_len~protocol:58inletcs2=Cstruct.of_bigarraybstr~off:40~len:payload_leninletchk=0inletchk=Utcp.Checksum.feed_cstructchkcs0inletchk=Utcp.Checksum.feed_cstructchkcs1inletchk=Utcp.Checksum.feed_cstructchkcs2inletchk=Utcp.Checksum.finallychkinBstr.set_uint16_bebstr42chkin{Packet.lladdr;dst;len;fn}endmoduleNeighbor=structtypestate=|Incompleteof{expire_at:int;sent_probes:int}|Reachableof{lladdr:Macaddr.t;expire_at:int}|StaleofMacaddr.t|Delayof{lladdr:Macaddr.t;expire_at:int}|Probeof{lladdr:Macaddr.t;expire_at:int;sent_probes:int}typet=state*boolletlladdr=function|Reachable{lladdr;_}|Stalelladdr|Delay{lladdr;_}|Probe{lladdr;_}->Somelladdr|Incomplete_->Noneletweight(_t:t)=1endmoduleNeighbors=Lru.F.Make(Ipaddr.V6)(Neighbor)typet=Neighbors.tletmakecapacity=Neighbors.emptycapacityletsolicited_node_prefix=Ipaddr.V6.Prefix.of_string_exn"ff02::1:ff00:0/104"let_1s=1_000_000_000let_5s=5_000_000_000let_30s=30_000_000_000typeaction=|PacketofPacket.t|CancelofIpaddr.V6.t|Release_withofIpaddr.V6.t*Macaddr.t(* NOTE(dinosaure): For simplicity's sake, the transition produces at most a
single action. This action can be "expanded" to send multiple packets, but
this expansion is done later (outside of NDPv6). For now, this is sufficient,
and we can use the [List.cons]/[List.rev] pair rather than [List.rev_append]
when aggregating all actions for all our entries. *)lettransition~mackey(state,is_router)nowevent=letopenNeighborinmatch(state,event)with(* | INCOMPLETE | Retransmit timeout, | Retransmit NS | INCOMPLETE
| | less than N | Start retransmit |
| | retransmissions. | timer |
| | | |
| INCOMPLETE | Retransmit timeout, | Discard entry | -
| | N or more | Send ICMP error |
| | retransmissions. | |
*)|Incomplete{expire_at:int;sent_probes;_},_whenexpire_at<=now->ifsent_probes>=3(* MAX_MULTICAST_SOLICIT *)then(None,Some(Cancelkey))elsebeginletexpire_at=now+_1sinletsent_probes=sent_probes+1inletdst=Ipaddr.V6.Prefix.network_addresssolicited_node_prefixkeyinassert(Ipaddr.V6.is_multicastdst);letlladdr=Ipaddr.V6.multicast_to_macdstin(* RFC 4861: SLLA must be the sender's link-layer address *)letns={NS.target=key;slla=Somemac}inletpkt=NS.encode_into~lladdr~dstnsinletaction=Some(Packetpkt)in(Some(Incomplete{expire_at;sent_probes},is_router),action)end(* | REACHABLE | timeout, more than | - | STALE
| | N seconds since | |
| | reachability confirm. | |
*)|Reachable{expire_at;lladdr},_whenexpire_at<=now->(Some(Stalelladdr,is_router),None)(* | DELAY | Delay timeout | Send unicast NS probe | PROBE
| | | Start retransmit timer |
*)|Delay{lladdr;expire_at},_whenexpire_at<=now->letexpire_at=now+_1sinletsent_probes=1in(* RFC 4861: SLLA must be the sender's link-layer address *)letns={NS.target=key;slla=Somemac}inletpkt=NS.encode_into~lladdr~dst:keynsinletaction=Some(Packetpkt)in(Some(Probe{lladdr;expire_at;sent_probes},is_router),action)(* | PROBE | Retransmit timeout, | Retransmit NS | PROBE
| | less than N | |
| | retransmissions. | |
| | | |
| PROBE | Retransmit timeout, | Discard entry | -
| | N or more | |
| | retransmissions. | |
*)|Probe{lladdr;expire_at;sent_probes},_whenexpire_at<=now->ifsent_probes>=3(* MAX_UNICAST_SOLICIT *)then(None,None)elsebeginletexpire_at=now+_1sinletsent_probes=sent_probes+1in(* RFC 4861: SLLA must be the sender's link-layer address *)letns={NS.target=key;slla=Somemac}inletpkt=NS.encode_into~lladdr~dst:keynsinletaction=Some(Packetpkt)in(Some(Probe{lladdr;expire_at;sent_probes},is_router),action)end(* | INCOMPLETE | NA, Solicited=1, | Record link-layer | REACHABLE
| | Override=any | address. Send queued |
| | | packets. |
*)|Incomplete_,`NA(_src,_dst,{NA.solicited=true;tlla=Somelladdr;_})->letexpire_at=now+_30sin(Some(Reachable{lladdr;expire_at},is_router),Some(Release_with(key,lladdr)))(* | INCOMPLETE | NA, Solicited=0, | Record link-layer | STALE
| | Override=any | address. Send queued |
| | | packets. |
*)|Incomplete_,`NA(_src,_dst,{NA.solicited=false;tlla=Somelladdr;_})->(Some(Stalelladdr,is_router),Some(Release_with(key,lladdr)))(* | INCOMPLETE | NA, Solicited=any, | Update content of | unchanged
| | Override=any, No | IsRouter flag |
| | Link-layer address | |
*)|Incomplete_,`NA(_src,_dst,{NA.tlla=None;_})->(Some(state,true),None)(* | REACHABLE | NA, Solicited=1, | - | STALE
| | Override=0 | |
| | Different link-layer | |
| | address than cached. | |
*)|(Reachable{lladdr;_},`NA(_src,_dst,{NA.solicited=true;tlla=Somelladdr';_}))->ifMacaddr.comparelladdrlladdr'!=0then(Some(Stalelladdr,is_router),None)else(Some(state,is_router),None)(* | !INCOMPLETE | NA, Solicited=1, | - | REACHABLE
| | Override=0 | |
| | Same link-layer | |
| | address as cached. | |
| | | |
| STALE, PROBE | NA, Solicited=1, | - | unchanged
| Or DELAY | Override=0 | |
| | Different link-layer | |
*)|((Stale_|Probe_|Delay_),`NA(_src,_dst,{NA.solicited=true;tlla=Somelladdr;override=false;_}))->letlladdr'=Neighbor.lladdrstateinletlladdr'=Option.getlladdr'inifMacaddr.comparelladdrlladdr'=0thenletexpire_at=now+_30sin(Some(Reachable{lladdr;expire_at},is_router),None)else(Some(state,is_router),None)(* | !INCOMPLETE | NA, Solicited=0, | - | unchanged
| | Override=1 | |
| | Same link-layer | |
| | address as cached. | |
| | | |
| !INCOMPLETE | NA, Solicited=1, | Record link-layer | REACHABLE
| | Override=1 | address (if |
| | | different). |
| | | |
| !INCOMPLETE | NA, Solicited=0, | Record link-layer | STALE
| | Override=1 | address. |
| | Different link-layer | |
| | address than cached. | |
*)|((Stale_|Probe_|Delay_|Reachable_),`NA(_src,_dst,{NA.solicited;override=true;tlla=Somelladdr;_}))->letlladdr'=Neighbor.lladdrstateinletlladdr'=Option.getlladdr'inif(notsolicited)&&Macaddr.comparelladdrlladdr'=0then(Some(state,is_router),None)elseifsolicitedthenletexpire_at=now+_30sin(Some(Reachable{lladdr;expire_at},is_router),None)else(* not solicited && Macaddr.compare lladdr lladdr' <> 0 *)let()=assert(notsolicited)inlet()=assert(Macaddr.comparelladdrlladdr'<>0)in(Some(Stalelladdr,is_router),None)(* | !INCOMPLETE | NA, Solicited=any, | Update content of | unchanged
| | Override=any, No | IsRouter flag. |
| | link-layer address | |
*)|((Stale_|Probe_|Delay_|Reachable_),`NA(_src,_dst,{NA.tlla=None;router;_}))->(Some(state,router),None)(* | !INCOMPLETE | NA, Solicited=0, | - | unchanged
| | Override=0 | |
*)|((Stale_|Probe_|Delay_|Reachable_),`NA(_src,_dst,{NA.solicited=false;override=false;_}))->(Some(state,is_router),None)(* 7.2.3. Receipt of Neighbor Solicitations
... the recipient SHOULD create or update the Neighbor Cache entry for the
IP Source Address of the solicitation. If an entry does not already exist,
the node SHOULD create a new one and set its reachability state to STALE as
specified in Section 7.3.3. If an entry already exists, and the cached
link-layer address differs from the one in the received Source Link-Layer
option, the cached address should be replaced by the received address, and
the entry's reachability state MUST be set to STALE. *)|Incomplete_,`NS(src,_dst,{NS.slla=Somelladdr;_})->ifIpaddr.V6.comparekeysrc=0thenletstate=Stalelladdrin(Some(state,is_router),Some(Release_with(key,lladdr)))elseletstate=Stalelladdrin(Some(state,false),None)|((Stale_|Probe_|Delay_|Reachable_),`NS(src,_dst,{NS.slla=Somelladdr;_}))->letlladdr'=Neighbor.lladdrstateinletlladdr'=Option.getlladdr'inifIpaddr.V6.comparekeysrc=0&&Macaddr.comparelladdrlladdr'<>0thenletstate=Stalelladdrin(Some(state,is_router),None)elseletstate=Stalelladdrin(Some(state,false),None)|(Incomplete_|Reachable_|Delay_|Probe_|Stale_),_->(Some(state,is_router),None)lettickt~mac~nowevent=letfnkeyvalue(actions,t')=letpush=Option.fold~none:actions~some:(Fun.flipList.consactions)inmatchtransition~mackeyvaluenoweventwith|Somevalue',action->lett'=Neighbors.addkeyvalue't'in(pushaction,t')|None,action->(pushaction,t)in(* NOTE(dinosaure): even if we can [fold_k] here (which performs better), we
would like to keep the usage order to clean up then. *)letcapacity=Neighbors.capacitytinletactions,t'=Neighbors.foldfn([],Neighbors.emptycapacity)tin(List.revactions,Neighbors.trimt')letlladdrtaddr=matchOption.mapfst(Neighbors.findaddrt)with|None|Some(Neighbor.Incomplete_)->None|Some(Stalelladdr|Reachable{lladdr;_}|Delay{lladdr;_}|Probe{lladdr;_})->Somelladdrletis_reachabletaddr=matchOption.mapfst(Neighbors.findaddrt)with|Some(Neighbor.Incomplete_)|None->false|_->trueletis_routertaddr=Option.mapsnd(Neighbors.findaddrt)letqueryt~mac~nowaddr=(* | - | Packet to send. | Create entry. | INCOMPLETE
| | | Send multicast NS. |
| | | Start retransmit timer |
*)matchNeighbors.findaddrtwith|None->letexpire_at=now+_1sinletsent_probes=0inletstate=Neighbor.Incomplete{expire_at;sent_probes}inletdst=Ipaddr.V6.Prefix.network_addresssolicited_node_prefixaddrinassert(Ipaddr.V6.is_multicastdst);letlladdr=Ipaddr.V6.multicast_to_macdstin(* RFC 4861: SLLA must be the sender's link-layer address *)letns={NS.target=addr;slla=Somemac}inletpkt=NS.encode_into~lladdr~dstnsinletaction=Some(Packetpkt)inlett=Neighbors.addaddr(state,false)tinlett=Neighbors.trimtin(t,None,action)|SomeNeighbor.(Incomplete_,_)->(t,None,None)(* | !INCOMPLETE | upper-layer reachability | - | REACHABLE
| | confirmation | |
TODO(dinosaure): not sure that it's currently on this case
that we should set the state to REACHABLE.
*)|Some((Neighbor.Reachable{lladdr;_}|Delay{lladdr;_}|Probe{lladdr;_}),_)->(t,Somelladdr,None)(* | STALE | Sending packet | Start delay timer | DELAY *)|Some(Neighbor.Stalelladdr,is_router)->letexpire_at=now+_5sinletstate=Neighbor.Delay{lladdr;expire_at}inlett=Neighbors.removeaddrtinlett=Neighbors.addaddr(state,is_router)tin(t,Somelladdr,None)