diff --git a/src/liburing/socket.pxd b/src/liburing/socket.pxd index fb8eda3..d73ea46 100644 --- a/src/liburing/socket.pxd +++ b/src/liburing/socket.pxd @@ -5,6 +5,7 @@ from .queue cimport io_uring_sqe cdef class sockaddr: cdef: + bint free void* ptr socklen_t sizeof readonly sa_family_t family diff --git a/src/liburing/socket.pyx b/src/liburing/socket.pyx index d5df8d8..36c7d28 100644 --- a/src/liburing/socket.pyx +++ b/src/liburing/socket.pyx @@ -10,6 +10,11 @@ cdef class sockaddr: >>> addr = sockaddr(AF_INET, b'0.0.0.0', 12345) >>> addr = sockaddr(AF_INET6, b'::1', 12345) >>> bind(sockfd, addr) + + Note + - `sockaddr()` is low level setup, letting you serve/connect directly using path/ip. + If you need higher level features you can use `getaddrinfo()` this lets you connect + using domain names, ... ''' def __cinit__(self, sa_family_t family=0, char* addr=b'', in_port_t port=0, uint32_t scope_id=0): @@ -24,6 +29,7 @@ cdef class sockaddr: self.ptr = sockaddr_un(addr) if self.ptr is NULL: memory_error(self) + self.free = True self.sizeof = sizeof(__sockaddr_un) self.family = family @@ -33,6 +39,7 @@ cdef class sockaddr: self.ptr = sockaddr_in(addr, port) if self.ptr is NULL: raise ValueError('`sockaddr(AF_INET)` - `addr` did not receive IPv4 address!') + self.free = True self.sizeof = sizeof(__sockaddr_in) self.family = family @@ -42,21 +49,22 @@ cdef class sockaddr: self.ptr = sockaddr_in6(addr, port, scope_id) if self.ptr is NULL: raise ValueError('`sockaddr(AF_INET6)` - `addr` did not receive IPv6 address!') + self.free = True self.sizeof = sizeof(__sockaddr_in6) self.family = family - elif not family: # incoming - raise NotImplementedError - else: # error + elif family: # error raise NotImplementedError def __dealloc__(self): - if self.ptr is not NULL: + if self.free and self.ptr is not NULL: PyMem_RawFree(self.ptr) self.ptr = NULL + self.free = False - def __repr__(self): - return f'{self.__class__.__name__}({self._test})' + # TODO: return (host, port) + # def __repr__(self): + # return f'{self.__class__.__name__}({self._test})' @property def _test(self)-> dict: @@ -504,7 +512,7 @@ cpdef enum io_uring_socket_op: SOCKET_URING_OP_GETSOCKOPT = __SOCKET_URING_OP_GETSOCKOPT SOCKET_URING_OP_SETSOCKOPT = __SOCKET_URING_OP_SETSOCKOPT -# defines +# setsockopt & getsockopt start >>> SOL_SOCKET = __SOL_SOCKET SO_DEBUG = __SO_DEBUG SO_REUSEADDR = __SO_REUSEADDR @@ -550,3 +558,34 @@ SO_TIMESTAMPNS = __SO_TIMESTAMPNS SO_TIMESTAMPING = __SO_TIMESTAMPING SO_RCVTIMEO = __SO_RCVTIMEO SO_SNDTIMEO = __SO_SNDTIMEO +# setsockopt & getsockopt end <<< + +cpdef enum SocketProto: + IPPROTO_IP = __IPPROTO_IP + IPPROTO_ICMP = __IPPROTO_ICMP + IPPROTO_IGMP = __IPPROTO_IGMP + IPPROTO_IPIP = __IPPROTO_IPIP + IPPROTO_TCP = __IPPROTO_TCP + IPPROTO_EGP = __IPPROTO_EGP + IPPROTO_PUP = __IPPROTO_PUP + IPPROTO_UDP = __IPPROTO_UDP + IPPROTO_IDP = __IPPROTO_IDP + IPPROTO_TP = __IPPROTO_TP + IPPROTO_DCCP = __IPPROTO_DCCP + IPPROTO_IPV6 = __IPPROTO_IPV6 + IPPROTO_RSVP = __IPPROTO_RSVP + IPPROTO_GRE = __IPPROTO_GRE + IPPROTO_ESP = __IPPROTO_ESP + IPPROTO_AH = __IPPROTO_AH + IPPROTO_MTP = __IPPROTO_MTP + IPPROTO_BEETPH = __IPPROTO_BEETPH + IPPROTO_ENCAP = __IPPROTO_ENCAP + IPPROTO_PIM = __IPPROTO_PIM + IPPROTO_COMP = __IPPROTO_COMP + IPPROTO_L2TP = __IPPROTO_L2TP + IPPROTO_SCTP = __IPPROTO_SCTP + IPPROTO_UDPLITE = __IPPROTO_UDPLITE + IPPROTO_MPLS = __IPPROTO_MPLS + IPPROTO_ETHERNET = __IPPROTO_ETHERNET + IPPROTO_RAW = __IPPROTO_RAW + IPPROTO_MPTCP = __IPPROTO_MPTCP diff --git a/src/liburing/socket_extra.pxd b/src/liburing/socket_extra.pxd index 855260b..c019ccb 100644 --- a/src/liburing/socket_extra.pxd +++ b/src/liburing/socket_extra.pxd @@ -3,6 +3,10 @@ from .socket cimport * from .error cimport trap_error +cdef class getaddrinfo: + cdef __addrinfo* ptr + + cpdef int bind(int sockfd, sockaddr addr) nogil cpdef int listen(int sockfd, int backlog) nogil cpdef int getpeername(int sockfd, sockaddr addr) nogil diff --git a/src/liburing/socket_extra.pyx b/src/liburing/socket_extra.pyx index 840ebe2..2b64f4a 100644 --- a/src/liburing/socket_extra.pyx +++ b/src/liburing/socket_extra.pyx @@ -1,12 +1,63 @@ from libc.string cimport memset +cdef class getaddrinfo: + def __cinit__(self, const char* host, char* port_service, + int family=0, int type=0, int proto=0, int flags=0): + ''' + Example + >>> for af_, sock_, proto, canon, addr in getaddrinfo(b'127.0.0.1', b'12345'): + ... ... + ... io_uring_prep_socket(sqe, af_, sock_) + ... ... + ... io_uring_prep_connect(sqe, sockfd, addr) + ... ... + ... break # if connection successful break, else try next. + ''' + # TODO: `port_service` should handle both `int` & `bytes` types. + cdef: + int no + __addrinfo hints + + memset(&hints, 0, sizeof(__addrinfo)) + hints.ai_flags = flags + hints.ai_family = family + hints.ai_socktype = type + hints.ai_protocol = proto + if no := __getaddrinfo(host, port_service, &hints, &self.ptr): + trap_error(no, __gai_strerror(no).decode()) + + def __dealloc__(self): + if self.ptr is not NULL: + __freeaddrinfo(self.ptr) + self.ptr = NULL + + def __iter__(self): + cdef: + __addrinfo* p = self.ptr + sockaddr addr + while p.ai_next is not NULL: + addr = sockaddr() + addr.ptr = p.ai_addr + addr.family = p.ai_family + addr.sizeof = p.ai_addrlen + yield ( + p.ai_family, + p.ai_socktype, + p.ai_protocol, + p.ai_canonname or b'', + addr + ) + p = p.ai_next + + cpdef int bind(int sockfd, sockaddr addr) nogil: return trap_error(__bind(sockfd, <__sockaddr*>addr.ptr, addr.sizeof)) cpdef int listen(int sockfd, int backlog) nogil: return trap_error(__listen(sockfd, backlog)) +# TODO cpdef int getpeername(int sockfd, sockaddr addr) nogil: ''' TODO ''' return trap_error(__getpeername(sockfd, <__sockaddr*>addr.ptr, &addr.sizeof)) @@ -50,7 +101,6 @@ cpdef tuple[bytes, uint16_t] getsockname(int sockfd, sockaddr addr): raise TypeError('getsockname() - received `addr.family` type not supported!') return (ip, port) - cpdef tuple getnameinfo(sockaddr addr, int flags=0): ''' Example diff --git a/test/socket/getaddinfo_test.py b/test/socket/getaddinfo_test.py new file mode 100644 index 0000000..821273c --- /dev/null +++ b/test/socket/getaddinfo_test.py @@ -0,0 +1,19 @@ +import pytest +import liburing + + +def test_getaddrinfo(): + assert len(list(liburing.getaddrinfo(b'127.0.0.1', b'12345'))) == 2 + assert len(list(liburing.getaddrinfo(b'python.org', b'80'))) > 12 < 33 + for af_, sock_, proto, canon, addr in liburing.getaddrinfo(b'127.0.0.1', b'12345'): + assert af_ == liburing.AF_INET + assert sock_ == liburing.SOCK_STREAM + assert proto == liburing.IPPROTO_TCP + assert canon == b'' + assert type(addr) is liburing.sockaddr + assert addr.family == liburing.AF_INET + break + + msg = 'Servname not supported for ai_socktype' + with pytest.raises(OSError, match=msg): + liburing.getaddrinfo(b'127.0.0.1', b'123abc45') diff --git a/test/socket/getsockname_test.py b/test/socket/getsockname_test.py index abc9850..c281042 100644 --- a/test/socket/getsockname_test.py +++ b/test/socket/getsockname_test.py @@ -38,6 +38,6 @@ def test_getsockname(ring, cqe): liburing.io_uring_prep_close(sqe, sockfd) sqe.user_data = i+1 assert liburing.io_uring_submit_and_wait_timeout(ring, cqe, 1, ts) == 1 - sockfd = liburing.trap_error(cqe.res) + liburing.trap_error(cqe.res) assert cqe.user_data == i+1 liburing.io_uring_cqe_seen(ring, cqe)