网络命名空间和公共 IP

Tim*_*mos 5 networking namespace

首先,我阅读了这篇关于网络命名空间的精彩文章,因此我或多或少知道网络命名空间的作用以及如何配置它们。

我面临的实际问题是:

  • 我想在一台物理机上以 LAN 模式运行一些 Valve 游戏服务器(CS、CS:GO、TF2)。所以我希望局域网中的每个客户端都列出局域网中的所有服务器,因为这是最好的用户体验,而不是手动连接到 ip:port。
  • 客户端软件通过向端口 27015 - 27020 广播来查找 LAN 服务器,因此总共有 6 个端口可用于运行服务器,否则服务器将不会在 LAN 浏览器中列出。但是,我有 6 台以上的服务器,所以我需要为同一个物理服务器使用 1 个以上的 IP。实际计划是每个游戏有 1 个 IP。
  • 我不能,我再说一遍,不能告诉游戏服务器绑定到特定的 IP,因为这会阻止服务器在客户端的 LAN 浏览器中列出,即使我明确告诉它像 LAN 服务器一样工作。

(一直在尝试运行 CS 1.6、CSGO 或 TF2 服务器的人可能会在这里识别出“+ip <ip address>”问题)

通过拥有多个 IP,我无法解决问题,因为我无法告诉游戏服务器绑定到特定的 IP 地址 - 软件总是采用主 IP,因此这无法正常工作。(端口冲突,或者软件占用了27020+端口,导致服务器在局域网浏览器中不可见)

我想通过使用网络命名空间来解决它 - 每个游戏 1 个网络命名空间:

  • 在“csgo”网络命名空间中,我将运行 5 个 CSGO 实例。(27015 - 27019)
  • 在“tf2”网络命名空间中,我将运行 1 个 TF2 实例。(27015)
  • 在“cs16”网络命名空间中,我将运行 2 个 CS 1.6 实例。(27015 - 27016)

因为我将在命名空间中运行游戏软件,所以该软件只会看到 1 个 IP,并会自动获取该 IP。(嗯,这就是我的想法!)。

所以总共有 4 个网络命名空间(“default”、“csgo”、“tf2”和“cs16”)。配置如下:

- 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)
Run Code Online (Sandbox Code Playgroud)

现在的问题是,这能而且会奏效吗?如果我在命名空间“csgo”中运行CSGO服务器软件,那么局域网服务器的公共IP是否为192.168.0.171?还是会是 192.168.0.160?或者也许是 192.168.0.161?如上所述,我真的需要为每个游戏分配一个单独的 IP 地址,以使所有 9 个服务器都出现在 LAN 浏览器中。

如果不是,这个问题真的可以通过使用网络命名空间来解决吗?

Mar*_*iae 5

将应用程序绑定到特定的 IP 地址是一个众所周知的难题:并非所有应用程序都像ssh 一样,它允许您通过-b选项指定要绑定到的 IP 地址。例如,众所周知,Firefox 和 Chrome 不受此影响。

幸运的是,有一个解决方案:这家伙修改了bind.so系统库,允许在命令行中指定绑定地址,如下:

$ BIND_ADDR="192.0.2.100" LD_PRELOAD=/usr/lib/bind.so firefox
Run Code Online (Sandbox Code Playgroud)

通过预加载绑定共享对象,您可以绕过选择接口以不同方式绑定的系统版本。

与同时运行多个网络空间相比,这在系统资源上更容易、更轻量。

上面的网页给出了如何编译模块和两个指令此链接预编译的32位和64位版本。

(仅供参考:我知道您不感兴趣,但可以轻松修改代码以强制绑定到特定端口)。

编辑

我完全忘记了游戏很可能会使用 UDP,而上面的技巧仅适用于 TCP 连接。我将我的答案留在原处,希望能帮助有此类 TCP 问题的人,但作为对 Timmos 的回答,这完全没用。

为了弥补我的错误,我向您传递了一个(非常简单!)我编写的脚本,该脚本设置了(可能是多个)网络命名空间之一。

#!/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
Run Code Online (Sandbox Code Playgroud)

它假设您的主接口称为eth0(如果您的主接口被称为不同,请相应地更改对它的单个引用),并使用macvlan接口,这意味着您只能通过以太网连接使用该脚本。此外,它不需要使用网桥。

您可以按如下方式启动/停止单独的网络命名空间(我将脚本称为nns,但您可以随意称呼它):

nns network_namespace_1 start
nns network_namespace_2 stop
Run Code Online (Sandbox Code Playgroud)

您可以在本地 DHCP 服务器允许的范围内拥有尽可能多的不同网络命名空间,因为每个 macvlan 接口都从您的 LAN DHCP 服务器获取 IP 地址。如果已存在同名的网络命名空间,则必须选择不同的名称。

所有网络命名空间都可以相互通信,这要归功于其创建命令中的模式桥接选项。该脚本在新的网络命名空间中打开一个xterm终端(我喜欢xterm,如果您不喜欢,您可以在脚本顶部更改它),以便从 xterm 中启动您的应用程序。

我在脚本中保留了调试选项set -x,这可能会帮助您解决一些初始问题。完成后,只需删除该行。

干杯。

  • @Timmos 不,那是**肯定**的问题。我完全忘记了游戏很可能使用 UDP。我的错,很抱歉浪费你的时间。 (2认同)
  • @Timmos:它们更简单,您不需要配置**两端**,也不需要告诉内核有关单个 IP 地址的新路由(通常,您有 *ip route add 192.168.0.0/24 dev eth0*,但如果您的 NNS 在同一个广播域中具有 IP 地址,例如 *192.168.0.71*,您的主机将需要知道:**ip route add 192.168.0.71/32 dev veth0**)。此外,最初*veth* IF 必须被桥接(例如,使用*tap* IF),这也使事情变得复杂。没有什么比 *macvlan* 更简单的了,如果需要,您还可以为其分配一个 *ad hoc* MAC 地址。 (2认同)