implement addrinfo stuff
authorTero Marttila <terom@fixme.fi>
Sun, 16 Aug 2009 16:38:44 +0300
changeset 5 59bed837c265
parent 4 664a1dfe08ac
child 6 10bd48c9b6ce
implement addrinfo stuff
inc/py.pxd
libc.pxd
libc.pyx
py.pxd
py.pyx
sctp/constants.pyx
sctp/sock.pxd
sctp/sock.pyx
setup.py
sock/addr.pxd
sock/addr.pyx
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inc/py.pxd	Sun Aug 16 16:38:44 2009 +0300
@@ -0,0 +1,1 @@
+../py.pxd
\ No newline at end of file
--- a/libc.pxd	Sun Aug 16 04:15:07 2009 +0300
+++ b/libc.pxd	Sun Aug 16 16:38:44 2009 +0300
@@ -27,6 +27,7 @@
     
 cdef extern from "string.h" :
     void* memcpy (void *dest, void *src, size_t n)
+    void* memset (void *s, int c, size_t n)
 
     char* strerror (int errno)
 
@@ -112,6 +113,35 @@
     int c_inet_pton "inet_pton" (int af, char *src, void *dst)
 
 cdef extern from "netdb.h" :
+    ## getaddrinfo
+    struct addrinfo :
+        int         ai_flags
+        int         ai_family
+        int         ai_socktype
+        int         ai_protocol
+        int         ai_addrlen
+        sockaddr   *ai_addr
+        char       *ai_canonname
+        addrinfo   *ai_next
+    
+    enum :
+        AI_PASSIVE
+        AI_CANONNAME
+        AI_NUMERICHOST
+        AI_V4MAPPED
+        AI_ALL
+        AI_ADDRCONFIG
+        # AI_*IDN*
+        AI_NUMERICSERV
+
+    int c_getaddrinfo "getaddrinfo" (
+            char *node, char *service, 
+            addrinfo *hints, addrinfo **res
+    )
+
+    void c_freeaddrinfo "freeaddrinfo" (addrinfo *res)
+
+    ## getnameinfo
     int c_getnameinfo "getnameinfo" (
             sockaddr *sa, socklen_t salen, 
             char *host, size_t hostlen, 
--- a/libc.pyx	Sun Aug 16 04:15:07 2009 +0300
+++ b/libc.pyx	Sun Aug 16 16:38:44 2009 +0300
@@ -1,5 +1,7 @@
 from libc cimport *
 
+from py cimport raise_errno
+
 cdef object inet_ntop (int af, void *sockaddr) :
     """
         Wrapper around inet_ntop, returning a PyString
@@ -9,8 +11,7 @@
     cdef char buf[INET6_ADDRSTRLEN]
 
     if c_inet_ntop(af, sockaddr, buf, sizeof(buf)) == NULL :
-        # XXX: errno?
-        raise OSError(errno)
+        raise_errno()
     
     # autoconvert -> str
     return buf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/py.pxd	Sun Aug 16 16:38:44 2009 +0300
@@ -0,0 +1,12 @@
+cdef extern from "Python.h" :
+    struct PyObject :
+        pass
+
+    PyObject* PyErr_SetFromErrno (PyObject *type)
+    
+    ## built-in exceptions
+    PyObject* PyExc_OSError
+
+# raise OSError with errno
+cdef int raise_errno () except -1
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/py.pyx	Sun Aug 16 16:38:44 2009 +0300
@@ -0,0 +1,6 @@
+
+cdef int raise_errno () except -1 :
+    PyErr_SetFromErrno(PyExc_OSError)
+
+    return -1
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sctp/constants.pyx	Sun Aug 16 16:38:44 2009 +0300
@@ -0,0 +1,62 @@
+cimport sctp.sock as sctp
+
+SOL_SCTP = sctp.SOL_SCTP
+IPPROTO_SCTP = sctp.IPPROTO_SCTP
+
+SCTP_RTOINFO = sctp.SCTP_RTOINFO
+SCTP_ASSOCINFO = sctp.SCTP_ASSOCINFO
+SCTP_INITMSG = sctp.SCTP_INITMSG
+SCTP_NODELAY = sctp.SCTP_NODELAY
+SCTP_AUTOCLOSE = sctp.SCTP_AUTOCLOSE
+SCTP_SET_PEER_PRIMARY_ADDR = sctp.SCTP_SET_PEER_PRIMARY_ADDR
+SCTP_PRIMARY_ADDR = sctp.SCTP_PRIMARY_ADDR
+SCTP_ADAPTATION_LAYER = sctp.SCTP_ADAPTATION_LAYER
+SCTP_DISABLE_FRAGMENTS = sctp.SCTP_DISABLE_FRAGMENTS
+SCTP_PEER_ADDR_PARAMS = sctp.SCTP_PEER_ADDR_PARAMS
+SCTP_DEFAULT_SEND_PARAM = sctp.SCTP_DEFAULT_SEND_PARAM
+SCTP_EVENTS = sctp.SCTP_EVENTS
+SCTP_I_WANT_MAPPED_V4_ADDR = sctp.SCTP_I_WANT_MAPPED_V4_ADDR
+SCTP_MAXSEG = sctp.SCTP_MAXSEG
+SCTP_STATUS = sctp.SCTP_STATUS
+SCTP_GET_PEER_ADDR_INFO = sctp.SCTP_GET_PEER_ADDR_INFO
+SCTP_DELAYED_ACK_TIME = sctp.SCTP_DELAYED_ACK_TIME
+SCTP_CONTEXT = sctp.SCTP_CONTEXT
+SCTP_FRAGMENT_INTERLEAVE = sctp.SCTP_FRAGMENT_INTERLEAVE
+SCTP_PARTIAL_DELIVERY_POINT = sctp.SCTP_PARTIAL_DELIVERY_POINT
+SCTP_MAX_BURST = sctp.SCTP_MAX_BURST
+
+SCTP_ADDR_AVAILABLE = sctp.SCTP_ADDR_AVAILABLE
+SCTP_ADDR_UNREACHABLE = sctp.SCTP_ADDR_UNREACHABLE
+SCTP_ADDR_REMOVED = sctp.SCTP_ADDR_REMOVED
+SCTP_ADDR_ADDED = sctp.SCTP_ADDR_ADDED
+SCTP_ADDR_MADE_PRIM = sctp.SCTP_ADDR_MADE_PRIM
+SCTP_ADDR_CONFIRMED = sctp.SCTP_ADDR_CONFIRMED
+
+
+SCTP_DATA_UNSENT = sctp.SCTP_DATA_UNSENT
+SCTP_DATA_SENT = sctp.SCTP_DATA_SENT
+
+SCTP_PARTIAL_DELIVERY_ABORTED = sctp.SCTP_PARTIAL_DELIVERY_ABORTED
+
+# enum sctp_sn_type :
+SCTP_SN_TYPE_BASE = sctp.SCTP_SN_TYPE_BASE
+SCTP_ASSOC_CHANGE = sctp.SCTP_ASSOC_CHANGE
+SCTP_PEER_ADDR_CHANGE = sctp.SCTP_PEER_ADDR_CHANGE
+SCTP_SEND_FAILED = sctp.SCTP_SEND_FAILED
+SCTP_REMOTE_ERROR = sctp.SCTP_REMOTE_ERROR
+SCTP_SHUTDOWN_EVENT = sctp.SCTP_SHUTDOWN_EVENT
+SCTP_PARTIAL_DELIVERY_EVENT = sctp.SCTP_PARTIAL_DELIVERY_EVENT
+SCTP_ADAPTATION_INDICATION = sctp.SCTP_ADAPTATION_INDICATION
+
+# enum sctp_sn_error :
+SCTP_FAILED_THRESHOLD = sctp.SCTP_FAILED_THRESHOLD
+SCTP_RECEIVED_SACK = sctp.SCTP_RECEIVED_SACK
+SCTP_HEARTBEAT_SUCCESS = sctp.SCTP_HEARTBEAT_SUCCESS
+SCTP_RESPONSE_TO_USER_REQ = sctp.SCTP_RESPONSE_TO_USER_REQ
+SCTP_INTERNAL_ERROR = sctp.SCTP_INTERNAL_ERROR
+SCTP_SHUTDOWN_GUARD_EXPIRES = sctp.SCTP_SHUTDOWN_GUARD_EXPIRES
+SCTP_PEER_FAULTY = sctp.SCTP_PEER_FAULTY
+
+SCTP_BINDX_ADD_ADDR = sctp.SCTP_BINDX_ADD_ADDR
+SCTP_BINDX_REM_ADDR = sctp.SCTP_BINDX_REM_ADDR
+
--- a/sctp/sock.pxd	Sun Aug 16 04:15:07 2009 +0300
+++ b/sctp/sock.pxd	Sun Aug 16 16:38:44 2009 +0300
@@ -10,31 +10,33 @@
 
     ## constants
     # sockapi
-    int SOL_SCTP
-    int IPPROTO_SCTP
+    enum :
+        SOL_SCTP
+        IPPROTO_SCTP
 
     # sockopts
-    int SCTP_RTOINFO
-    int SCTP_ASSOCINFO
-    int SCTP_INITMSG
-    int SCTP_NODELAY
-    int SCTP_AUTOCLOSE
-    int SCTP_SET_PEER_PRIMARY_ADDR
-    int SCTP_PRIMARY_ADDR
-    int SCTP_ADAPTATION_LAYER
-    int SCTP_DISABLE_FRAGMENTS
-    int SCTP_PEER_ADDR_PARAMS
-    int SCTP_DEFAULT_SEND_PARAM
-    int SCTP_EVENTS
-    int SCTP_I_WANT_MAPPED_V4_ADDR
-    int SCTP_MAXSEG
-    int SCTP_STATUS
-    int SCTP_GET_PEER_ADDR_INFO
-    int SCTP_DELAYED_ACK_TIME
-    int SCTP_CONTEXT
-    int SCTP_FRAGMENT_INTERLEAVE
-    int SCTP_PARTIAL_DELIVERY_POINT
-    int SCTP_MAX_BURST
+    enum :
+        SCTP_RTOINFO
+        SCTP_ASSOCINFO
+        SCTP_INITMSG
+        SCTP_NODELAY
+        SCTP_AUTOCLOSE
+        SCTP_SET_PEER_PRIMARY_ADDR
+        SCTP_PRIMARY_ADDR
+        SCTP_ADAPTATION_LAYER
+        SCTP_DISABLE_FRAGMENTS
+        SCTP_PEER_ADDR_PARAMS
+        SCTP_DEFAULT_SEND_PARAM
+        SCTP_EVENTS
+        SCTP_I_WANT_MAPPED_V4_ADDR
+        SCTP_MAXSEG
+        SCTP_STATUS
+        SCTP_GET_PEER_ADDR_INFO
+        SCTP_DELAYED_ACK_TIME
+        SCTP_CONTEXT
+        SCTP_FRAGMENT_INTERLEAVE
+        SCTP_PARTIAL_DELIVERY_POINT
+        SCTP_MAX_BURST
 
     ## send/recv-msg cmsghdr's
     struct sctp_initmsg :
@@ -192,10 +194,13 @@
     
 
     ## sctp_bindx
-    int SCTP_BINDX_ADD_ADDR
-    int SCTP_BINDX_REM_ADDR
+    enum :
+        SCTP_BINDX_ADD_ADDR
+        SCTP_BINDX_REM_ADDR
 
     int c_sctp_bindx "sctp_bindx" (int sd, libc.sockaddr *addrs, int addrcnt, int flags)
-    int sctp_connectx (int sd, libc.sockaddr *addrs, int addrcnt)
+
+    # XXX: missing return-sctp_assoc_t-id argument!
+    int c_sctp_connectx "sctp_connectx" (int sd, libc.sockaddr *addrs, int addrcnt)
 
 
--- a/sctp/sock.pyx	Sun Aug 16 04:15:07 2009 +0300
+++ b/sctp/sock.pyx	Sun Aug 16 16:38:44 2009 +0300
@@ -9,47 +9,32 @@
 
 """
 
-from sctp cimport *
+from sctp.sock cimport *
+from py cimport raise_errno
 
 cimport libc
 cimport sock.addr
 
-cdef extern from "Python.h" :
-    struct PyObject :
-        pass
-
-    PyObject* PyErr_SetFromErrno (PyObject *type)
-
-    PyObject* PyExc_OSError
-
-cdef int raise_errno () except -1 :
-    PyErr_SetFromErrno(PyExc_OSError)
-
-    return -1
-
-def sctp_bindx (int sd, object addrs, int flags) :
+cdef size_t addrsoup_len (object addrs) except -1 :
     """
-        Bind the given SOCK_SEQPACKET to the given set of sock.addr.sockaddr's.
-
-            sd          the system socket FD
-            addresses   the list of qmsk.net.sock.addr.sockaddr's
-            flags       one of SCTP_BINDX_ADD/REM_ADDR
-        
+        Calculate the length of the addr_buf required to store the given addrsoup
     """
 
-    # ensure that addrs stays the same... ?
-    addrs = tuple(addrs)
-
-    cdef size_t addr_count = len(addrs)
+    cdef sock.addr.sockaddr addr
     cdef size_t addr_size = 0
-    cdef sock.addr.sockaddr addr
 
     # whoever decided that sctp_bindx takes an array of mixed sockaddr_in/sockaddr_in6's should be shot
     for addr in addrs :
         addr_size += addr._get_sockaddr_len()
     
-    # alloc buffer to hold all the sockaddr_*'s
-    cdef char *addr_buf = <char *> libc.alloca(addr_size)
+    return addr_size
+
+cdef addrsoup_store (object addrs, char *addr_buf) :
+    """
+        Store the sockaddr_*'s for the given addresses into the given buffer, which should be addrsoup_len() bytes long
+    """
+
+    cdef sock.addr.sockaddr addr
     cdef char *addr_ptr = addr_buf
 
     # fill it
@@ -66,7 +51,48 @@
         # move to next
         addr_ptr += sa_len
 
+def sctp_bindx (int sd, object addrs, int flags) :
+    """
+        Bind the given SOCK_SEQPACKET to the given set of sock.addr.sockaddr's.
+
+            sd          the system socket FD
+            addresses   the list of qmsk.net.sock.addr.sockaddr's
+            flags       one of SCTP_BINDX_ADD/REM_ADDR
+        
+    """
+
+    # ensure that addrs stays the same... ?
+    addrs = tuple(addrs)
+
+    # alloc buffer to hold all the sockaddr_*'s
+    cdef char *addr_buf = <char *> libc.alloca(addrsoup_len(addrs))
+
+    # store
+    addrsoup_store(addrs, addr_buf)
+
     # then call
-    if c_sctp_bindx(sd, <libc.sockaddr *> addr_buf, addr_count, flags) < 0 :
+    if c_sctp_bindx(sd, <libc.sockaddr *> addr_buf, len(addrs), flags) < 0 :
         raise_errno()
 
+def sctp_connectx (int sd, object addrs) :
+    """
+        Connect the given SOCK_SEQPACKET to the given set of remote sock.addr.sockaddr's.
+
+            sd          the system socket FD
+            addresses   the list of qmsk.net.sock.addr.sockaddr's
+        
+    """
+
+    # ensure that addrs stays the same... ?
+    addrs = tuple(addrs)
+
+    # alloc buffer to hold all the sockaddr_*'s
+    cdef char *addr_buf = <char *> libc.alloca(addrsoup_len(addrs))
+
+    # store
+    addrsoup_store(addrs, addr_buf)
+
+    # then call
+    if c_sctp_connectx(sd, <libc.sockaddr *> addr_buf, len(addrs)) < 0 :
+        raise_errno()
+
--- a/setup.py	Sun Aug 16 04:15:07 2009 +0300
+++ b/setup.py	Sun Aug 16 16:38:44 2009 +0300
@@ -9,8 +9,10 @@
     cmdclass = {'build_ext': build_ext},
     ext_modules = [
         cython_ext("libc", ["libc.pyx"]),
+        cython_ext("py", ["py.pyx"]),
         cython_ext("sock.addr", ["sock/addr.pyx"]),
         cython_ext("sctp.sock", ["sctp/sock.pyx"], libraries=['sctp']),
+        cython_ext("sctp.constants", ["sctp/constants.pyx"]),
     ]
 )
 
--- a/sock/addr.pxd	Sun Aug 16 04:15:07 2009 +0300
+++ b/sock/addr.pxd	Sun Aug 16 16:38:44 2009 +0300
@@ -4,6 +4,8 @@
     """
         A network-level socket address
 
+        XXX: rename to 'address'
+
         >>> sockaddr().family
         0
         >>> sockaddr().port
@@ -20,7 +22,7 @@
     # XXX: this should be a class constant! It's part of our type safety!
     cdef readonly libc.sa_family_t family
 
-    cdef void _init_family (self, libc.sa_family_t family=?)
+    cdef void _init_family (self, libc.sa_family_t family = ?)
 
     # get the sockaddr/socklen
     # each of these can be NULL to ignore it
@@ -28,4 +30,57 @@
 
     cdef libc.sockaddr* _get_sockaddr_ptr (self) except NULL
     cdef libc.socklen_t _get_sockaddr_len (self) except -1
+    
+    # set the sockaddr, socklen must match
+    cdef int _set_sockaddr (self, libc.sockaddr *sa, size_t sa_len) except -1
 
+# build a sockaddr from the given sockaddr struct, based on sa_family
+cdef sockaddr build_sockaddr (libc.sockaddr *sa, size_t sa_len)
+
+cdef class addrinfo :
+    """
+        A socket-level endpoint address, which contains the full socket parameters and an bind/connect address
+    """
+
+    cdef readonly int flags
+    cdef readonly int family, socktype, protocol
+    cdef readonly sockaddr addr
+    cdef readonly object canonname
+
+    cdef _init_addrinfo (self, libc.addrinfo *c_ai)
+
+# build and return a new addrinfo instance
+cdef addrinfo build_addrinfo (libc.addrinfo *c_ai)
+
+cdef class endpoint :
+    """
+        A network-level socket endpoint. This is the level that humans mostly work with, but the tricky bit is that
+        an endpoint can map to more than one sockaddr...
+
+        Hence, endpoints are stored as human-readable hostname/service strings, which are then translated to sockaddrs
+        using getaddrinfo.
+
+        >>> import socket
+        >>> e = endpoint('127.0.0.1', 80)
+        >>> str(e)
+        'hostname=127.0.0.1, service=80'
+        >>> res = e.getaddrinfo(socket.AF_UNSPEC, socket.SOCK_STREAM)
+        >>> len(res)
+        1
+        >>> str(res[0])
+        'family=2, socktype=1, protocol=6, addr=127.0.0.1:80, canonname=None'
+        >>> e = endpoint('2001::5', 80)
+        >>> str(e)
+        'hostname=2001::5, service=80'
+        >>> res = e.getaddrinfo(socket.AF_UNSPEC, socket.SOCK_STREAM)
+        >>> len(res)
+        1
+        >>> str(res[0])
+        'family=10, socktype=1, protocol=6, addr=[2001::5]:80, canonname=None'
+
+    """
+    
+    # our defining attributes, set via __init__
+    cdef object hostname, service
+
+    cpdef getaddrinfo (self, int family, int socktype, int protocol = ?, int flags = ?)
--- a/sock/addr.pyx	Sun Aug 16 04:15:07 2009 +0300
+++ b/sock/addr.pyx	Sun Aug 16 16:38:44 2009 +0300
@@ -10,6 +10,7 @@
     cdef void _init_family (self, libc.sa_family_t family=libc.AF_UNSPEC) :
         self.family = family
 
+    # XXX:use size_t
     cdef int _get_sockaddr (self, libc.sockaddr **sa_ptr, libc.socklen_t *sa_len) except -1 :
         """
             Get the sockaddr pointer and sockaddr length for this address
@@ -41,6 +42,13 @@
 
         return sa_len
 
+    cdef int _set_sockaddr (self, libc.sockaddr *sa, size_t sa_len) except -1 :
+        """
+            Set the sockaddr value for this address; sa_len must match!
+        """
+
+        raise NotImplementedError()
+
     def getnameinfo (self) :
         """
             Returns a (host, serv) tuple for this address à la getnameinfo
@@ -140,6 +148,11 @@
 
         return 0
 
+    cdef int _set_sockaddr (self, libc.sockaddr *sa, size_t sa_len) except -1 :
+        assert sa_len == sizeof(self.sockaddr)
+
+        libc.memcpy(&self.sockaddr, sa, sa_len)
+
     property port :
         """
             The integer port number
@@ -209,6 +222,11 @@
 
         return 0
 
+    cdef int _set_sockaddr (self, libc.sockaddr *sa, size_t sa_len) except -1 :
+        assert sa_len == sizeof(self.sockaddr)
+
+        libc.memcpy(&self.sockaddr, sa, sa_len)
+
     property port :
         """
             The integer port number
@@ -226,3 +244,110 @@
         # format
         return "[%s]:%s" % self.getnameinfo()
 
+# mapping of AF -> sockaddr, user-modifyable
+SOCKADDR_BY_FAMILY = {
+    libc.AF_INET:   sockaddr_in,
+    libc.AF_INET6:  sockaddr_in6,
+}
+
+# build a sockaddr from the given sockaddr struct, based on sa_family
+cdef sockaddr build_sockaddr (libc.sockaddr *sa, size_t sa_len) :
+    # lookup correct class to use
+    addr_type = SOCKADDR_BY_FAMILY[sa.sa_family]
+    
+    # construct with defaults
+    cdef sockaddr addr = addr_type()
+
+    # store
+    addr._set_sockaddr(sa, sa_len)
+
+    return addr
+
+cdef class addrinfo :
+    
+    cdef _init_addrinfo (self, libc.addrinfo *ai) :
+        #ai.flags = c_ai.ai_flags
+        self.family = ai.ai_family
+        self.socktype = ai.ai_socktype
+        self.protocol = ai.ai_protocol
+        self.addr = build_sockaddr(ai.ai_addr, ai.ai_addrlen)
+        self.canonname = ai.ai_canonname if ai.ai_canonname else None
+
+    def __str__ (self) :
+        return "family=%d, socktype=%d, protocol=%d, addr=%s, canonname=%s" % (self.family, self.socktype, self.protocol, self.addr, self.canonname)
+
+cdef addrinfo build_addrinfo (libc.addrinfo *c_ai) :
+    cdef addrinfo ai = addrinfo()
+    
+    ai._init_addrinfo(c_ai)
+
+    return ai
+
+cdef class endpoint :
+
+    def __init__ (self, hostname=None, service=None) :
+        """
+            Construct with the given hostname/service, either of which may be None.
+
+            A hostname of None implies all valid local addresses (with AI_PASSIVE), and a service of None implies an
+            ephemeral port.
+
+                hostname        - the literal address or DNS hostname or anything else that GAI supports
+                service         - the numeric port or service name
+        """
+
+        self.hostname = str(hostname)
+        self.service = str(service)
+
+    cpdef getaddrinfo (self, int family, int socktype, int protocol = 0, int flags = libc.AI_PASSIVE) :
+        """
+            Look up our hostname/service using the given socket parameters, and return a sequence of addrinfo objects.
+        """
+        
+        # XXX: Cython doesn't support proper compound value literals...
+        cdef libc.addrinfo hints
+        
+        libc.memset(&hints, 0, sizeof(hints))
+        hints.ai_flags          = flags
+        hints.ai_family         = family
+        hints.ai_socktype       = socktype
+        hints.ai_protocol       = protocol
+
+        cdef libc.addrinfo *res, *r
+        cdef int err
+        cdef object ret = []
+
+        cdef char *hostname = NULL
+        cdef char *service = NULL
+
+        if self.hostname is not None :
+            hostname = self.hostname
+        
+        if self.service is not None :
+            service = self.service
+
+        # operate!
+        err = libc.c_getaddrinfo(hostname, service, &hints, &res)
+
+        try :
+            if err :
+                # XXX: raise a GAIError
+                raise Exception(libc.gai_strerror(err))
+            
+            # gather results
+            r = res
+
+            while r :
+                ret.append(build_addrinfo(r))
+
+                r = r.ai_next
+            
+            # ok
+            return ret
+
+        finally :
+            libc.c_freeaddrinfo(res)
+
+    def __str__ (self) :
+        return "hostname=%s, service=%s" % (self.hostname, self.service)
+