首先,我读过这篇关于网络命名空间的精彩文章所以我或多或少知道网络命名空间的作用以及如何配置它们。
我面临的实际问题是:
- 我想在一台物理机上以 LAN 模式运行一些 Valve 游戏服务器(CS、CS:GO、TF2)。所以我希望 LAN 中的每个客户端都列出 LAN 中的所有服务器,因为与手动连接到 ip:port 相比,这是最好的用户体验。
- 客户端软件通过广播到端口 27015 - 27020 来查找 LAN 服务器,因此总共有 6 个端口可用于运行服务器,否则服务器将不会在 LAN 浏览器中列出。但是,我有超过 6 台服务器,因此我需要为同一台物理服务器使用超过 1 个 IP。实际计划是每场比赛有1个IP。
- 我不能,我再说一遍,不能告诉游戏服务器绑定到特定的 IP,因为这会阻止服务器在客户端的 LAN 浏览器中列出,即使我明确告诉它像 LAN 服务器一样运行。
(尝试运行 CS 1.6、CSGO 或 TF2 服务器的人可能会认识到这里的“+ip <ip 地址>”问题)
通过拥有多个 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)
现在的问题是,这能行吗?如果我在命名空间“csgo”中运行CSGO服务器软件,那么LAN服务器的公共IP是否是192.168.0.171?还是 192.168.0.160?或者也许是 192.168.0.161?如上所述,我确实需要为每个游戏分配一个单独的 IP 地址,以使所有 9 个服务器都出现在 LAN 浏览器中。
如果不是,这个问题真的可以通过使用网络命名空间来解决吗?
答案1
将应用程序绑定到特定 IP 地址是一个众所周知的难题:并非所有应用程序都像SSH它允许您通过以下方式指定要绑定的IP地址-b选项。例如,Firefox 和 Chrome 就不受此影响。
幸运的是,有一个解决方案:这家伙已修改绑定文件系统库允许在命令行上指定绑定地址,如下所示:
$ BIND_ADDR="192.0.2.100" LD_PRELOAD=/usr/lib/bind.so firefox
通过预加载绑定共享对象,您可以绕过选择要以不同方式绑定的接口的系统版本。
这比同时运行多个网络空间要容易得多,而且对系统资源的占用要小得多。
上面的网页给出了如何编译模块的说明以及这个链接预编译的 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
它假设您的主界面被调用以太网0(如果您的调用方式不同,请相应地更改对它的单个引用),并使用麦克夫兰接口,这意味着您可以使用脚本仅有的有以太网连接。此外,它不需要使用网桥。
您可以按如下方式启动/停止一个单独的网络命名空间(我将脚本称为神经网络,但你可以随意称呼它):
nns network_namespace_1 start
nns network_namespace_2 stop
您可以拥有本地 DHCP 服务器允许的任意数量的不同网络命名空间,因为每个 macvlan 接口都会从 LAN DHCP 服务器获取 IP 地址。如果已存在同名的网络命名空间,则必须选择不同的名称。
所有网络名称空间都可以相互通信,这由模式桥他们的创建命令中的选项。该脚本打开一个xterm新网络命名空间中的终端(我喜欢xterm,如果不这样做,您可以在脚本顶部更改它),以便从 xterm 中启动您的应用程序。
我离开了调试选项,设置-x,在脚本中,这可能会帮助您解决一些初始问题。完成后,只需删除该行即可。
干杯。