5

To start, I've read this great article on network namespaces so I know more or less what network namespaces do and how to configure them.

The actual problem I'm facing is this:

  • I want to run some Valve game servers (CS, CS:GO, TF2) on 1 physical machine in LAN mode. So I want every client in the LAN to list all servers in the LAN, because this is the best user experience opposed to manually connecting to an ip:port.
  • The client software looks for LAN servers by broadcasting to ports 27015 - 27020, so 6 ports in total are available to run the server on, otherwise the servers won't be listed in the LAN browser. But, I have more than 6 servers so I need to use more than 1 IP for the same phyical server. The actual plan is to have 1 IP per game.
  • I cannot, and I repeat, cannot tell the game servers to bind to a specific IP as this stops the servers from being listed in the LAN browser at the client sides, even if I tell it explicitly to act like a LAN server.

(People that have been trying to run a CS 1.6, CSGO or TF2 server will probably recognize the "+ip <ip address>" problem here)

By having multiple IP's I cannot solve the problem, because I cannot tell the game server to bind to a specific IP address - the software always takes the primary IP so this can't work. (There is a port clash, or the software takes a port 27020+ which makes the server invisible in the LAN browser)

I want to solve it by using network namespaces - 1 network namespace per game:

  • In the "csgo" network namespace I will run 5 instances of CSGO. (27015 - 27019)
  • In the "tf2" network namespace I will run 1 instance of TF2. (27015)
  • In the "cs16" network namespace I will run 2 instances of CS 1.6. (27015 - 27016)

Because I would be running the game software in a namespace, the software will only see 1 IP and will automatically take that one. (Well, that's what I'm thinking!).

So 4 network namespaces in total ("default", "csgo", "tf2" and "cs16"). The configuration would look like:

- eth0 / 192.168.0.160 ("default" ns, internet access)
- veth0:0 / 192.168.0.161 ("default" ns) <======> veth0:1 / 192.168.0.171 ("csgo" NS)
- veth1:0 / 192.168.0.162 ("default" ns) <======> veth1:1 / 192.168.0.172 ("tf2" NS)
- veth2:0 / 192.168.0.163 ("default" ns) <======> veth2:1 / 192.168.0.173 ("cs16" NS)

Now the question is, can and will this work? If I run the CSGO server software in namespace "csgo", will the public IP of the LAN server then be 192.168.0.171? Or will it be 192.168.0.160? Or maybe 192.168.0.161? As per above, I really need a separate IP address for each game, to make all 9 server appear in the LAN browser.

If not, can this problem actually be solved by using network namespaces?

  • As a side note: these 9 servers won't be populated at the same time, because that would probably cause lag due to limited hardware resources. I just want to limit the server administration on my part while being at this LAN party, which means: I won't be stopping / starting the servers all the time. I just want them to be constantly available so people can just go ahead and play without needing me to start the server of choice. – Timmos Jan 18 '17 at 8:56
  • You also need to keep in mind that the network spaces are isolated from each other, so you need to create veth pairs from each network space into the global space, and the you need to forward all the broadcasts etc. That's possible, but personally I like the solution with modified bind a lot better. :-) – dirkt Jan 18 '17 at 12:01
  • Pls see my long edit... – MariusMatutiae Jan 19 '17 at 11:52
4

Binding applications to a specific IP address is a notoriously difficult problem: not all applications are as kind as ssh which allows you to specify the IP address you want to bind to by means of the -b option. For instance, Firefox and Chrome are notoriously impervious to this.

Luckily, there is a solution: this guy has modified the bind.so system library to allow one to specify the binding address on the command line, as follows:

$ BIND_ADDR="192.0.2.100" LD_PRELOAD=/usr/lib/bind.so firefox

By preloading the bind shared object, you bypass the system version which chooses the interface to bind to differently.

This is a heck of a lot easier and lighter on system resources than running multiple network spaces simultaneously.

The Web page above gives both instructions on how to compile the module and this link to pre-compiled 32- and 64-bit versions.

(Just for reference: I know you are not interested, but the code can be easily modified to force binding to a specific port).

EDIT:

I completely forgot that games would most likely use UDP, while the trick above only works for TCP connections. I am leaving my answer in place, in the hope of helping someone with TCP problems of this sort, but as an answer to Timmos this is completely useless.

To make up for my mistake, I am passing you a (very simple!) script I wrote which sets up one of (possibly many) network namespaces.

#!/bin/bash
#
# This script will setup a network namespace with a macvlan
# which obtains its IP address via dhclient from the LAN on which the host is
# placed
#

set -x

# It will open an xterm window in the new network namespace; if anything
# else is required, change the statement below.

export XTERM1=xterm

# The script will temporarily activate ip forwarding for you. If you
# do not wish to retain this feature, you will have to issue, at the 
# end of this session, the command
# echo 0 > /proc/sys/net/ipv4/ip_forward 
# yourself. 

###############################################################################

export WHEREIS=/usr/bin/whereis

# First of all, check that the script is run by root:

[ "root" != "$USER" ] && exec sudo $0 "$@"

if [ $# != 2 ]; then 
    echo "Usage $0 name action"
    echo "where name is the network namespace name,"
    echo " and action is one of start| stop| reload."
    exit 1
fi

# Do we have all it takes?

IERROR1=0
IERROR2=0
IERROR3=0

export IP=$($WHEREIS -b ip | /usr/bin/awk '{print $2}')
export IPTABLES=$($WHEREIS -b iptables | /usr/bin/awk '{print $2}')
export XTERM=$($WHEREIS -b $XTERM1 | /usr/bin/awk '{print $2}')

if [ "x$IP" = "x" ] ; then
    echo "please install the iproute2 package"
    IERROR1=1
fi

if [ "x$IPTABLES" = "x" ] ; then
    echo "please install the iptables package"
    IERROR2=1
fi

if [ "x$XTERM" = "x" ] ; then
    echo "please install the xterm package"
    IERROR3=1
fi

if [[ $IERROR1 == 0 && $IERROR2 == 0 && $IERROR3 == 0 ]] 
then
    :   
else
    exit 1
fi


prelim() {

# Perform some preliminary setup. First, clear the proposed 
# namespace name of blank characters; then create a directory
# for logging info, and a pid file in it; lastly, enable IPv4 
# forwarding. 

    VAR=$1
    export NNSNAME=${VAR//[[:space:]]}

    export OUTDIR=/var/log/newns/$NNSNAME

    if [ ! -d $OUTDIR ]; then
        /bin/mkdir -p $OUTDIR
    fi
    export PID=$OUTDIR/pid$NNSNAME

    echo 1 > /proc/sys/net/ipv4/ip_forward

}

start_nns() {

# Check whether a namespace with the same name already exists. 

    $IP netns list | /bin/grep $1 2> /dev/null
    if [ $? == 0 ]; then 
        echo "Network namespace $1 already exists,"
        echo "please choose another name"
        exit 1
    fi

# Here we take care of DNS

    /bin/mkdir -p /etc/netns/$1
    echo "nameserver 8.8.8.8" > /etc/netns/$1/resolv.conf
    echo "nameserver 8.8.4.4" >> /etc/netns/$1/resolv.conf

# The following creates the new namespace, and the macvlan interface

    $IP netns add $1
    $IP link add link eth0 mac$1 type macvlan mode bridge

# This assigns the macvlan interface, mac$1, to the new 
# namespace, asks for an IP address via a call to dhclient,
# brings up this and the (essential) lo interface, 
# creates a new terminal in the new namespace and 
# stores its pid for the purpose of tearing it cleanly, later. 

    $IP link set mac$1 netns $1
    $IP netns exec $1 /sbin/dhclient -v mac$1 1> /dev/null 2>&1
    $IP netns exec $1 $IP link set dev lo up
    $IP netns exec $1 su -c $XTERM $SUDO_USER &
    $IP netns exec $1 echo "$!" > $PID


}

stop_nns() {

# Check that the namespace to be torn down really exists

    $IP netns list | /bin/grep $1 2>&1 1> /dev/null
    if [ ! $? == 0 ]; then 
        echo "Network namespace $1 does not exist,"
        echo "please choose another name"
        exit 1
    fi

# This kills the terminal in the separate namespace and
# removes the file and the directory where it is stored.

    /bin/kill -TERM $(cat $PID) 2> /dev/null 1> /dev/null
    /bin/rm $PID
    /bin/rmdir $OUTDIR  
    $IP netns del $1

# This deletes the file and direcotory connected with the DNSes. 

    /bin/rm /etc/netns/$1/resolv.conf
    /bin/rmdir /etc/netns/$1

}


case $2 in
    start)
        prelim "$1"
        start_nns $NNSNAME
        ;;
    stop)
        prelim "$1"
        stop_nns $NNSNAME
        ;;
    reload)
        prelim "$1"
        stop_nns $NNSNAME
        prelim "$1"
        start_nns $NNSNAME
        ;;
    *) 
# This removes the absolute path from the command name

        NAME1=$0
        NAMESHORT=${NAME1##*/}

        echo "Usage:" $NAMESHORT "name action,"
        echo "where name is the name of the network namespace,"
        echo "and action is one of start|stop|reload"
        ;;
esac

It assumes your main interface is called eth0 (if yours is called differently, change the single reference to it accordingly), and uses macvlan interfaces, which means you can use the script only with an ethernet connection. Also, it does not need to use bridges.

You start/stop a separate network namespace as follows (I call the script nns, but you can call it whatever you like):

nns network_namespace_1 start
nns network_namespace_2 stop

You can have as many different network namespaces as your local DHCP server allows, since each macvlan interface gets an IP address from your LAN DHCP server. If a network namespace with the same name already exists, you will have to pick a different name.

All network namespaces can talk to each other, courtesy of the mode bridge option in their creation command. The script opens an xterm terminal in the new network namespace (I like xterm, if you do not you can change that at the top of the script) so that, from within the xterm you can start your applications.

I left the debugging option, set -x, in the script, which may help you iron out some initial problem. When done, just remove that line.

Cheers.

  • This looks promising and plain simple. I'll try it tonight. About your port comment, wouldn't this interfere with the server software choosing the port itself? By default it chooses a port in the range 27015-27020, so what would be the result if you do an external bind to a different port? Am I right in the assumption that a "port bind" in this case would limit the port range that the OS offers to the game server software - and it's up to the software to handle the situation? – Timmos Jan 18 '17 at 12:36
  • @Timmos It would certainly interfere, that's why I mentioned in parentheses and stated that it is not your case. You are quite right. – MariusMatutiae Jan 18 '17 at 13:21
  • I did the steps and compiled it myself, used it to start the process, but the IP is still the first one (x.x.x.160) instead of the one I feed to the envvar BIND_ADDR (x.x.x.161). The page mentions it only supports TCP, not UDP, maybe that's the problem. – Timmos Jan 19 '17 at 8:40
  • 2
    @Timmos No, that is certainly the problem. I completely forgot that games most likely used UDP. My bad, apologies for wasting your time. – MariusMatutiae Jan 19 '17 at 8:45
  • 2
    @Timmos: they are simpler, you do not need to configure both ends nor do you need to tell your kernel about the new route for a single IP address (normally, you have ip route add 192.168.0.0/24 dev eth0, but if your NNS has IP address in the same broadcast domai , say 192.168.0.71, your host will need to know that: ip route add 192.168.0.71/32 dev veth0). Also, initially veth IFs has to be bridged (with, for instance, a tap IF), which also complicated matters. Nothing is simpler than a macvlan, you can also assign it an ad hoc MAC address if you want. – MariusMatutiae Feb 6 '17 at 8:50

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.