diff --git a/Doc/lib/libsocket.tex b/Doc/lib/libsocket.tex index 4febf220c61..73c0ff311f0 100644 --- a/Doc/lib/libsocket.tex +++ b/Doc/lib/libsocket.tex @@ -150,6 +150,11 @@ those symbols that are defined in the \UNIX{} header files are defined; for a few symbols, default values are provided. \end{datadesc} +\begin{datadesc}{has_ipv6} +This constant contains a boolean value which indicates if IPv6 is +supported on this platform. +\end{datadesc} + \begin{funcdesc}{getaddrinfo}{host, port\optional{, family, socktype, proto, flags}} Resolves the \var{host}/\var{port} argument, into a sequence of @@ -349,6 +354,43 @@ length, \exception{socket.error} will be raised. support. \end{funcdesc} +\begin{funcdesc}{inet_pton}{address_family, ip_string} +Convert an IP address from its family-specific string format to a packed, +binary format. + +Supported values for address_family are currently \constant{AF_INET} +and \constant{AF_INET6}. + +\function{inet_pton()} is useful when a library or network protocol calls for +an object of type \ctype{struct in_addr} (similar to \function{inet_aton()}) +or \ctype{struct in6_addr}. + +If the IP address string passed to this function is invalid, +\exception{socket.error} will be raised. Note that exactly what is valid +depends on both the value of \var{address_family} and the underlying +implementation of \cfunction{inet_pton()}. +\versionadded{2.3} +\end{funcdesc} + +\begin{funcdesc}{inet_ntop}{address_family, packed_ip} +Convert a packed IP address (a string of some number of characters) to its +standard, family-specific string representation (for example, '7.10.0.5' or +'5aef:2b::8') + +Supported values for address_family are currently \constant{AF_INET} +and \constant{AF_INET6}. + +\function{inet_pton()} is useful when a library or network protocol calls for +an object of type \ctype{struct in_addr} (similar to \function{inet_aton()}) +or \ctype{struct in6_addr}. + +If the string passed to this function is not the correct length for the +specified address family, \exception{ValueError} will be raised. +A \exception{socket.error} is raised for errors from the call to +\function{inet_ntop()}. +\versionadded{2.3} +\end{funcdesc} + \begin{funcdesc}{getdefaulttimeout}{} Return the default timeout in floating seconds for new socket objects. A value of \code{None} indicates that new socket objects have no timeout. diff --git a/Lib/socket.py b/Lib/socket.py index cb0181b2a6c..12f3c052d29 100644 --- a/Lib/socket.py +++ b/Lib/socket.py @@ -30,6 +30,7 @@ Special objects: SocketType -- type object for socket objects error -- exception raised for I/O errors +has_ipv6 -- boolean value indicating if IPv6 is supported Integer constants: diff --git a/Lib/test/test_socket.py b/Lib/test/test_socket.py index bca1c6cabd1..16a39896b0c 100644 --- a/Lib/test/test_socket.py +++ b/Lib/test/test_socket.py @@ -318,6 +318,65 @@ class GeneralModuleTests(unittest.TestCase): # Check that setting it to an invalid type raises TypeError self.assertRaises(TypeError, socket.setdefaulttimeout, "spam") + def testIPv4toString(self): + from socket import inet_aton as f, inet_pton, AF_INET + g = lambda a: inet_pton(AF_INET, a) + + self.assertEquals('\x00\x00\x00\x00', f('0.0.0.0')) + self.assertEquals('\xff\x00\xff\x00', f('255.0.255.0')) + self.assertEquals('\xaa\xaa\xaa\xaa', f('170.170.170.170')) + self.assertEquals('\x01\x02\x03\x04', f('1.2.3.4')) + + self.assertEquals('\x00\x00\x00\x00', g('0.0.0.0')) + self.assertEquals('\xff\x00\xff\x00', g('255.0.255.0')) + self.assertEquals('\xaa\xaa\xaa\xaa', g('170.170.170.170')) + + def testIPv6toString(self): + try: + from socket import inet_pton, AF_INET6, has_ipv6 + if not has_ipv6: + return + except ImportError: + return + f = lambda a: inet_pton(AF_INET6, a) + + self.assertEquals('\x00' * 16, f('::')) + self.assertEquals('\x00' * 16, f('0::0')) + self.assertEquals('\x00\x01' + '\x00' * 14, f('1::')) + self.assertEquals( + '\x45\xef\x76\xcb\x00\x1a\x56\xef\xaf\xeb\x0b\xac\x19\x24\xae\xae', + f('45ef:76cb:1a:56ef:afeb:bac:1924:aeae') + ) + + def testStringToIPv4(self): + from socket import inet_ntoa as f, inet_ntop, AF_INET + g = lambda a: inet_ntop(AF_INET, a) + + self.assertEquals('1.0.1.0', f('\x01\x00\x01\x00')) + self.assertEquals('170.85.170.85', f('\xaa\x55\xaa\x55')) + self.assertEquals('255.255.255.255', f('\xff\xff\xff\xff')) + self.assertEquals('1.2.3.4', f('\x01\x02\x03\x04')) + + self.assertEquals('1.0.1.0', g('\x01\x00\x01\x00')) + self.assertEquals('170.85.170.85', g('\xaa\x55\xaa\x55')) + self.assertEquals('255.255.255.255', g('\xff\xff\xff\xff')) + + def testStringToIPv6(self): + try: + from socket import inet_ntop, AF_INET6, has_ipv6 + if not has_ipv6: + return + except ImportError: + return + f = lambda a: inet_ntop(AF_INET6, a) + + self.assertEquals('::', f('\x00' * 16)) + self.assertEquals('::1', f('\x00' * 15 + '\x01')) + self.assertEquals( + 'aef:b01:506:1001:ffff:9997:55:170', + f('\x0a\xef\x0b\x01\x05\x06\x10\x01\xff\xff\x99\x97\x00\x55\x01\x70') + ) + # XXX The following don't test module-level functionality... def testSockName(self): diff --git a/Misc/ACKS b/Misc/ACKS index f99f6584a91..2fbe9af5c0b 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -83,6 +83,7 @@ Alastair Burt Tarn Weisner Burton Lee Busby Ralph Butler +Jp Calderone Daniel Calvelo Brett Cannon Mike Carlton diff --git a/Misc/NEWS b/Misc/NEWS index d666762ea0e..8c1d7c531d5 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -383,6 +383,10 @@ Extension modules zlib test suite using the unittest module. (SF bug #640230 and patch #678531.) +- The socket module now provides the functions inet_pton and inet_ntop + for converting between string and packed representation of IP addresses. + See SF patch #658327. + - Added an itertools module containing high speed, memory efficient looping constructs inspired by tools from Haskell and SML. diff --git a/Modules/socketmodule.c b/Modules/socketmodule.c index ba64cb86864..fd6ede0ea78 100644 --- a/Modules/socketmodule.c +++ b/Modules/socketmodule.c @@ -35,6 +35,7 @@ Module interface: --> List of (family, socktype, proto, canonname, sockaddr) - socket.getnameinfo(sockaddr, flags) --> (host, port) - socket.AF_INET, socket.SOCK_STREAM, etc.: constants from +- socket.has_ipv6: boolean value indicating if IPv6 is supported - socket.inet_aton(IP address) -> 32-bit packed IP representation - socket.inet_ntoa(packed IP) -> IP address string - socket.getdefaulttimeout() -> None | float @@ -62,6 +63,9 @@ Local naming conventions: #include "Python.h" +#undef MAX +#define MAX(x, y) ((x) < (y) ? (y) : (x)) + /* Socket object documentation */ PyDoc_STRVAR(sock_doc, "socket([family[, type[, proto]]]) -> socket object\n\ @@ -2776,6 +2780,100 @@ socket_inet_ntoa(PyObject *self, PyObject *args) return PyString_FromString(inet_ntoa(packed_addr)); } +#ifdef HAVE_INET_PTON + +PyDoc_STRVAR(inet_pton_doc, +"inet_pton(af, ip) -> packed IP address string\n\ +\n\ +Convert an IP address from string format to a packed string suitable\n\ +for use with low-level network functions."); + +static PyObject * +socket_inet_pton(PyObject *self, PyObject *args) +{ + int af; + char* ip; + int retval; + char packed[MAX(sizeof(struct in_addr), sizeof(struct in6_addr))]; + + if (!PyArg_ParseTuple(args, "is:inet_pton", &af, &ip)) { + return NULL; + } + + retval = inet_pton(af, ip, packed); + if (retval < 0) { + PyErr_SetFromErrno(socket_error); + return NULL; + } else if (retval == 0) { + PyErr_SetString(socket_error, + "illegal IP address string passed to inet_pton"); + return NULL; + } else if (af == AF_INET) { + return PyString_FromStringAndSize(packed, + sizeof(struct in_addr)); + } else if (af == AF_INET6) { + return PyString_FromStringAndSize(packed, + sizeof(struct in6_addr)); + } else { + PyErr_SetString(socket_error, "unknown address family"); + return NULL; + } +} + +PyDoc_STRVAR(inet_ntop_doc, +"inet_ntop(af, packed_ip) -> string formatted IP address\n\ +\n\ +Convert a packed IP address of the given family to string format."); + +static PyObject * +socket_inet_ntop(PyObject *self, PyObject *args) +{ + int af; + char* packed; + int len; + const char* retval; + char ip[MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1]; + + /* Guarantee NUL-termination for PyString_FromString() below */ + memset((void *) &ip[0], '\0', sizeof(ip) + 1); + + if (!PyArg_ParseTuple(args, "is#:inet_ntop", &af, &packed, &len)) { + return NULL; + } + + if (af == AF_INET) { + if (len != sizeof(struct in_addr)) { + PyErr_SetString(PyExc_ValueError, + "invalid length of packed IP address string"); + return NULL; + } + } else if (af == AF_INET6) { + if (len != sizeof(struct in6_addr)) { + PyErr_SetString(PyExc_ValueError, + "invalid length of packed IP address string"); + return NULL; + } + } else { + PyErr_Format(PyExc_ValueError, + "unknown address family %d", af); + return NULL; + } + + retval = inet_ntop(af, packed, ip, sizeof(ip)); + if (!retval) { + PyErr_SetFromErrno(socket_error); + return NULL; + } else { + return PyString_FromString(retval); + } + + /* NOTREACHED */ + PyErr_SetString(PyExc_RuntimeError, "invalid handling of inet_ntop"); + return NULL; +} + +#endif /* HAVE_INET_PTON */ + /* Python interface to getaddrinfo(host, port). */ /*ARGSUSED*/ @@ -3035,6 +3133,12 @@ static PyMethodDef socket_methods[] = { METH_VARARGS, inet_aton_doc}, {"inet_ntoa", socket_inet_ntoa, METH_VARARGS, inet_ntoa_doc}, +#ifdef HAVE_INET_PTON + {"inet_pton", socket_inet_pton, + METH_VARARGS, inet_pton_doc}, + {"inet_ntop", socket_inet_ntop, + METH_VARARGS, inet_ntop_doc}, +#endif {"getaddrinfo", socket_getaddrinfo, METH_VARARGS, getaddrinfo_doc}, {"getnameinfo", socket_getnameinfo, @@ -3178,7 +3282,7 @@ See the socket module for documentation."); PyMODINIT_FUNC init_socket(void) { - PyObject *m; + PyObject *m, *has_ipv6; if (!os_init()) return; @@ -3214,6 +3318,14 @@ init_socket(void) (PyObject *)&sock_type) != 0) return; +#ifdef ENABLE_IPV6 + has_ipv6 = Py_True; +#else + has_ipv6 = Py_False; +#endif + Py_INCREF(has_ipv6); + PyModule_AddObject(m, "has_ipv6", has_ipv6); + /* Export C API */ if (PyModule_AddObject(m, PySocket_CAPI_NAME, PyCObject_FromVoidPtr((void *)&PySocketModuleAPI, NULL) @@ -3800,6 +3912,7 @@ init_socket(void) #ifndef HAVE_INET_PTON /* Simplistic emulation code for inet_pton that only works for IPv4 */ +/* These are not exposed because they do not set errno properly */ int inet_pton(int af, const char *src, void *dst)