#lang scribble/doc
@(require scribble/manual
		  scribble/basic
          ;; planet croacks with the natives
 		  ;;(for-label "main.ss" scheme scheme/port net/url net/dns
		  ))

@title{mzsocket}
@;@table-of-contents{}

@section{Overview}

mzsocket is a native extension library for PLT-scheme, 
that provides the BSD/POSIX sockets interface. 

It supports IPv4, IPv6, Unix domain, and raw sockets depending
on availability on the host platform.

@subsection{Installation}

To use the library  through PLaneT:
@schemeinput[
(require (planet vyzo/socket))
]

To locally install the library, extract the library archive to your 
collects directory and run
@commandline{setup-plt -l socket}

To use the local library:
@schemeinput[
(require socket)
]
To run basic tests on the library:
@schemeinput[(require (only socket/test run-tests))]
@schemeinput[(run-tests)]

@subsection{License}

(C) Copyright 2007,2008 Dimitris Vyzovitis <vyzo at media dot mit dot edu>

mzsocket is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

mzsocket is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License
along with mzsocket.  If not, see 
@link["http://www.gnu.org/licenses/"]{<http://www.gnu.org/licenses/>}.

@section{API}

@defmodule[socket]

The @schemeid[socket] API closely follows the native POSIX/BSD API. 
In general, all operations that might block come in two variants @schemeid[op] and @schemeid[op*]. 
The blocking variant @schemeid[op] blocks the current scheme thread until the 
system call completes.
The non-blocking variant @schemeid[op*] raises an exception with error code 
@scheme[EWOULDBLOCK] if the operation cannot be completed (see @secref{exn}).
Non-blocking operations can be synchronized in a matter similar to @tt{select}
using events and @scheme[sync] (see @secref{sync}) .

@subsection{Sockets}

@subsubsection{Primitives}
@defproc[(socket (family fixnum? PF_INET)
				 (type fixnum? SOCK_STREAM)
				 (proto fixnum? 0))
		 socket?]{
Socket constructor.

Creates a new socket, registered with the current custodian.
Sockets are implicitly closed when they become unreachable.
}

@defproc[(socket? (o any)) boolean?]{
Socket predicate.
}

@defproc[(stream-socket? (o any)) boolean?]{
True for @scheme[SOCK_STREAM] sockets.
}

@defproc[(socket-close (o socket?)) void?]{
Explicitly close a socket.
}

@defproc[(socket-closed? (o socket?)) boolean?]{
True if the socket @scheme[o] has been closed.
}


@subsubsection{Address Binding}

@defproc[(socket-bind (o socket?) (addr address)) void?]{
Explicitly bind a socket to a specific address.
}

@defproc[(socket-getsockname (o socket?)) address]{
The local address of a bound socket.
}
@defproc[(socket-getpeername (o socket?)) address]{
The remote address of a connected socket.
}

@subsubsection{Connections}

@defproc[(socket-listen (o socket?) (backlog fixnum?)) void?]{
Listens for incoming connections in a stream socket.
}

@deftogether[(
@defproc[(socket-accept (o socket?)) (values socket? address)]
@defproc[(socket-accept* (o socket?)) (values socket? address)]
)]{
Accept an incoming connection in a stream socket.
}

@deftogether[(
@defproc[(socket-connect (o socket?) (peer address)) void?]
@defproc[(socket-connect* (o socket?) (peer address)) boolean?]
)]{
Connect a socket to a remote endpoint.

The non-blocking variant @schemeid[socket-connect*]
returns @scheme[#t] if the connection can be immediately completed; otherwise, 
an asynchronous connection is initiated and @scheme[#f] is returned. 
}

@defproc[(socket-shutdown (o socket?) (how fixnum?)) void?]{
Directional socket shutdown. 
@scheme[how] must be one of @scheme[SHUT_RD], @scheme[SHUT_WR], @scheme[SHUT_RDWR].
}


@subsubsection{Binary I/O}

@deftogether[(
@defproc[(socket-send (o socket?) (x bytes?) 
					  (b fixnum? 0) 
					  (e fixnum? (bytes-length x)) 
					  (flags fixnum? 0))
		  fixnum?]
@defproc[(socket-send* (o socket?) (x bytes?) 
					   (b fixnum? 0) 
					   (e fixnum? (bytes-length x)) 
					   (flags fixnum? 0))
		  fixnum?]
@defproc[(socket-sendto (o socket?) (peer address) (x bytes?) 
						(b fixnum? 0) 
					  	(e fixnum? (bytes-length x)) 
					  	(flags fixnum? 0))
		  fixnum?]
@defproc[(socket-sendto* (o socket?) (peer address) (x bytes?) 
					   	 (b fixnum? 0) 
					   	 (e fixnum? (bytes-length x)) 
					   	 (flags fixnum? 0))
		  fixnum?]
)]{
Send the bytes contained in @scheme[x][@scheme[b] @scheme[e]).
Return the number of bytes written.
}

@deftogether[(
@defproc[(socket-recv (o socket?) (x bytes?) 
					  (b fixnum? 0) 
					  (e fixnum? (bytes-length x)) 
					  (flags fixnum? 0))
		  fixnum?]
@defproc[(socket-recv* (o socket?) (x bytes?) 
					   (b fixnum? 0) 
					   (e fixnum? (bytes-length x)) 
					   (flags fixnum? 0))
		  fixnum?]
@defproc[(socket-recvfrom (o socket?) (x bytes?) 
						  (b fixnum? 0) 
					  	  (e fixnum? (bytes-length x)) 
					  	  (flags fixnum? 0))
		  (values fixnum? address)]
@defproc[(socket-recvfrom* (o socket?) (x bytes?) 
					   	   (b fixnum? 0) 
					   	   (e fixnum? (bytes-length x)) 
					   	   (flags fixnum? 0))
		  (values fixnum? address)]
)]{
Receive data into the buffer delimited by @scheme[x][@scheme[b] @scheme[e]).
Return the number of bytes read and, in the case of 
@schemeid[socket-recvfrom]/@schemeid[socket-recvfrom*]  the peer address 
as a second value.
For stream sockets, a value of @scheme[0] indicates a peer shutdown.
}

@deftogether[(
@defproc[(socket-send-all (o socket?) (x bytes?) 
						  (b fixnum? 0) (e fixnum? (bytes-length x)))
		  fixnum?]
@defproc[(socket-recv-all (o socket?) (x bytes?) 
						  (b fixnum? 0) (e fixnum? (bytes-length x)))
		  fixnum?]
)]{
Send (receive) all the data in the buffer delimited by 
@scheme[x][@scheme[b] @scheme[e]), blocking if necessary. 
Return the number of bytes sent (received), which may be less than 
@scheme[(- e b)] if an error occurs or the peer has shutdown the socket.
}

@deftogether[(
@defproc[(socket-send/port (o socket?) (inp input-port?) 
						   (bufsz fixnum? 4096))
		  fixnum?]
@defproc[(socket-recv/port (o socket?) (outp output-port?) 
						   (bufsz fixnum? 4096))
		  fixnum?]
)]{
Copy the contents of a port to a socket (and vice versa) with a buffer 
size @scheme[bufsz]. Return the (total) number of bytes copied.
}

@subsubsection{Port I/O}

@deftogether[(
@defproc[(socket-input-port (o socket?)) input-port?]
@defproc[(socket-output-port (o socket?)) output-port?]
)]{
Create input and output ports tied to a stream socket.
}

@defproc[(open-socket-stream (peer address)) 
		 (values input-port? output-port?)]{
Opens a client stream connected to @schemeid[peer].
Returns input and output ports for the stream.
}

@subsubsection{Control I/O}

@margin-note{Supported only in UNIX-like systems}

@deftogether[(
@defproc[(socket-sendmsg (o socket?) 
						 (name (or bytes? #f)) 
						 (data (or bytes? #f)) 
						 (control (or bytes? #f))
						 (flags fixnum? 0))
		 fixnum?]
@defproc[(socket-sendmsg* (o socket?) 
						  (name (or bytes? #f)) 
						  (data (or bytes? #f)) 
						  (control (or bytes? #f))
				 		  (flags fixnum? 0))
		 fixnum?]
)]{
Send a raw message with control parameters.
Return the number of bytes written.
}

@deftogether[(
@defproc[(socket-recvmsg (o socket?) 
						 (name (or bytes? #f)) 
						 (data (or bytes? #f)) 
						 (control (or bytes? #f))
						 (flags fixnum? 0))
		 fixnum?]
@defproc[(socket-recvmsg* (o socket?) 
						  (name (or bytes? #f)) 
						  (data (or bytes? #f)) 
						  (control (or bytes? #f))
				 		  (flags fixnum? 0))
		 (values fixnum? (or fixnum? #f) (or fixnum? #f) fixnum?)]
)]{
Receive a raw message with control parameters.
Return 4 values: 
@itemize{
@item{The length of the payload, placed in the @scheme[data] buffer}
@item{The length of the peer name, placed in the @scheme[name] buffer 
		  when applicable}
@item{The length of the control data, placed in the @scheme[control] buffer
		  when applicable}
@item{The received message flags}
}
}

@subsubsection{Sockopts}

@deftogether[(
@defproc[(socket-getsockopt (o socket?)
							(level fixnum?) (option fixnum?))
		 any]
@defproc[(socket-setsockopt (o socket?) 
							(level fixnum?) (option fixnum?)
				   			(value any))
		  void?]
)]{
Performs getsockopt/setsockopt, translating between raw option values and
scheme objects.
See the file @tt{sockopt.in} in the source distribution for the sockopts
supported with value translation.
}

@deftogether[(
@defproc[(socket-getsockopt-raw (o socket?)
								(level fixnum?) (option fixnum?)
								(value bytes?))
		 fixnum?]
@defproc[(socket-setsockopt-raw (o socket?) 
								(level fixnum?) (option fixnum?)
				   				(value bytes?))
		  void?]
)]{
Raw getsockopt/setsockopt.
}

@subsubsection[#:tag "sync"]{Synchronization}

@defproc[(socket-recv-evt (o socket?)) evt?]{
Create an event that is ready when data can be immediately received from the
socket.
The result of the synchronization is the event itself.
}

@defproc[(socket-send-evt (o socket?)) evt?]{
Create an event that is ready when data can be immediately sent through the
socket.
The result of the synchronization is the event itself.
}

@defproc[(socket-connect-evt (o socket?)) evt?]{
Create an event that is ready when an asynchronous connection initiated with
@scheme[socket-connect*] completes.
If the connection completes without error the result of the synchronization
is the socket itself; if the connection fails the result is an exception.
}

@defproc[(socket-accept-evt (o socket?)) evt?]{
Creates an event that is ready when an connection can be accepted.
The result of the synchronization is a new client socket if a connection is
accepted successfully or an exception if the system fails to accept the
new connection.
}

@subsection{Addresses}

Internet addresses (IPv4 and IPv6) are encapsulated in instances of a native 
@schemeid[inet-address] type, while UNIX domain addresses are 
represented as a scheme @schemeid[path].

@defproc*[(((inet-address (family fixnum?) (host bytes?) (port fixnum?))
		    inet-address?)
		  ((inet-address (family fixnum?) (host bytes?) (port fixnum?)
						(flowinfo fixnum?) (scopeid fixnum?))
		   inet-address?))]{
Raw @schemeid[inet-address] constructor. 

@scheme[family] must be @scheme[AF_INET] or @scheme[AF_INET6].
@scheme[host] must be a parseable by @tt{inet_pton}.
@scheme[flowinfo] and @scheme[scopeid] are only meaningful for IPv6 
addresses.
}

@deftogether[(
@defproc[(inet4-address (host (or bytes? string?)) (port fixnum?))
		 inet-address?]
@defproc[(inet6-address (host (or bytes? string?)) (port fixnum?) ...)
		 inet-address?]
)]{
Helper constructors for IPv4 and IPv6 addresses.
}

@deftogether[(
@defproc[(inet-address? (o any)) boolean?]
@defproc[(inet4-address? (o any)) boolean?]
@defproc[(inet6-address? (o any)) boolean?]
)]{
@schemeid[inet-address] predicates
}

@deftogether[(
@defproc[(inet-address-family (o inet-address?)) fixnum?]
@defproc[(inet-address-host (o inet-address?)) bytes?]
@defproc[(inet-address-port (o inet-address?)) fixnum?]
)]{
@schemeid[inet-address] field extractors.
}

@defproc[(inet-address=? (o inet-address?) ...+) boolean?]{
@schemeid[inet-address] equality test
}

@deftogether[(
@defproc[(inet-address->vector (o inet-address?)) vector?]
@defproc[(vector->inet-address (o vector?)) inet-address]
)]{
Convert an @schemeid[inet-address] to/from a vector of the form
@scheme[#(AF_INET addr port)] or 
@scheme[#(AF_INET6 addr port flowinfo scopeid)], suitable for marshalling.
}

@deftogether[(
@defproc[(pack-address (o address)) bytes?]
@defproc[(unpack-address (o bytes?)) address]
)]{
Raw address packing and unpacking. The binary representation is a direct
copy of the underlying @tt{sockaddr} struct, and thus platform-dependent.
It is mainly useful for control I/O and sockopts.
}

@subsection[#:tag "exn"]{Exceptions}

@defstruct[exn:fail:socket ((errno fixnum?))]{
Subtype of of @scheme[exn:fail:network].
Instances are raised by the mzsocket API on syscall errors.
@scheme[errno] contains the value of the system @tt{errno} related to the
error.
}

@defproc[(exn:fail:socket:blocking? (o any)) boolean?]{
True for instances of @schemeid[exn:fail:socket] with error code 
@scheme[EWOULDBLOCK].
}

@subsection{Constants}

@subsubsection{Features}

@defthing[SOCKET_HAVE_RAW boolean?]{
True if raw sockets are available.
}

@defthing[SOCKET_HAVE_IPV6 boolean?]{
True if IPv6 is available.
}

@defthing[SOCKET_HAVE_UNIX boolean?]{
True if UNIX domain sockets are available.
}

@subsubsection{Fixed Addresses}

@deftogether[(
@defthing[INADDR_ANY bytes?]
@defthing[INADDR_LOOPBACK bytes?]
@defthing[INADDR_BROADCAST bytes?]
@defthing[IN6ADDR_ANY bytes?]
@defthing[IN6ADDR_LOOPBACK bytes?]
)]{
Fixed host addresses, provided as immutable byte strings.
}

@subsubsection{Standard Constants}

@deftogether[(
@defthing[SHUT_RD fixnum?]
@defthing[SHUT_WR fixnum?]
@defthing[SHUT_RDWR fixnum?]
@defthing[AF_UNSPEC fixnum?]
@defthing[PF_UNSPEC fixnum?]
@defthing[AF_INET fixnum?]
@defthing[PF_INET fixnum?]
@defthing[AF_INET6 fixnum?]
@defthing[PF_INET6 fixnum?]
@defthing[AF_UNIX fixnum?]
@defthing[PF_UNIX fixnum?]
@defthing[IPPROTO_IP fixnum?]
@defthing[IPPROTO_IPV6 fixnum?]
@defthing[IPPROTO_TCP fixnum?]
@defthing[IPPROTO_UDP fixnum?]
@defthing[IPPROTO_ICMP fixnum?]
@defthing[IPPROTO_RAW fixnum?]
@defthing[SOCK_STREAM fixnum?]
@defthing[SOCK_DGRAM fixnum?]
@defthing[SOCK_RAW fixnum?]
@defthing[SOL_SOCKET fixnum?]
@defthing[SO_ACCEPTCONN fixnum?]
@defthing[SO_BROADCAST fixnum?]
@defthing[SO_DEBUG fixnum?]
@defthing[SO_DONTROUTE fixnum?]
@defthing[SO_ERROR fixnum?]
@defthing[SO_KEEPALIVE fixnum?]
@defthing[SO_OOBLINE fixnum?]
@defthing[SO_PASSCRED fixnum?]
@defthing[SO_RCVBUF fixnum?]
@defthing[SO_SNDBUF fixnum?]
@defthing[SO_RCVLOWAT fixnum?]
@defthing[SO_SNDLOWAT fixnum?]
@defthing[SO_RCVTIMEO fixnum?]
@defthing[SO_SNDTIMEO fixnum?]
@defthing[SO_REUSEADDR fixnum?]
@defthing[SO_REUSEPORT fixnum?]
@defthing[SO_TYPE fixnum?]
@defthing[SO_TIMESTAMP fixnum?]
@defthing[SO_USELOOPBACK fixnum?]
@defthing[IP_ADD_MEMBERSHIP fixnum?]
@defthing[IP_DROP_MEMBERSHIP fixnum?]
@defthing[IP_HDRINCL fixnum?]
@defthing[IP_MTU fixnum?]
@defthing[IP_MTU_DISCOVER fixnum?]
@defthing[IP_MULTICAST_IF fixnum?]
@defthing[IP_MULTICAST_LOOP fixnum?]
@defthing[IP_MULTICAST_TTL fixnum?]
@defthing[IP_OPTIONS fixnum?]
@defthing[IP_PKTINFO fixnum?]
@defthing[IP_RECVDSTADDR fixnum?]
@defthing[IP_RECVIF fixnum?]
@defthing[IP_RECVTOS fixnum?]
@defthing[IP_RECVTTL fixnum?]
@defthing[IP_TOS fixnum?]
@defthing[IP_TTL fixnum?]
@defthing[IPV6_ADD_MEMBERSHIP fixnum?]
@defthing[IPV6_DROP_MEMBERSHIP fixnum?]
@defthing[IPV6_MULTICAST_HOPS fixnum?]
@defthing[IPV6_MULTICAST_IF fixnum?]
@defthing[IPV6_MULTICAST_LOOP fixnum?]
@defthing[IPV6_MTU fixnum?]
@defthing[IPV6_MTU_DISCOVER fixnum?]
@defthing[IPV6_CHECKSUM fixnum?]
@defthing[IPV6_PKTINFO fixnum?]
@defthing[IPV6_RECVERR fixnum?]
@defthing[IPV6_UNICAST_HOPS fixnum?]
@defthing[IPV6_RTHDR fixnum?]
@defthing[IPV6_AUTHHDR fixnum?]
@defthing[IPV6_DSTOPTS fixnum?]
@defthing[IPV6_HOPOPTS fixnum?]
@defthing[IPV6_FLOWINFO fixnum?]
@defthing[IPV6_HOPLIMIT fixnum?]
@defthing[TCP_KEEPALIVE fixnum?]
@defthing[TCP_KEEPIDLE fixnum?]
@defthing[TCP_KEEPINTVL fixnum?]
@defthing[TCP_MAXRT fixnum?]
@defthing[TCP_MAXSEG fixnum?]
@defthing[TCP_NODELAY fixnum?]
@defthing[TCP_SYNCNT fixnum?]
@defthing[IP_PMTUDISC_WANT fixnum?]
@defthing[IP_PMTUDISC_DONT fixnum?]
@defthing[IP_PMTUDISC_DO fixnum?]
@defthing[IPV6_PKTOPTIONS fixnum?]
@defthing[IPTOS_LOWDELAY fixnum?]
@defthing[IPTOS_THROUGHPUT fixnum?]
@defthing[IPTOS_RELIABILITY fixnum?]
@defthing[IPTOS_MINCOST fixnum?]
@defthing[MSG_DONTROUTE fixnum?]
@defthing[MSG_DONTWAIT fixnum?]
@defthing[MSG_MORE fixnum?]
@defthing[MSG_OOB fixnum?]
)]{
Standard system constants, depending on platform 
availability.
}

@subsubsection{Error Codes}

@deftogether[(
@defthing[EACCES fixnum?]
@defthing[EAFNOSUPPORT fixnum?]
@defthing[EPFNOSUPPORT fixnum?]
@defthing[EPROTONOSUPPORT fixnum?]
@defthing[ENOBUFS fixnum?]
@defthing[EFAULT fixnum?]
@defthing[EADDRINUSE fixnum?]
@defthing[EINVAL fixnum?]
@defthing[ENOTSOCK fixnum?]
@defthing[EOPNOTSUPP fixnum?]
@defthing[EWOULDBLOCK fixnum?]
@defthing[ECONNABORTED fixnum?]
@defthing[EMFILE fixnum?]
@defthing[ETIMEDOUT fixnum?]
@defthing[ESOCKTNOSUPPORT fixnum?]
@defthing[EALREADY fixnum?]
@defthing[ECONNREFUSED fixnum?]
@defthing[EISCONN fixnum?]
@defthing[ENETUNREACH fixnum?]
@defthing[ECONNRESET fixnum?]
@defthing[EDESTADDRREQ fixnum?]
@defthing[EMSGSIZE fixnum?]
@defthing[ENOTCONN fixnum?]
@defthing[EPROTOTYPE fixnum?]
@defthing[ENOPROTOOPT fixnum?]
@defthing[EINPROGRESS fixnum?]
@defthing[EADDRNOTAVAIL fixnum?]
@defthing[ENETDOWN fixnum?]
@defthing[ENETRESET fixnum?]
@defthing[ESHUTDOWN fixnum?]
@defthing[EHOSTDOWN fixnum?]
@defthing[EHOSTUNREACH fixnum?]
@defthing[ENOMEM fixnum?]
@defthing[ENFILE fixnum?]
@defthing[EBADF fixnum?]
@defthing[EPIPE fixnum?]
)]{
Standard error codes
}

@section{Examples}

@subsection{A URL fetcher}

A function that fetches a url and prints the result to the current output port:

@schemeblock[
(require net/url net/dns)
(define (get-url what)
  (let* ((url (string->url what))
         (host (dns-get-address (dns-find-nameserver) (url-host url)))
         (port (or (url-port url) 80))
         (sock (socket)))
    (socket-connect sock (inet4-address host port))
    (socket-send-all sock (url->request url))
    (socket-shutdown sock SHUT_WR)
    (socket-recv/port sock (current-output-port))
    (socket-close sock)))
]

To fetch googgle.com try:
@schemeinput[(get-url "http://www.google.com")]

The function uses standard library modules from the net collection and
a trivial HTTP/1.0 request constructor:
@schemeblock[
(define (url->request url)
  (string->bytes/utf-8 (format "GET ~a HTTP/1.0\r\n\r\n" (url->string url))))
]

The same function implemented using socket ports:
@schemeblock[
(require scheme/port) (code:comment #, @t{for copy-port})
(define (get-url/stream what)
  (let* ((url (string->url what))
         (host (dns-get-address (dns-find-nameserver) (url-host url)))
         (port (or (url-port url) 80)))
    (let-values (((inp outp) (open-socket-stream (inet4-address host port))))
      (write-bytes (url->request url) outp)
      (close-output-port outp)
      (copy-port inp (current-output-port)))))
]

@subsection{An echo server}

A simple multi-threaded echo server implementation for stream sockets:
@schemeblock[
(define (echo-server domain addr)
  (let ((sock (socket domain SOCK_STREAM)))
    (socket-setsockopt sock SOL_SOCKET SO_REUSEADDR #t)
    (socket-bind sock addr)
    (socket-listen sock 5)
    (let lp ()
      (let-values (((clisock cliaddr) (socket-accept sock)))
        (thread (lambda () (echo clisock cliaddr)))
        (lp)))))
]

Client connections are proceesed by the function @schemeidfont{echo}:
@schemeblock[
(define (echo sock addr)
  (let ((buf (make-bytes 4096)))
    (let lp ()
      (let ((ilen (socket-recv sock buf)))
        (unless (= ilen 0)
          (socket-send-all sock buf 0 ilen)
          (lp)))))
  (socket-close sock))
]

To run the server on port 5000:
@schemeinput[
(echo-server PF_INET (inet4-address INADDR_ANY 5000))
]

If you want to limit connections to localhost only, 
then bind to @schemeidfont{INADDR_LOOPBACK}:
@schemeinput[
(echo-server PF_INET (inet4-address INADDR_LOOPBACK 5000))
]

You can now interact with the server via telnet:
@commandline{telnet localhost 5000}

Or you can simply connect a port-pair to it from an mzscheme shell:
@schemeinput[
(let-values (((in out) (open-socket-stream (inet4-address #"127.0.0.1" 5000))))
  (write '(hello world) out)
  (read in))
]
@schemeresult[=> (hello world)]


The server can run on a unix domain socket as well:
@schemeinput[
(echo-server PF_UNIX (string->path "/tmp/echosrv"))
]

@subsection{UDP echos}

A UDP echo server:

@schemeblock[
(define (udp-echo-server port)
  (let ((sock (socket PF_INET SOCK_DGRAM))
        (buf (make-bytes 1500)))
    (socket-setsockopt sock SOL_SOCKET SO_REUSEADDR #t)
    (code:comment #, @t{receive broadcasts too})
    (socket-setsockopt sock SOL_SOCKET SO_BROADCAST #t)
    (socket-bind sock (inet4-address INADDR_ANY port))
    (let lp ()
      (let-values (((ilen peer) (socket-recvfrom sock buf)))
        (socket-sendto sock peer buf 0 ilen)
        (lp)))))
]

A function that sends a message to a udp-echo-server and waits for the
reply with a timeout:
@schemeblock[
(define (udp-echo-sendto dest timeout msg)
  (let* ((sock (socket PF_INET SOCK_DGRAM))
         (buf (make-bytes (bytes-length msg))))
    (socket-sendto sock dest msg)
    (sync/timeout timeout
      (handle-evt (socket-recv-evt sock)
        (lambda (x)
          (let-values (((ilen peer) (socket-recvfrom sock buf)))
            (values peer buf)))))))
]

A function that uses broadcast to find a udp echo server in the LAN:
@schemeblock[
(define (udp-echo-find port timeout)
  (let* ((sock (socket PF_INET SOCK_DGRAM))
         (buf (make-bytes 8)))
    (socket-setsockopt sock SOL_SOCKET SO_BROADCAST #t)
    (socket-sendto sock (inet4-address INADDR_BROADCAST port) #"hello")
    (sync/timeout timeout
      (handle-evt (socket-recv-evt sock)
        (lambda (x)
          (let-values (((ilen peer) (socket-recvfrom sock buf))) peer))))))
]
