0

Let's assume I have some executable foo and I have bar. Both are hard-coded to listen on port 80. Let's assume we have root.

How can I get both running on different ports? Or one of them? You can assume I don't have the source code.

I can think of obviously the following ways:

  1. Run it on a VM; map the host port to this
  2. Similar, run it on a container (Docker etc) and have the host mapping point to the internal
  3. Could you hook the bind syscall with ptrace or something like that?
  4. Run a hex search for 80 in binary and modify the parameter going into bind

Can you do this with network namespaces a la Docker?

1
2

With the network namespace feature of Linux since about kernel 2.6.29 it is possible to run a program in its own network space that can be constructed with its own links, addresses and routing and can also be connected to the main network space of the host through routing. This can be done with userspace programs without docker or other container frameworks.

So if you want to run a program that listens on all interfaces to a port that is also used by another program, then you can run it simultaneously on its own interface.

For example, setup a network namespace called island. Add an interface veth-island and put it in a 10.1.0.0 network. Connect its virtual ethernet interface to a peer virtual interface veth-host in the main network space.

ip netns add island
ip link add veth-island type veth peer name veth-host
ip link set veth-island netns island
ip netns exec island ip link set veth-island up
ip netns exec island ip link set lo up
ip link set veth-host up
ip netns exec island ip address add 10.1.0.2/16 dev veth-island
ip addr add 10.1.0.1/16 dev veth-host

Now the island namespace has its own loopback interface and an ethernet interface called veth-island with address 10.1.0.2, which can reach veth-host on 10.1.0.1.

You can check with:

ip netns exec island ip address show

From the main host you can reach 10.1.0.2 and everything that listens on it.

So if you have a program that listens to port 8080 on all interfaces, then you can start it with:
ip netns exec island program arguments, and in the main host you can connect to it on 10.1.0.2 port 8080, and nowhere else.

You can do ip netns exec island netstat -plnt and you will see the program listening on all interfaces in namespace island.

Without using iptables this is about as far as you can get. The main limitation is that the program in the island namespace cannot do outbound connections to the outside world and name resolving. Changing this may not be what you want on a production machine, because routing makes subtle changes to the way which ip features are accepted or not.

So basically to setup the outbound functions (replace eth0 with your outward interface):

#Activate router functions
echo 1 > /proc/sys/net/ipv4/ip_forward
#Set a gateway for the island namespace
ip netns exec island ip route add default via 10.1.0.1
#Masquerade outgoing connections (you can limit to tcp with `-p tcp`)
iptables -t nat -A POSTROUTING -s 10.1.0.0/16 -o eth0 -j MASQUERADE
#Let packets move from the outward interface to the virtual ethernet pair and vice versa
iptables -A FORWARD -i eth0 -o veth-host -j ACCEPT
iptables -A FORWARD -o eth0 -i veth-host -j ACCEPT
#Setup a resolver (replace with your own DNS, does not work with a loopback resolver)
mkdir -p /etc/netns/island
echo nameserver dns-ip > /etc/netns/island/resolv.conf
#Maybe give it its own hosts file, to do edits
cp /etc/hosts /etc/netns/island/hosts

Now you can test with ip netns exec island ping example.com

You could use something like socat to map the server to a chosen port in the main namespace.

socat tcp-listen:8080,reuseaddr,fork tcp-connect:10.1.0.2:80

Or you could use iptables as a TCP proxy of sorts. Which involves a sysctl setting also.

# Proxy connections on localhost at a given port of main namespace to a chosen port on other namespace
# You have to set a sysctl and SNAT if you want to connect to loopback in main namespace and get packets returned from island namespace
# This sets up DNAT for packets output from a locally executed program in the main namespace
iptables -t nat -A OUTPUT -o lo -p tcp -m tcp --dport 8080 -j DNAT --to-destination 10.1.0.2:80
# You can omit SNAT if you connect to any interface different from loopback
iptables -t nat -A POSTROUTING -d 10.1.0.2/32 -p tcp -m tcp --dport 80 -j SNAT --to-source 10.1.0.1
# Allow routing of localhost output packets to veth-host (only needed for connections on loopback)
sysctl -w net.ipv4.conf.veth-host.route_localnet=1

The iptables solution is tricky, because netstat will not show you anything listening in the main namespace for the DNAT'ed port. You could start a program that binds to the same port in the main namespace. With the commands given, that would mean for example that connections from outside would reach the program listening on the port in the main namespace, but a locally executed program in the main namespace would connect to the DNAT'ed port in the island namespace. If you want to connect from outside you would have to add a PREROUTING DNAT rule. Just saying, if more persons work on this machine, there could be confusion.

Caveat. For some applications virtual ethernet interfaces can be unreliable. I ran into this once with a VPN application. It worked of sorts, but unworkably slow. Could be ipsec, could be other transport layering.

1

Linux programs rarely use system calls directly. Usually there is a libc function with the same name. There is a standard way to override libc (and any other dynamic library) functions:

  • You write a small dynamic library that redefines the function you want (e.g. bind). Inside this library you can access the previous function definition with dlsym(RLTD_NEXT, "function_name").
  • You add your dynamic library to the LD_PRELOAD environment variable.

For your specific problem, you can create a file libaltbind.c:

#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>

int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen);
typedef int bindfn_type(int fd, const struct sockaddr *addr, socklen_t addrlen);

static bindfn_type* real_bind = find_bind;

int find_bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
  bindfn_type* found;
  found = (bindfn_type*) dlsym(RTLD_NEXT, "bind");
  if (found == NULL) {
    fprintf(stderr, "Didn't find the real bind().\n");
    return -1;
  }
  real_bind = found;
  return real_bind(fd,addr,addrlen);
}

int bind(int fd, const struct sockaddr *addr, socklen_t addrlen) {
  if (addr->sa_family == AF_INET) {
    struct sockaddr_in *addr_in = (struct sockaddr_in*) addr;
    if (ntohs(addr_in->sin_port) == 80) {
      char* env = getenv("ALT_PORT");
      if (env != NULL) {
    int new_port = atoi(env);
    if (new_port != -1) {
      addr_in->sin_port = htons(new_port);
    }
      }
    }
  }
  return real_bind(fd, addr, addrlen);
}

and turn it into a dynamic library with:

gcc -c -fPIC -Wall -Werror -o libaltbind.o libaltbind.c
gcc -shared -ldl -o libaltbind.so libaltbind.o

Now, all you have to do to bind your program to port 8080 instead of 80 is to run:

ALT_PORT=8080 LD_PRELOAD=/full/path/to/libaltbind.so program
6
  • This is a very nice solution I hadn't considered. Of course, not everything uses libc but many things do. – Vaughan Hilts Feb 12 '20 at 20:43
  • Almost all programs dynamically linked programs use libc. I don't know any real program, which doesn't. – Piotr P. Karwasz Feb 12 '20 at 20:58
  • There's musl and friends, right? – Vaughan Hilts Feb 12 '20 at 21:00
  • In this sense this solution will work with any implementation of the C library, since LD_PRELOAD is handled by the dynamic linker ld.so. It won't work if the program performs a direct system call. – Piotr P. Karwasz Feb 12 '20 at 21:06
  • Does this work if the wrapped program is statically linked? – Gerrit Feb 12 '20 at 22:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.