[PATCH 0/9] Clean ups to automatic port forwarding
This contains an assortment of cleanups to the code supporting the automatic port forwarding more for past - specifically get_bound_ports() and related functions. This shouldn't mae any functional changes. Based on the series with fixes for new cppcheck-2.12 warnings. David Gibson (9): conf: Cleaner initialisation of default forwarding modes port_fwd: Move automatic port forwarding code to port_fwd.[ch] port_fwd: Better parameterise procfs_scan_listen() util: Add open_in_ns() helper port_fwd: Pre-open /proc/net/* files rather than on-demand port_fwd: Don't NS_CALL get_bound_ports() port_fwd: Split TCP and UDP cases for get_bound_ports() port_fwd: Move port scanning /proc fds into struct port_fwd port_fwd: Simplify get_bound_ports_*() to port_fwd_scan_*() Makefile | 2 +- conf.c | 113 +++++-------------------------------------- conf.h | 1 - passt.h | 5 -- port_fwd.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++++ port_fwd.h | 9 ++++ tcp.c | 39 +-------------- util.c | 104 +++++++++++++++------------------------ util.h | 3 +- 9 files changed, 204 insertions(+), 211 deletions(-) create mode 100644 port_fwd.c -- 2.41.0
Initialisation of the forwarding mode variables is complicated a bit by the
fact that we have different defaults for passt and pasta modes. This leads
to some debateably duplicated code between those two cases.
More significantly, however, currently the final setting of the mode
variable is interleaved with the code to actually start automatic scanning
when that's selected. This essentially mingles "policy" code (setting the
default mode), with implementation code (making that happen). That's a bit
inflexible if we want to change policies in future.
Disentangle these two pieces, and use a slightly different construction to
make things briefer as well.
Signed-off-by: David Gibson
The implementation of scanning /proc files to do automatic port forwarding
is a bit awkwardly split between procfs_scan_listen() in util.c,
get_bound_ports() and related functions in conf.c and the initial setup
code in conf().
Consolidate all of this into port_fwd.h, which already has some related
definitions, and a new port_fwd.c.
Signed-off-by: David Gibson
procfs_scan_listen() does some slightly clunky logic to deduce the fd it
wants to use, the path it wants to open and the state it's looking for
based on parameters for protocol, IP version and whether we're in the
namespace.
However, the caller already has to make choices based on similar parameters
so it can just pass in the things that procfs_scan_listen() needs directly.
Signed-off-by: David Gibson
On Thu, 5 Oct 2023 14:44:39 +1100
David Gibson
procfs_scan_listen() does some slightly clunky logic to deduce the fd it wants to use, the path it wants to open and the state it's looking for based on parameters for protocol, IP version and whether we're in the namespace.
However, the caller already has to make choices based on similar parameters so it can just pass in the things that procfs_scan_listen() needs directly.
Signed-off-by: David Gibson
--- port_fwd.c | 53 +++++++++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/port_fwd.c b/port_fwd.c index 136a450..4b54877 100644 --- a/port_fwd.c +++ b/port_fwd.c @@ -23,39 +23,27 @@ #include "passt.h" #include "lineread.h"
+#define UDP_LISTEN 0x07 +#define TCP_LISTEN 0x0a + /** * procfs_scan_listen() - Set bits for listening TCP or UDP sockets from procfs - * @proto: IPPROTO_TCP or IPPROTO_UDP - * @ip_version: IP version, V4 or V6 - * @ns: Use saved file descriptors for namespace if set + * @fd: Pointer to fd for relevant /proc/net file + * @path: Path to /proc/net file to open (if fd is -1) + * @lstate: Code for listening state to scan for * @map: Bitmap where numbers of ports in listening state will be set * @exclude: Bitmap of ports to exclude from setting (and clear) * * #syscalls:pasta lseek * #syscalls:pasta ppc64le:_llseek ppc64:_llseek armv6l:_llseek armv7l:_llseek */ -static void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, - int ns, uint8_t *map, const uint8_t *exclude) +static void procfs_scan_listen(int *fd, const char *path, unsigned int lstate, + uint8_t *map, const uint8_t *exclude) { - char *path, *line; struct lineread lr; unsigned long port; unsigned int state; - int *fd; - - if (proto == IPPROTO_TCP) { - fd = &c->proc_net_tcp[ip_version][ns]; - if (ip_version == V4) - path = "/proc/net/tcp"; - else - path = "/proc/net/tcp6"; - } else { - fd = &c->proc_net_udp[ip_version][ns]; - if (ip_version == V4) - path = "/proc/net/udp"; - else - path = "/proc/net/udp6"; - } + char *line;
if (*fd != -1) { if (lseek(*fd, 0, SEEK_SET)) { @@ -74,8 +62,7 @@ static void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, continue;
/* See enum in kernel's include/net/tcp_states.h */
This comment should now be moved before #define UDP_LISTEN and TCP_LISTEN, as it's not otherwise clear where they're coming from. Given how late I'm reviewing all this, and that presumably you have a bunch of series/patches based on it, I'm also fine to apply this and patch it in a separate commit, or also fix this up on merge myself -- let me know. Same for the comments to the next patches/series. -- Stefano
On Thu, Nov 02, 2023 at 07:07:19PM +0100, Stefano Brivio wrote:
On Thu, 5 Oct 2023 14:44:39 +1100 David Gibson
wrote: procfs_scan_listen() does some slightly clunky logic to deduce the fd it wants to use, the path it wants to open and the state it's looking for based on parameters for protocol, IP version and whether we're in the namespace.
However, the caller already has to make choices based on similar parameters so it can just pass in the things that procfs_scan_listen() needs directly.
Signed-off-by: David Gibson
--- port_fwd.c | 53 +++++++++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/port_fwd.c b/port_fwd.c index 136a450..4b54877 100644 --- a/port_fwd.c +++ b/port_fwd.c @@ -23,39 +23,27 @@ #include "passt.h" #include "lineread.h"
+#define UDP_LISTEN 0x07 +#define TCP_LISTEN 0x0a + /** * procfs_scan_listen() - Set bits for listening TCP or UDP sockets from procfs - * @proto: IPPROTO_TCP or IPPROTO_UDP - * @ip_version: IP version, V4 or V6 - * @ns: Use saved file descriptors for namespace if set + * @fd: Pointer to fd for relevant /proc/net file + * @path: Path to /proc/net file to open (if fd is -1) + * @lstate: Code for listening state to scan for * @map: Bitmap where numbers of ports in listening state will be set * @exclude: Bitmap of ports to exclude from setting (and clear) * * #syscalls:pasta lseek * #syscalls:pasta ppc64le:_llseek ppc64:_llseek armv6l:_llseek armv7l:_llseek */ -static void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, - int ns, uint8_t *map, const uint8_t *exclude) +static void procfs_scan_listen(int *fd, const char *path, unsigned int lstate, + uint8_t *map, const uint8_t *exclude) { - char *path, *line; struct lineread lr; unsigned long port; unsigned int state; - int *fd; - - if (proto == IPPROTO_TCP) { - fd = &c->proc_net_tcp[ip_version][ns]; - if (ip_version == V4) - path = "/proc/net/tcp"; - else - path = "/proc/net/tcp6"; - } else { - fd = &c->proc_net_udp[ip_version][ns]; - if (ip_version == V4) - path = "/proc/net/udp"; - else - path = "/proc/net/udp6"; - } + char *line;
if (*fd != -1) { if (lseek(*fd, 0, SEEK_SET)) { @@ -74,8 +62,7 @@ static void procfs_scan_listen(struct ctx *c, uint8_t proto, int ip_version, continue;
/* See enum in kernel's include/net/tcp_states.h */
This comment should now be moved before #define UDP_LISTEN and TCP_LISTEN, as it's not otherwise clear where they're coming from.
Good point, fixed.
Given how late I'm reviewing all this, and that presumably you have a bunch of series/patches based on it, I'm also fine to apply this and patch it in a separate commit, or also fix this up on merge myself -- let me know.
Same for the comments to the next patches/series.
Eh, I don't think the rebasing should be too hard, so I'm happy to sort that out. -- David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
Most of our helpers which need to enter the pasta network namespace are
quite specialised. Add one which is rather general - it just open()s a
given file in the namespace context and returns the fd back to the main
namespace. This will have some future uses.
Signed-off-by: David Gibson
On Thu, 5 Oct 2023 14:44:40 +1100
David Gibson
Most of our helpers which need to enter the pasta network namespace are quite specialised. Add one which is rather general - it just open()s a given file in the namespace context and returns the fd back to the main namespace. This will have some future uses.
Signed-off-by: David Gibson
--- util.c | 39 +++++++++++++++++++++++++++++++++++++++ util.h | 1 + 2 files changed, 40 insertions(+) diff --git a/util.c b/util.c index a8f3b35..92ad375 100644 --- a/util.c +++ b/util.c @@ -364,6 +364,45 @@ bool ns_is_init(void) return ret; }
+struct open_in_ns_args {
It would be nice to have the usual kerneldoc-style comment to this (at a first reading: are "flags" the flags for open(2) or something specialised for internal use?).
+ const struct ctx *c; + int fd; + int err; + const char *path; + int flags; +}; + +static int do_open_in_ns(void *arg)
Same for this one. The rest of the series makes sense and looks good to me. -- Stefano
On Thu, Nov 02, 2023 at 07:07:25PM +0100, Stefano Brivio wrote:
On Thu, 5 Oct 2023 14:44:40 +1100 David Gibson
wrote: Most of our helpers which need to enter the pasta network namespace are quite specialised. Add one which is rather general - it just open()s a given file in the namespace context and returns the fd back to the main namespace. This will have some future uses.
Signed-off-by: David Gibson
--- util.c | 39 +++++++++++++++++++++++++++++++++++++++ util.h | 1 + 2 files changed, 40 insertions(+) diff --git a/util.c b/util.c index a8f3b35..92ad375 100644 --- a/util.c +++ b/util.c @@ -364,6 +364,45 @@ bool ns_is_init(void) return ret; }
+struct open_in_ns_args {
It would be nice to have the usual kerneldoc-style comment to this (at a first reading: are "flags" the flags for open(2) or something specialised for internal use?).
+ const struct ctx *c; + int fd; + int err; + const char *path; + int flags; +}; + +static int do_open_in_ns(void *arg)
Same for this one.
Eh, ok. I left it out originally, because they both seemed like they were essentially internals of open_in_ns() itself, but I've added them now.
The rest of the series makes sense and looks good to me.
-- David Gibson | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you. NOT _the_ _other_ | _way_ _around_! http://www.ozlabs.org/~dgibson
procfs_scan_listen() can either use an already opened fd for a /proc/net
file, or it will open it. So, effectively it will open the file on the
first call, then re-use the fd in subsequent calls. However, it's not
possible to open the /proc/net files after we isolate our filesystem in
isolate_prefork(). That means that for each /proc/net file we must call
procfs_scan_listen() at least once before isolate_prefork(), or it won't
work afterwards.
That happens to be the case, but it's a pretty fragile requirement. To
make this more robust, instead always pre-open the /proc files we will need
in get_bounds_port_init() and have procfs_scan_listen() just use those.
Signed-off-by: David Gibson
When we want to scan for bound ports in the namespace we use NS_CALL() to
run get_bound_ports() in the namespace. However, the only thing it
actually needed to be in the namespace for was to open the /proc/net file
it was scanning. Since we now always pre-open those, we no longer need
to switch to the namespace for the actual get_bound_ports() calls.
That in turn means that tcp_port_detect() doesn't need to run in the ns
either, and we can just replace it with inline calls to get_bound_ports().
Signed-off-by: David Gibson
Currently get_bound_ports() takes a parameter to determine if it scans for
UDP or TCP bound ports, but in fact there's almost nothing in common
between those two paths. The parameter appears primarily to have been
a convenience for when we needed to invoke this function via NS_CALL().
Now that we don't need that, split it into separate TCP and UDP versions.
Signed-off-by: David Gibson
Currently we store /proc/net fds used to implement automatic port
forwarding in the proc_net_{tcp,udp} fields of the main context structure.
However, in fact each of those is associated with a particular direction
of forwarding, and we already have struct port_fwd which collects all
other information related to a particular direction of port forwarding.
We can simplify things a bit by moving the /proc fds into struct port_fwd.
Signed-off-by: David Gibson
get_bound_ports_*() now only use their context and ns parameters to
determine which forwarding maps they're operating on. Each function needs
the map they're actually updating, as well as the map for the other
direction, to avoid creating forwarding loops. The UDP function also
requires the corresponding TCP map, to implement the behaviour where we
forward UDP ports of the same number as bound TCP ports for tools like
iperf3.
Passing those maps directly as parameters simplifies the code without
making the callers life harder, because those already know the relevant
maps. IMO, invoking these functions in terms of where they're looking for
updated forwarding also makes more logical sense than in terms of where
they're looking for bound ports. Given that new way of looking at the
functions, also rename them to port_fwd_scan_*().
Signed-off-by: David Gibson
participants (2)
-
David Gibson
-
Stefano Brivio