123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133(*
* Copyright (c) 2014 Anil Madhavapeddy <anil@recoil.org>
* Copyright (c) 2014 Nicolas Ojeda Bar <n.oje.bar@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)letsrc=Logs.Src.create"udpv6-socket"~doc:"UDP socket v6 (platform native)"moduleLog=(valLogs.src_logsrc:Logs.LOG)openLwt.Infixtypeipaddr=Ipaddr.V6.ttypecallback=src:ipaddr->dst:ipaddr->src_port:int->Cstruct.t->unitLwt.ttypet={interface:Unix.inet_addr;(* source ip to bind to *)listen_fds:((Unix.inet_addr*int),Lwt_unix.file_descr)Hashtbl.t;(* UDPv6 fds bound to a particular source ip/port *)mutableswitched_off:unitLwt.t;}letset_switched_offtswitched_off=t.switched_off<-Lwt.pick[switched_off;t.switched_off]letignore_canceled=function|Lwt.Canceled->Lwt.return_unit|exn->raiseexnletget_udpv6_listening_fd?(preserve=true){listen_fds;interface;_}port=tryLwt.return(false,Hashtbl.findlisten_fds(interface,port))withNot_found->letfd=Lwt_unix.(socketPF_INET6SOCK_DGRAM0)inLwt_unix.(setsockoptfdIPV6_ONLYtrue);Lwt_unix.bindfd(Lwt_unix.ADDR_INET(interface,port))>|=fun()->ifpreservethenHashtbl.addlisten_fds(interface,port)fd;(true,fd)typeerror=[`Sendto_failed]letpp_errorppf=function|`Sendto_failed->Fmt.pfppf"sendto failed to write any bytes"letclosefd=Lwt.catch(fun()->Lwt_unix.closefd)(function|Unix.Unix_error(Unix.EBADF,_,_)->Lwt.return_unit|e->Lwt.faile)letconnectid=lett=letlisten_fds=Hashtbl.create7inletinterface=matchidwith|None->Ipaddr_unix.V6.to_inet_addrIpaddr.V6.unspecified|Someip->Ipaddr_unix.V6.to_inet_addr(Ipaddr.V6.Prefix.addressip)in{interface;listen_fds;switched_off=fst(Lwt.wait())}inLwt.returntletdisconnectt=Hashtbl.fold(fun_fdr->r>>=fun()->closefd)t.listen_fdsLwt.return_unit>>=fun()->Lwt.cancelt.switched_off;Lwt.return_unitletinput_t~src:_~dst:__buf=Lwt.return_unitletwrite?src:_?src_port?ttl:_ttl~dst~dst_porttbuf=letopenLwt_unixinletrecwrite_to_fdfdbuf=Lwt.catch(fun()->Lwt_cstruct.sendtofdbuf[](ADDR_INET((Ipaddr_unix.V6.to_inet_addrdst),dst_port))>>=function|nwhenn=Cstruct.lengthbuf->Lwt.return(Ok())|0->Lwt.return(Error`Sendto_failed)|n->write_to_fdfd(Cstruct.subbufn(Cstruct.lengthbuf-n)))(* keep trying *)(fun_exn->Lwt.return(Error`Sendto_failed))inletport=matchsrc_portwithNone->0|Somex->xinget_udpv6_listening_fd~preserve:falsetport>>=fun(created,fd)->write_to_fdfdbuf>>=funr->(ifcreatedthenclosefdelseLwt.return_unit)>|=fun()->rletunlistent~port=tryletfd=Hashtbl.findt.listen_fds(t.interface,port)inHashtbl.removet.listen_fds(t.interface,port);Unix.close(Lwt_unix.unix_file_descrfd)with_->()letlistent~portcallback=ifport<0||port>65535thenraise(Invalid_argument(Printf.sprintf"invalid port number (%d)"port));unlistent~port;(* FIXME: we should not ignore the result *)Lwt.async(fun()->get_udpv6_listening_fdtport>>=fun(_,fd)->letbuf=Cstruct.create4096inletrecloop()=ifnot(Lwt.is_sleepingt.switched_off)thenraiseLwt.Canceled;Lwt.catch(fun()->Lwt_cstruct.recvfromfdbuf[]>>=fun(len,sa)->letbuf=Cstruct.subbuf0lenin(matchsawith|Lwt_unix.ADDR_INET(addr,src_port)->letsrc=Ipaddr_unix.V6.of_inet_addr_exnaddrinletdst=Ipaddr.V6.unspecifiedin(* TODO *)callback~src~dst~src_portbuf|_->Lwt.return_unit)>|=fun()->`Continue)(function|Unix.Unix_error(Unix.EBADF,_,_)->Log.warn(funm->m"error bad file descriptor in accept");Lwt.return`Stop|exn->Log.warn(funm->m"exception %s in recvfrom"(Printexc.to_stringexn));Lwt.return`Continue)>>=function|`Continue->loop()|`Stop->Lwt.return_unitinLwt.catchloopignore_canceled>>=fun()->closefd)