diff --git a/src/include/ipxe/in.h b/src/include/ipxe/in.h index 1208ae38..eee9159f 100644 --- a/src/include/ipxe/in.h +++ b/src/include/ipxe/in.h @@ -59,19 +59,22 @@ struct sockaddr_in { * Always set to @c AF_INET for IPv4 addresses */ sa_family_t sin_family; + /** Flags (part of struct @c sockaddr_tcpip) */ + uint16_t sin_flags; /** TCP/IP port (part of struct @c sockaddr_tcpip) */ uint16_t sin_port; /** IPv4 address */ struct in_addr sin_addr; /** Padding * - * This ensures that a struct @c sockaddr_tcpip is large - * enough to hold a socket address for any TCP/IP address - * family. + * This ensures that a struct @c sockaddr_in is large enough + * to hold a socket address for any TCP/IP address family. */ - char pad[ sizeof ( struct sockaddr ) - sizeof ( sa_family_t ) - - sizeof ( uint16_t ) - - sizeof ( struct in_addr ) ]; + char pad[ sizeof ( struct sockaddr ) - + ( sizeof ( sa_family_t ) /* sin_family */ + + sizeof ( uint16_t ) /* sin_flags */ + + sizeof ( uint16_t ) /* sin_port */ + + sizeof ( struct in_addr ) /* sin_addr */ ) ]; } __attribute__ (( may_alias )); /** @@ -83,11 +86,26 @@ struct sockaddr_in6 { * Always set to @c AF_INET6 for IPv6 addresses */ sa_family_t sin6_family; + /** Flags (part of struct @c sockaddr_tcpip) */ + uint16_t sin6_flags; /** TCP/IP port (part of struct @c sockaddr_tcpip) */ uint16_t sin6_port; uint32_t sin6_flowinfo; /* Flow number */ struct in6_addr sin6_addr; /* 128-bit destination address */ uint32_t sin6_scope_id; /* Scope ID */ + /** Padding + * + * This ensures that a struct @c sockaddr_in6 is large + * enough to hold a socket address for any TCP/IP address + * family. + */ + char pad[ sizeof ( struct sockaddr ) - + ( sizeof ( sa_family_t ) /* sin6_family */ + + sizeof ( uint16_t ) /* sin6_flags */ + + sizeof ( uint16_t ) /* sin6_port */ + + sizeof ( uint32_t ) /* sin6_flowinfo */ + + sizeof ( struct in6_addr ) /* sin6_addr */ + + sizeof ( uint32_t ) /* sin6_scope_id */ ) ]; } __attribute__ (( may_alias )); extern int inet_aton ( const char *cp, struct in_addr *inp ); diff --git a/src/include/ipxe/tcpip.h b/src/include/ipxe/tcpip.h index 34d7f88f..0cc688a9 100644 --- a/src/include/ipxe/tcpip.h +++ b/src/include/ipxe/tcpip.h @@ -24,6 +24,16 @@ struct net_device; */ #define TCPIP_EMPTY_CSUM 0xffff +/** TCP/IP address flags */ +enum tcpip_st_flags { + /** Bind to a privileged port (less than 1024) + * + * This value is chosen as 1024 to optimise the calculations + * in tcpip_bind(). + */ + TCPIP_BIND_PRIVILEGED = 0x0400, +}; + /** * TCP/IP socket address * @@ -33,6 +43,8 @@ struct net_device; struct sockaddr_tcpip { /** Socket address family (part of struct @c sockaddr) */ sa_family_t st_family; + /** Flags */ + uint16_t st_flags; /** TCP/IP port */ uint16_t st_port; /** Padding @@ -42,7 +54,9 @@ struct sockaddr_tcpip { * family. */ char pad[ sizeof ( struct sockaddr ) - - ( sizeof ( sa_family_t ) + sizeof ( uint16_t ) ) ]; + ( sizeof ( sa_family_t ) /* st_family */ + + sizeof ( uint16_t ) /* st_flags */ + + sizeof ( uint16_t ) /* st_port */ ) ]; } __attribute__ (( may_alias )); /** @@ -125,6 +139,8 @@ extern int tcpip_tx ( struct io_buffer *iobuf, struct tcpip_protocol *tcpip, extern uint16_t generic_tcpip_continue_chksum ( uint16_t partial, const void *data, size_t len ); extern uint16_t tcpip_chksum ( const void *data, size_t len ); +extern int tcpip_bind ( struct sockaddr_tcpip *st_local, + int ( * available ) ( int port ) ); /* Use generic_tcpip_continue_chksum() if no architecture-specific * version is available diff --git a/src/net/tcp.c b/src/net/tcp.c index b97107fc..0e18c831 100644 --- a/src/net/tcp.c +++ b/src/net/tcp.c @@ -157,6 +157,7 @@ static LIST_HEAD ( tcp_conns ); static struct interface_descriptor tcp_xfer_desc; static void tcp_expired ( struct retry_timer *timer, int over ); static void tcp_wait_expired ( struct retry_timer *timer, int over ); +static struct tcp_connection * tcp_demux ( unsigned int local_port ); static int tcp_rx_ack ( struct tcp_connection *tcp, uint32_t ack, uint32_t win ); @@ -226,46 +227,14 @@ tcp_dump_flags ( struct tcp_connection *tcp, unsigned int flags ) { */ /** - * Bind TCP connection to local port + * Check if local TCP port is available * - * @v tcp TCP connection * @v port Local port number - * @ret rc Return status code - * - * If the port is 0, the connection is assigned an available port - * between 1024 and 65535. + * @ret port Local port number, or negative error */ -static int tcp_bind ( struct tcp_connection *tcp, unsigned int port ) { - struct tcp_connection *existing; - uint16_t try_port; - unsigned int i; +static int tcp_port_available ( int port ) { - /* If no port is specified, find an available port */ - if ( ! port ) { - try_port = random(); - for ( i = 0 ; i < 65536 ; i++ ) { - try_port++; - if ( try_port < 1024 ) - continue; - if ( tcp_bind ( tcp, try_port ) == 0 ) - return 0; - } - DBGC ( tcp, "TCP %p could not bind: no free ports\n", tcp ); - return -EADDRINUSE; - } - - /* Attempt bind to local port */ - list_for_each_entry ( existing, &tcp_conns, list ) { - if ( existing->local_port == port ) { - DBGC ( tcp, "TCP %p could not bind: port %d in use\n", - tcp, port ); - return -EADDRINUSE; - } - } - tcp->local_port = port; - - DBGC ( tcp, "TCP %p bound to port %d\n", tcp, port ); - return 0; + return ( tcp_demux ( port ) ? -EADDRINUSE : port ); } /** @@ -281,7 +250,7 @@ static int tcp_open ( struct interface *xfer, struct sockaddr *peer, struct sockaddr_tcpip *st_peer = ( struct sockaddr_tcpip * ) peer; struct sockaddr_tcpip *st_local = ( struct sockaddr_tcpip * ) local; struct tcp_connection *tcp; - unsigned int bind_port; + int port; int rc; /* Allocate and initialise structure */ @@ -303,9 +272,15 @@ static int tcp_open ( struct interface *xfer, struct sockaddr *peer, memcpy ( &tcp->peer, st_peer, sizeof ( tcp->peer ) ); /* Bind to local port */ - bind_port = ( st_local ? ntohs ( st_local->st_port ) : 0 ); - if ( ( rc = tcp_bind ( tcp, bind_port ) ) != 0 ) + port = tcpip_bind ( st_local, tcp_port_available ); + if ( port < 0 ) { + rc = port; + DBGC ( tcp, "TCP %p could not bind: %s\n", + tcp, strerror ( rc ) ); goto err; + } + tcp->local_port = port; + DBGC ( tcp, "TCP %p bound to port %d\n", tcp, tcp->local_port ); /* Start timer to initiate SYN */ start_timer_nodelay ( &tcp->timer ); diff --git a/src/net/tcpip.c b/src/net/tcpip.c index 8e187f7e..721a4e48 100644 --- a/src/net/tcpip.c +++ b/src/net/tcpip.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -133,3 +134,46 @@ uint16_t generic_tcpip_continue_chksum ( uint16_t partial, uint16_t tcpip_chksum ( const void *data, size_t len ) { return tcpip_continue_chksum ( TCPIP_EMPTY_CSUM, data, len ); } + +/** + * Bind to local TCP/IP port + * + * @v st_local Local TCP/IP socket address, or NULL + * @v available Function to check port availability + * @ret port Local port number, or negative error + */ +int tcpip_bind ( struct sockaddr_tcpip *st_local, + int ( * available ) ( int port ) ) { + uint16_t flags = 0; + uint16_t try_port = 0; + uint16_t min_port; + uint16_t max_port; + unsigned int offset; + unsigned int i; + + /* Extract parameters from local socket address */ + if ( st_local ) { + flags = st_local->st_flags; + try_port = ntohs ( st_local->st_port ); + } + + /* If an explicit port is specified, check its availability */ + if ( try_port ) + return available ( try_port ); + + /* Otherwise, find an available port in the range [1,1023] or + * [1025,65535] as appropriate. + */ + min_port = ( ( ( ! flags ) & TCPIP_BIND_PRIVILEGED ) + 1 ); + max_port = ( ( flags & TCPIP_BIND_PRIVILEGED ) - 1 ); + offset = random(); + for ( i = 0 ; i <= max_port ; i++ ) { + try_port = ( ( i + offset ) & max_port ); + if ( try_port < min_port ) + continue; + if ( available ( try_port ) < 0 ) + continue; + return try_port; + } + return -EADDRINUSE; +} diff --git a/src/net/udp.c b/src/net/udp.c index bae5f4a7..edc7488a 100644 --- a/src/net/udp.c +++ b/src/net/udp.c @@ -48,45 +48,19 @@ static struct interface_descriptor udp_xfer_desc; struct tcpip_protocol udp_protocol __tcpip_protocol; /** - * Bind UDP connection to local port + * Check if local UDP port is available * - * @v udp UDP connection - * @ret rc Return status code - * - * Opens the UDP connection and binds to the specified local port. If - * no local port is specified, the first available port will be used. + * @v port Local port number + * @ret port Local port number, or negative error */ -static int udp_bind ( struct udp_connection *udp ) { - struct udp_connection *existing; - static uint16_t try_port = 1023; +static int udp_port_available ( int port ) { + struct udp_connection *udp; - /* If no port specified, find the first available port */ - if ( ! udp->local.st_port ) { - while ( try_port ) { - try_port++; - if ( try_port < 1024 ) - continue; - udp->local.st_port = htons ( try_port ); - if ( udp_bind ( udp ) == 0 ) - return 0; - } - return -EADDRINUSE; - } - - /* Attempt bind to local port */ - list_for_each_entry ( existing, &udp_conns, list ) { - if ( existing->local.st_port == udp->local.st_port ) { - DBGC ( udp, "UDP %p could not bind: port %d in use\n", - udp, ntohs ( udp->local.st_port ) ); + list_for_each_entry ( udp, &udp_conns, list ) { + if ( udp->local.st_port == htons ( port ) ) return -EADDRINUSE; - } } - - /* Add to UDP connection list */ - DBGC ( udp, "UDP %p bound to port %d\n", - udp, ntohs ( udp->local.st_port ) ); - - return 0; + return port; } /** @@ -104,6 +78,7 @@ static int udp_open_common ( struct interface *xfer, struct sockaddr_tcpip *st_peer = ( struct sockaddr_tcpip * ) peer; struct sockaddr_tcpip *st_local = ( struct sockaddr_tcpip * ) local; struct udp_connection *udp; + int port; int rc; /* Allocate and initialise structure */ @@ -120,8 +95,16 @@ static int udp_open_common ( struct interface *xfer, /* Bind to local port */ if ( ! promisc ) { - if ( ( rc = udp_bind ( udp ) ) != 0 ) + port = tcpip_bind ( st_local, udp_port_available ); + if ( port < 0 ) { + rc = port; + DBGC ( udp, "UDP %p could not bind: %s\n", + udp, strerror ( rc ) ); goto err; + } + udp->local.st_port = htons ( port ); + DBGC ( udp, "UDP %p bound to port %d\n", + udp, ntohs ( udp->local.st_port ) ); } /* Attach parent interface, transfer reference to connection