# HG changeset patch # User Tero Marttila # Date 1250429924 -10800 # Node ID 59bed837c2650a106e95a30af8f5bdb6d122a755 # Parent 664a1dfe08acb2feeb73962c38a279361f22d74d implement addrinfo stuff diff -r 664a1dfe08ac -r 59bed837c265 inc/py.pxd --- /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 diff -r 664a1dfe08ac -r 59bed837c265 libc.pxd --- 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, diff -r 664a1dfe08ac -r 59bed837c265 libc.pyx --- 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 diff -r 664a1dfe08ac -r 59bed837c265 py.pxd --- /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 + diff -r 664a1dfe08ac -r 59bed837c265 py.pyx --- /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 + diff -r 664a1dfe08ac -r 59bed837c265 sctp/constants.pyx --- /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 + diff -r 664a1dfe08ac -r 59bed837c265 sctp/sock.pxd --- 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) diff -r 664a1dfe08ac -r 59bed837c265 sctp/sock.pyx --- 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 = 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 = libc.alloca(addrsoup_len(addrs)) + + # store + addrsoup_store(addrs, addr_buf) + # then call - if c_sctp_bindx(sd, addr_buf, addr_count, flags) < 0 : + if c_sctp_bindx(sd, 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 = libc.alloca(addrsoup_len(addrs)) + + # store + addrsoup_store(addrs, addr_buf) + + # then call + if c_sctp_connectx(sd, addr_buf, len(addrs)) < 0 : + raise_errno() + diff -r 664a1dfe08ac -r 59bed837c265 setup.py --- 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"]), ] ) diff -r 664a1dfe08ac -r 59bed837c265 sock/addr.pxd --- 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 = ?) diff -r 664a1dfe08ac -r 59bed837c265 sock/addr.pyx --- 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) +