From 5d1e3fbcdcb03bf1c13cb9bd01b9d6bb62f776d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 14 Dec 2016 23:35:42 -0500 Subject: tests: skip journal.stream tests on ENOENT error When running in a chroot, doctests that called journal.stream would fail with ENOENT. Move the tests to test_journal, where we can skip tests properly (without uglyfying the documentation). Fixes #32. --- systemd/journal.py | 8 ++++---- systemd/test/test_journal.py | 27 +++++++++++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/systemd/journal.py b/systemd/journal.py index e9f714e..41ffbdc 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -456,8 +456,8 @@ def stream(identifier=None, priority=LOG_INFO, level_prefix=False): newline character is written. >>> from systemd import journal - >>> stream = journal.stream('myapp') - >>> res = stream.write('message...\n') + >>> stream = journal.stream('myapp') # doctest: +SKIP + >>> res = stream.write('message...\n') # doctest: +SKIP will produce the following message in the journal:: @@ -470,8 +470,8 @@ def stream(identifier=None, priority=LOG_INFO, level_prefix=False): This interface can be used conveniently with the print function: >>> from __future__ import print_function - >>> fileobj = journal.stream() - >>> print('message...', file=fileobj) # doctest: +SKIP + >>> stream = journal.stream() # doctest: +SKIP + >>> print('message...', file=stream) # doctest: +SKIP priority is the syslog priority, one of `LOG_EMERG`, `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py index dceec3f..2ecfc16 100644 --- a/systemd/test/test_journal.py +++ b/systemd/test/test_journal.py @@ -1,3 +1,4 @@ +from __future__ import print_function import contextlib import datetime import errno @@ -13,11 +14,11 @@ import pytest TEST_MID = uuid.UUID('8441372f8dca4ca98694a6091fd8519f') @contextlib.contextmanager -def skip_enosys(): +def skip_oserror(code): try: yield - except OSError as e: - if e.errno == errno.ENOSYS: + except (OSError, IOError) as e: + if e.errno == code: pytest.skip() raise @@ -96,7 +97,7 @@ def test_reader_init_path_nondirectory_fd(): def test_reader_init_path_fd(tmpdir): fd = os.open(tmpdir.strpath, os.O_RDONLY) - with skip_enosys(): + with skip_oserror(errno.ENOSYS): j1 = journal.Reader(path=fd) assert list(j1) == [] @@ -139,7 +140,7 @@ def test_reader_this_machine(tmpdir): def test_reader_query_unique(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.query_unique('FOOBAR') assert isinstance(ans, set) assert ans == set() @@ -147,7 +148,7 @@ def test_reader_query_unique(tmpdir): def test_reader_enumerate_fields(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.enumerate_fields() assert isinstance(ans, set) assert ans == set() @@ -155,14 +156,14 @@ def test_reader_enumerate_fields(tmpdir): def test_reader_has_runtime_files(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.has_runtime_files() assert ans == False def test_reader_has_persistent_files(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.has_runtime_files() assert ans == False @@ -200,3 +201,13 @@ def test_seek_realtime(tmpdir): long_ago = datetime.datetime(1970, 5, 4) j.seek_realtime(long_ago) + +def test_journal_stream(): + # This will fail when running in a bare chroot without /run/systemd/journal/stdout + with skip_oserror(errno.ENOENT): + stream = journal.stream('test_journal.py') + + res = stream.write('message...\n') + assert res in (11, None) # Python2 returns None + + print('printed message...', file=stream) -- cgit v1.2.1 From 5d3be8ccba5fe48b09e2a4e816f21c39038adfd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 14 Dec 2016 23:43:28 -0500 Subject: _daemon,_reader: return ENOSYS instead of NotImplemented In _reader we were raising OSError(errno=ENOSYS), but in _dameon we were raising NotImplementedError. Let's always use ENOSYS. Also, make the messages in _reader more specific. Fixes #33. --- systemd/_daemon.c | 4 ++-- systemd/_reader.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/systemd/_daemon.c b/systemd/_daemon.c index a041b1b..87a33f0 100644 --- a/systemd/_daemon.c +++ b/systemd/_daemon.c @@ -145,14 +145,14 @@ static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { #ifdef HAVE_PID_NOTIFY r = sd_pid_notify(pid, unset, msg); #else - PyErr_SetString(PyExc_NotImplementedError, "Compiled without support for sd_pid_notify"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify"); return NULL; #endif } else { #ifdef HAVE_PID_NOTIFY_WITH_FDS r = sd_pid_notify_with_fds(pid, unset, msg, arr, n_fds); #else - PyErr_SetString(PyExc_NotImplementedError, "Compiled without support for sd_pid_notify_with_fds"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify_with_fds"); return NULL; #endif } diff --git a/systemd/_reader.c b/systemd/_reader.c index 3a2c218..5b7e191 100644 --- a/systemd/_reader.c +++ b/systemd/_reader.c @@ -1048,7 +1048,7 @@ static PyObject* Reader_enumerate_fields(Reader *self, PyObject *args) { _value_set = NULL; return value_set; #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_enumerate_fields"); return NULL; #endif } @@ -1069,7 +1069,7 @@ static PyObject* Reader_has_runtime_files(Reader *self, PyObject *args) { return PyBool_FromLong(r); #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_has_runtime_files"); return NULL; #endif } @@ -1090,7 +1090,7 @@ static PyObject* Reader_has_persistent_files(Reader *self, PyObject *args) { return PyBool_FromLong(r); #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_has_persistent_files"); return NULL; #endif } -- cgit v1.2.1 From 63cc41a45671dccadaa0723915fc1389327530cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 13 Dec 2016 20:11:24 -0500 Subject: daemon: add basic support for sd_is_socket_sockaddr Later on a proper wrapper in daemon.py should be added. --- setup.py | 3 +- systemd/_daemon.c | 55 ++++++++++++++++-- systemd/util.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ systemd/util.h | 33 +++++++++++ 4 files changed, 254 insertions(+), 6 deletions(-) create mode 100644 systemd/util.c create mode 100644 systemd/util.h diff --git a/setup.py b/setup.py index 529f613..1acfbc5 100644 --- a/setup.py +++ b/setup.py @@ -61,7 +61,8 @@ _reader = Extension('systemd/_reader', **lib('libsystemd', 'libsystemd-journal', **defines)) _daemon = Extension('systemd/_daemon', sources = ['systemd/_daemon.c', - 'systemd/pyutil.c'], + 'systemd/pyutil.c', + 'systemd/util.c'], extra_compile_args=['-Werror=implicit-function-declaration'], **lib('libsystemd', 'libsystemd-daemon', **defines)) id128 = Extension('systemd/id128', diff --git a/systemd/_daemon.c b/systemd/_daemon.c index 87a33f0..f122722 100644 --- a/systemd/_daemon.c +++ b/systemd/_daemon.c @@ -2,7 +2,7 @@ /*** - Copyright 2013 Zbigniew Jędrzejewski-Szmek + Copyright 2013-2016 Zbigniew Jędrzejewski-Szmek python-systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -31,12 +31,18 @@ #include "systemd/sd-daemon.h" #include "pyutil.h" #include "macro.h" +#include "util.h" #if LIBSYSTEMD_VERSION >= 214 # define HAVE_PID_NOTIFY -# if LIBSYSTEMD_VERSION >= 219 -# define HAVE_PID_NOTIFY_WITH_FDS -# endif +#endif + +#if LIBSYSTEMD_VERSION >= 219 +# define HAVE_PID_NOTIFY_WITH_FDS +#endif + +#if LIBSYSTEMD_VERSION >= 233 +# define HAVE_IS_SOCKET_SOCKADDR #endif PyDoc_STRVAR(module__doc__, @@ -311,6 +317,42 @@ static PyObject* is_socket_inet(PyObject *self, PyObject *args) { return PyBool_FromLong(r); } +#ifdef HAVE_IS_SOCKET_SOCKADDR +PyDoc_STRVAR(is_socket_sockaddr__doc__, + "_is_socket_sockaddr(fd, address, type=0, listening=-1) -> bool\n\n" + "Wraps sd_is_socket_inet_sockaddr(3).\n" + "`address` is a systemd-style numerical IPv4 or IPv6 address as used in\n" + "ListenStream=. A port may be included after a colon (\":\"). See\n" + "systemd.socket(5) for details.\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket_sockaddr(PyObject *self, PyObject *args) { + int r; + int fd, type = 0, listening = -1; + const char *address; + union sockaddr_union addr = {}; + unsigned addr_len; + + if (!PyArg_ParseTuple(args, "is|ii:_is_socket_sockaddr", + &fd, + &address, + &type, &listening)) + return NULL; + + r = parse_sockaddr(address, &addr, &addr_len); + if (r < 0) { + set_error(r, NULL, "Cannot parse address"); + return NULL; + } + + r = sd_is_socket_sockaddr(fd, type, &addr.sa, addr_len, listening); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} +#endif PyDoc_STRVAR(is_socket_unix__doc__, "_is_socket_unix(fd, type, listening, path) -> bool\n\n" @@ -355,8 +397,11 @@ static PyMethodDef methods[] = { { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, { "_is_socket_inet", is_socket_inet, METH_VARARGS, is_socket_inet__doc__}, +#ifdef HAVE_IS_SOCKET_SOCKADDR + { "_is_socket_sockaddr", is_socket_sockaddr, METH_VARARGS, is_socket_sockaddr__doc__}, +#endif { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, - { NULL, NULL, 0, NULL } /* Sentinel */ + {} /* Sentinel */ }; #if PY_MAJOR_VERSION < 3 diff --git a/systemd/util.c b/systemd/util.c new file mode 100644 index 0000000..c6ec3b2 --- /dev/null +++ b/systemd/util.c @@ -0,0 +1,169 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +/* stuff imported from systemd without any changes */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "util.h" + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + /* strtoul() is happy to parse negative values, and silently + * converts them to unsigned values without generating an + * error. We want a clean error, hence let's look for the "-" + * prefix on our own, and generate an error. But let's do so + * only after strtoul() validated that the string is clean + * otherwise, so that we return EINVAL preferably over + * ERANGE. */ + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +static bool socket_ipv6_is_supported(void) { + if (access("/proc/net/if_inet6", F_OK) != 0) + return false; + + return true; +} + +int parse_sockaddr(const char *s, + union sockaddr_union *addr, unsigned *addr_len) { + + char *e, *n; + unsigned u; + int r; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + e = strchr(s+1, ']'); + if (!e) + return -EINVAL; + + n = strndupa(s+1, e-s-1); + + errno = 0; + if (inet_pton(AF_INET6, n, &addr->in6.sin6_addr) <= 0) + return errno > 0 ? -errno : -EINVAL; + + e++; + if (*e != ':') + return -EINVAL; + + e++; + r = safe_atou(e, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16((uint16_t)u); + *addr_len = sizeof(struct sockaddr_in6); + + } else { + e = strchr(s, ':'); + if (e) { + r = safe_atou(e+1, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + n = strndupa(s, e-s); + + /* IPv4 in w.x.y.z:p notation? */ + r = inet_pton(AF_INET, n, &addr->in.sin_addr); + if (r < 0) + return -errno; + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + addr->in.sin_family = AF_INET; + addr->in.sin_port = htobe16((uint16_t)u); + *addr_len = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(n) > IF_NAMESIZE-1) + return -EINVAL; + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + if (idx == 0) + return -EINVAL; + + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16((uint16_t)u); + addr->in6.sin6_scope_id = idx; + addr->in6.sin6_addr = in6addr_any; + *addr_len = sizeof(struct sockaddr_in6); + } + } else { + /* Just a port */ + r = safe_atou(s, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (socket_ipv6_is_supported()) { + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16((uint16_t)u); + addr->in6.sin6_addr = in6addr_any; + *addr_len = sizeof(struct sockaddr_in6); + } else { + addr->in.sin_family = AF_INET; + addr->in.sin_port = htobe16((uint16_t)u); + addr->in.sin_addr.s_addr = INADDR_ANY; + *addr_len = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} diff --git a/systemd/util.h b/systemd/util.h new file mode 100644 index 0000000..337920c --- /dev/null +++ b/systemd/util.h @@ -0,0 +1,33 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +}; + +int safe_atou(const char *s, unsigned *ret_u); +int parse_sockaddr(const char *s, + union sockaddr_union *addr, unsigned *addr_len); -- cgit v1.2.1 From 17a1dad79588313c879a3eef94dcb8d5ad1b57ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 13 Dec 2016 20:38:46 -0500 Subject: util: add compatibility for old glibc Oh, Ubuntu! --- systemd/util.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/systemd/util.c b/systemd/util.c index c6ec3b2..f4551c3 100644 --- a/systemd/util.c +++ b/systemd/util.c @@ -19,6 +19,10 @@ /* stuff imported from systemd without any changes */ +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + #include #include #include -- cgit v1.2.1 From 5ffc22689058b4f14cd9a8bb17bed71aa051021d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 15 Dec 2016 08:38:48 -0500 Subject: _daemon: allow specifying flowinfo Not particularly useful, but let's have it for completeness. --- systemd/_daemon.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/systemd/_daemon.c b/systemd/_daemon.c index f122722..90cf205 100644 --- a/systemd/_daemon.c +++ b/systemd/_daemon.c @@ -319,7 +319,7 @@ static PyObject* is_socket_inet(PyObject *self, PyObject *args) { #ifdef HAVE_IS_SOCKET_SOCKADDR PyDoc_STRVAR(is_socket_sockaddr__doc__, - "_is_socket_sockaddr(fd, address, type=0, listening=-1) -> bool\n\n" + "_is_socket_sockaddr(fd, address, type=0, flowinfo=0, listening=-1) -> bool\n\n" "Wraps sd_is_socket_inet_sockaddr(3).\n" "`address` is a systemd-style numerical IPv4 or IPv6 address as used in\n" "ListenStream=. A port may be included after a colon (\":\"). See\n" @@ -329,15 +329,17 @@ PyDoc_STRVAR(is_socket_sockaddr__doc__, static PyObject* is_socket_sockaddr(PyObject *self, PyObject *args) { int r; - int fd, type = 0, listening = -1; + int fd, type = 0, flowinfo = 0, listening = -1; const char *address; union sockaddr_union addr = {}; unsigned addr_len; - if (!PyArg_ParseTuple(args, "is|ii:_is_socket_sockaddr", + if (!PyArg_ParseTuple(args, "is|iii:_is_socket_sockaddr", &fd, &address, - &type, &listening)) + &type, + &flowinfo, + &listening)) return NULL; r = parse_sockaddr(address, &addr, &addr_len); @@ -346,6 +348,15 @@ static PyObject* is_socket_sockaddr(PyObject *self, PyObject *args) { return NULL; } + if (flowinfo != 0) { + if (addr.sa.sa_family != AF_INET6) { + set_error(-EINVAL, NULL, "flowinfo is only applicable to IPv6 addresses"); + return NULL; + } + + addr.in6.sin6_flowinfo = flowinfo; + } + r = sd_is_socket_sockaddr(fd, type, &addr.sa, addr_len, listening); if (set_error(r, NULL, NULL) < 0) return NULL; -- cgit v1.2.1 From 4a3cee1ad33d35bb14b845a4d5cfcebe98c99260 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 15 Dec 2016 20:14:19 -0500 Subject: daemon: add wrapper for sd_is_socket_sockaddr and tests Specifying the address without does not work. --- systemd/daemon.py | 15 +++++++++++++++ systemd/test/test_daemon.py | 44 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/systemd/daemon.py b/systemd/daemon.py index 82011ca..5d398f2 100644 --- a/systemd/daemon.py +++ b/systemd/daemon.py @@ -5,6 +5,7 @@ from ._daemon import (__version__, _is_fifo, _is_socket, _is_socket_inet, + _is_socket_sockaddr, _is_socket_unix, _is_mq, LISTEN_FDS_START) @@ -28,6 +29,20 @@ def is_socket_inet(fileobj, family=_AF_UNSPEC, type=0, listening=-1, port=0): fd = _convert_fileobj(fileobj) return _is_socket_inet(fd, family, type, listening, port) +def is_socket_sockaddr(fileobj, address, type=0, flowinfo=0, listening=-1): + """Check socket type, address and/or port, flowinfo, listening state. + + Wraps sd_is_socket_inet_sockaddr(3). + + `address` is a systemd-style numerical IPv4 or IPv6 address as used in + ListenStream=. A port may be included after a colon (":"). + See systemd.socket(5) for details. + + Constants for `family` are defined in the socket module. + """ + fd = _convert_fileobj(fileobj) + return _is_socket_sockaddr(fd, address, type, flowinfo, listening) + def is_socket_unix(fileobj, type=0, listening=-1, path=None): fd = _convert_fileobj(fileobj) return _is_socket_unix(fd, type, listening, path) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index e827e1d..272c46f 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -9,6 +9,7 @@ from systemd.daemon import (booted, is_socket, _is_socket, is_socket_inet, _is_socket_inet, is_socket_unix, _is_socket_unix, + is_socket_sockaddr, _is_socket_sockaddr, is_mq, _is_mq, listen_fds, notify) @@ -122,15 +123,18 @@ def test_no_mismatch(): assert not is_fifo(sock) assert not is_mq_wrapper(sock) assert not is_socket_inet(sock) + assert not is_socket_sockaddr(sock, '127.0.0.1:2000') fd = sock.fileno() assert not is_fifo(fd) assert not is_mq_wrapper(fd) assert not is_socket_inet(fd) + assert not is_socket_sockaddr(fd, '127.0.0.1:2000') assert not _is_fifo(fd) assert not _is_mq_wrapper(fd) assert not _is_socket_inet(fd) + assert not _is_socket_sockaddr(fd, '127.0.0.1:2000') def test_is_socket(): with closing_socketpair(socket.AF_UNIX) as pair: @@ -141,12 +145,42 @@ def test_is_socket(): assert not is_socket(arg, socket.AF_INET) assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert not is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) - assert is_socket(sock) - assert is_socket(arg, socket.AF_UNIX) - assert not is_socket(arg, socket.AF_INET) - assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) - assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert _is_socket(arg) + assert _is_socket(arg, socket.AF_UNIX) + assert not _is_socket(arg, socket.AF_INET) + assert _is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) + assert not _is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert not _is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) + +def test_is_socket_sockaddr(): + with contextlib.closing(socket.socket(socket.AF_INET)) as sock: + sock.bind(('127.0.0.1', 0)) + addr, port = sock.getsockname() + + for listening in (0, 1): + for arg in (sock, sock.fileno()): + # assert is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_STREAM) + assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), socket.SOCK_STREAM) + + assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=listening) + assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=-1) + assert not is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=not listening) + + with pytest.raises(ValueError): + is_socket_sockaddr(arg, '127.0.0.1', flowinfo=123456) + + assert not is_socket_sockaddr(arg, '129.168.11.11:23'.format(port), socket.SOCK_STREAM) + #assert not is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + + with pytest.raises(ValueError): + _is_socket_sockaddr(arg, '127.0.0.1', 0, 123456) + + assert not _is_socket_sockaddr(arg, '129.168.11.11:23'.format(port), socket.SOCK_STREAM) + #assert not _is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + + sock.listen(11) def test__is_socket(): with closing_socketpair(socket.AF_UNIX) as pair: -- cgit v1.2.1 From bc691d8e293a593fbd14ad1d592d06f4f490ed29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 15 Dec 2016 20:47:10 -0500 Subject: Fix handling of addresses without port and add tests --- systemd/test/test_daemon.py | 19 +++++----- systemd/util.c | 90 ++++++++++++++++++++++++++------------------- 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index 272c46f..5e9f5b8 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -158,27 +158,28 @@ def test_is_socket_sockaddr(): with contextlib.closing(socket.socket(socket.AF_INET)) as sock: sock.bind(('127.0.0.1', 0)) addr, port = sock.getsockname() + port = ':{}'.format(port) for listening in (0, 1): for arg in (sock, sock.fileno()): - # assert is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_STREAM) - assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), socket.SOCK_STREAM) + assert is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_STREAM) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, socket.SOCK_STREAM) - assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=listening) - assert is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=-1) - assert not is_socket_sockaddr(arg, '127.0.0.1:{}'.format(port), listening=not listening) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=listening) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=-1) + assert not is_socket_sockaddr(arg, '127.0.0.1' + port, listening=not listening) with pytest.raises(ValueError): is_socket_sockaddr(arg, '127.0.0.1', flowinfo=123456) - assert not is_socket_sockaddr(arg, '129.168.11.11:23'.format(port), socket.SOCK_STREAM) - #assert not is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + assert not is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) + assert not is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) with pytest.raises(ValueError): _is_socket_sockaddr(arg, '127.0.0.1', 0, 123456) - assert not _is_socket_sockaddr(arg, '129.168.11.11:23'.format(port), socket.SOCK_STREAM) - #assert not _is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + assert not _is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) + assert not _is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) sock.listen(11) diff --git a/systemd/util.c b/systemd/util.c index f4551c3..e02c825 100644 --- a/systemd/util.c +++ b/systemd/util.c @@ -71,6 +71,43 @@ static bool socket_ipv6_is_supported(void) { return true; } +static int assign_address(const char *s, + uint16_t port, + union sockaddr_union *addr, unsigned *addr_len) { + int r; + + /* IPv4 in w.x.y.z:p notation? */ + r = inet_pton(AF_INET, s, &addr->in.sin_addr); + if (r < 0) + return -errno; + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + addr->in.sin_family = AF_INET; + addr->in.sin_port = htobe16(port); + *addr_len = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(s) > IF_NAMESIZE-1) + return -EINVAL; + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(s); + if (idx == 0) + return -EINVAL; + + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16(port); + addr->in6.sin6_scope_id = idx; + addr->in6.sin6_addr = in6addr_any; + *addr_len = sizeof(struct sockaddr_in6); + } + + return 0; +} + + int parse_sockaddr(const char *s, union sockaddr_union *addr, unsigned *addr_len) { @@ -92,19 +129,22 @@ int parse_sockaddr(const char *s, return errno > 0 ? -errno : -EINVAL; e++; - if (*e != ':') - return -EINVAL; + if (*e) { + if (*e != ':') + return -EINVAL; - e++; - r = safe_atou(e, &u); - if (r < 0) - return r; + e++; + r = safe_atou(e, &u); + if (r < 0) + return r; - if (u <= 0 || u > 0xFFFF) - return -EINVAL; + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + addr->in6.sin6_port = htobe16((uint16_t)u); + } addr->in6.sin6_family = AF_INET6; - addr->in6.sin6_port = htobe16((uint16_t)u); *addr_len = sizeof(struct sockaddr_in6); } else { @@ -118,40 +158,14 @@ int parse_sockaddr(const char *s, return -EINVAL; n = strndupa(s, e-s); + return assign_address(n, u, addr, addr_len); - /* IPv4 in w.x.y.z:p notation? */ - r = inet_pton(AF_INET, n, &addr->in.sin_addr); - if (r < 0) - return -errno; - - if (r > 0) { - /* Gotcha, it's a traditional IPv4 address */ - addr->in.sin_family = AF_INET; - addr->in.sin_port = htobe16((uint16_t)u); - *addr_len = sizeof(struct sockaddr_in); - } else { - unsigned idx; - - if (strlen(n) > IF_NAMESIZE-1) - return -EINVAL; - - /* Uh, our last resort, an interface name */ - idx = if_nametoindex(n); - if (idx == 0) - return -EINVAL; - - addr->in6.sin6_family = AF_INET6; - addr->in6.sin6_port = htobe16((uint16_t)u); - addr->in6.sin6_scope_id = idx; - addr->in6.sin6_addr = in6addr_any; - *addr_len = sizeof(struct sockaddr_in6); - } } else { - /* Just a port */ r = safe_atou(s, &u); if (r < 0) - return r; + return assign_address(s, 0, addr, addr_len); + /* Just a port */ if (u <= 0 || u > 0xFFFF) return -EINVAL; -- cgit v1.2.1