]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[tcpip] Allow binding to unspecified privileged ports (below 1024)
authorMichael Brown <mcb30@ipxe.org>
Tue, 6 Aug 2013 14:56:54 +0000 (15:56 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 6 Aug 2013 14:56:54 +0000 (15:56 +0100)
Originally-implemented-by: Marin Hannache <git@mareo.fr>
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/in.h
src/include/ipxe/tcpip.h
src/net/tcp.c
src/net/tcpip.c
src/net/udp.c

index 1208ae38a7841fcc3813521d86e7f9a0608bdbfc..eee9159fb6fc7e531faacc7a9c5af5ef746efa72 100644 (file)
@@ -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 );
index 34d7f88f4d62c566bec8d6b72070d9917e3fbc20..0cc688a92e047405d60eca8160cb232e74f0e831 100644 (file)
@@ -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
index b97107fc8c3bcaedbbaf74c1d42450318717d727..0e18c831dd6a79b04c47a2a4c4c41bc4506ed8b4 100644 (file)
@@ -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;
-
-       /* 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;
+static int tcp_port_available ( int 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 );
index 8e187f7ecc7603865ecadfb3abdb81ba81409ef7..721a4e4837304cf9ca520a1159e37d1f2c7050d8 100644 (file)
@@ -1,4 +1,5 @@
 #include <stdint.h>
+#include <stdlib.h>
 #include <string.h>
 #include <errno.h>
 #include <byteswap.h>
@@ -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;
+}
index bae5f4a739b978468d86b55ff0cc5553d070d8db..edc7488a36362c63e6bf0a49cc51fb2790cdfe54 100644 (file)
@@ -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;
-
-       /* 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;
-       }
+static int udp_port_available ( int port ) {
+       struct udp_connection *udp;
 
-       /* 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