Espaços para nome de rede e IP público

5

Para começar, li este excelente artigo sobre namespaces de rede para que eu saiba mais ou menos o que os namespaces de rede fazem e como configurá-los.

O problema real que estou enfrentando é o seguinte:

  • Eu quero rodar alguns servidores de jogos da Valve (CS, CS: GO, TF2) em uma máquina física no modo LAN. Por isso, quero que todos os clientes na LAN listem todos os servidores na LAN, porque essa é a melhor experiência do usuário, em vez de se conectar manualmente a um ip: port.
  • O software cliente procura servidores LAN por meio da transmissão para as portas 27015 - 27020, portanto, há 6 portas no total disponíveis para executar o servidor, caso contrário, os servidores não serão listados no navegador da LAN. Mas, eu tenho mais de 6 servidores, então eu preciso usar mais de 1 IP para o mesmo servidor phyical. O plano real é ter 1 IP por jogo.
  • Eu não posso, e repito, não posso dizer aos servidores de jogos para se ligarem a um IP específico, pois isso impede que os servidores sejam listados no navegador da LAN nos lados do cliente, mesmo que eu diga isso explicitamente para agir como um servidor de LAN .

(As pessoas que tentam executar um servidor CS 1.6, CSGO ou TF2 provavelmente reconhecerão o problema "+ ip < ip address >" aqui)

Por ter vários IPs, não consigo resolver o problema, porque não posso dizer ao servidor de jogos para vincular a um endereço IP específico - o software sempre usa o IP principal para que isso não funcione. (Há um conflito de portas ou o software usa uma porta 27020+ que torna o servidor invisível no navegador da LAN)

Eu quero resolvê-lo usando namespaces de rede - 1 namespace de rede por jogo:

  • No namespace da rede "csgo", executarei 5 instâncias do CSGO. (27015 - 27019)
  • No namespace da rede "tf2", executarei uma instância do TF2. (27015)
  • No namespace de rede "cs16", executarei 2 instâncias do CS 1.6. (27015 - 27016)

Como eu estaria executando o software do jogo em um namespace, o software só verá 1 IP e o levará automaticamente. (Bem, é isso que eu estou pensando!)

Portanto, 4 namespaces de rede no total ("padrão", "csgo", "tf2" e "cs16"). A configuração seria semelhante:

- 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)

Agora a questão é: pode e vai funcionar? Se eu executar o software do servidor CSGO no namespace "csgo", o IP público do servidor da LAN será 192.168.0.171? Ou será 192.168.0.160? Ou talvez 192.168.0.161? Como acima, eu realmente preciso de um endereço IP separado para cada jogo, para que todos os 9 servidores apareçam no navegador da LAN.

Se não, este problema pode realmente ser resolvido usando namespaces de rede?

    
por Timmos 18.01.2017 / 09:29

1 resposta

4

A vinculação de aplicativos a um endereço IP específico é um problema notoriamente difícil: nem todos os aplicativos são tão bons quanto ssh que permite especificar o endereço IP ao qual você deseja vincular por meio do -b opção. Por exemplo, o Firefox e o Chrome são notoriamente impermeáveis a isso.

Por sorte, há uma solução: esse cara modificou a biblioteca do sistema bind.so para permitir que um especifique o endereço de ligação na linha de comando, da seguinte forma:

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

Ao pré-carregar o objeto compartilhado bind , você ignora a versão do sistema que escolhe a interface a ser vinculada de forma diferente.

Isso é muito mais fácil e leve em recursos do sistema do que executar vários espaços de rede simultaneamente.

A página da Web acima fornece as duas instruções sobre como compilar o módulo e este link para pré- versões compiladas de 32 e 64 bits.

(Apenas para referência: eu sei que você não está interessado, mas o código pode ser facilmente modificado para forçar a ligação a uma porta específica ).

EDITAR :

Esqueci completamente que os jogos provavelmente usariam o UDP, enquanto o truque acima só funciona para conexões TCP. Estou deixando minha resposta no lugar, na esperança de ajudar alguém com problemas TCP desse tipo, mas como resposta a Timmos isso é completamente inútil.

Para compensar meu erro, estou passando a você um script (muito simples!) que escrevi e que configura um dos (possivelmente muitos) namespaces de rede.

#!/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

Ele assume que sua interface principal é chamada eth0 (se a sua é chamada de forma diferente, altere a referência única para ela de acordo), e usa interfaces macvlan , o que significa que você pode use o script somente com uma conexão ethernet. Além disso, não precisa usar pontes.

Você inicia / interrompe um namespace de rede separado da seguinte maneira (eu chamo o script nns , mas você pode chamá-lo como quiser):

nns network_namespace_1 start
nns network_namespace_2 stop

Você pode ter tantos namespaces de rede diferentes quanto o seu servidor DHCP local permitir, já que cada interface do macvlan obtém um endereço IP do seu servidor DHCP da LAN. Se um namespace de rede com o mesmo nome já existir, você terá que escolher um nome diferente.

Todos os namespaces de rede podem se comunicar, cortesia da opção modo bridge em seu comando de criação. O script abre um terminal xterm no novo namespace de rede (eu gosto xterm , se você não pode mudar isso no topo do script) para que, de dentro o xterm você pode iniciar seus aplicativos.

Eu deixei a opção de depuração, set -x , no script, o que pode ajudá-lo a resolver algum problema inicial. Quando terminar, basta remover essa linha.

Felicidades.

    
por 18.01.2017 / 10:28