Я пытаюсь использовать ip netns
семейство команд в Linux для создания сетевого пространства имен, в котором я могу запускать программу, использующую широковещательную рассылку UDP. Мне не нужен доступ в Интернет или какой-либо интерфейс в корневом пространстве имен (но если это необходимо для работы, это определенно приемлемо).
Вот пример сервера и клиента на Ruby (протестирован с Ruby 1.9.3, но я ожидаю, что он будет работать и в других версиях):
#! /usr/bin/env ruby
require 'socket'
PORT = 5000
case ARGV[0]
when 'server'
soc = UDPSocket.open
begin
soc.bind('', PORT)
puts "SERVER #{Process.pid} listening on #{PORT}"
msg = soc.recv(1)
puts "SERVER got msg: #{msg}"
ensure
soc.close
end
when 'client'
soc = UDPSocket.open
begin
soc.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
puts "CLIENT sending message"
soc.send('m', 0, '<broadcast>', PORT)
ensure
soc.close
end
else
abort "usage: #{$0} {server | client}"
end
Он создает либо сервер, либо клиент. Сервер слушает 0.0.0.0
интерфейс (soc.bind('', ...)
). Клиент отправляет сообщение на широковещательный адрес (soc.send(..., ..., '<broadcast>', ...)
).
При запуске в корневом пространстве имен кажется, что он работает правильно:
$ ./udp-broadcast.rb server & sleep 0.5 && sudo netstat --listen --udp -p | grep 5000 && ./udp-broadcast.rb client
SERVER 22981 listening on 5000
udp 0 0 *:5000 *:* 22981/ruby
CLIENT sending message
SERVER got msg: m
Вот сценарий, в котором я пытаюсь создать новое сетевое пространство имен и запустить те же команды:
#!
set -e
NS=udp-broadcast-test
nsexec="ip netns exec $NS"
ip netns add $NS
trap "ip netns delete $NS" EXIT
$nsexec ip link set lo up
# Can loopback have a broadcast address?
# $nsexec ip link set lo broadcast 255.255.255.255
# RTNETLINK answers: Invalid argument
# $nsexec ip addr add broadcast 255.255.255.255 dev lo
# RTNETLINK answers: Invalid argument
$nsexec ip link add veth0 type veth peer name veth1
$nsexec ifconfig veth0 192.168.99.1/24 up
$nsexec ip link
$nsexec ip route
$nsexec ifconfig
timeout 2s $nsexec ./udp-broadcast.rb server &
sleep 0.2
$nsexec netstat -n --udp --listen -p
timeout 2s $nsexec ./udp-broadcast.rb client
wait
При запуске он дает следующий вывод:
$ sudo ./netns.sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: veth1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether e2:a1:c4:14:c4:5e brd ff:ff:ff:ff:ff:ff
3: veth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
link/ether a6:2f:84:9f:08:36 brd ff:ff:ff:ff:ff:ff
192.168.99.0/24 dev veth0 proto kernel scope link src 192.168.99.1
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
veth0 Link encap:Ethernet HWaddr a6:2f:84:9f:08:36
inet addr:192.168.99.1 Bcast:192.168.99.255 Mask:255.255.255.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
SERVER 23320 listening on 5000
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
udp 0 0 0.0.0.0:5000 0.0.0.0:* 23320/ruby
CLIENT sending message
./udp-broadcast.rb:23:in `send': Network is unreachable - sendto(2) (Errno::ENETUNREACH)
from ./udp-broadcast.rb:23:in `<main>'
Теперь, если я изменю адрес, который прослушивает сервер и на который клиент отправляет сообщение, на 192.168.99.1
, то сообщение доходит, поэтому я знаю свой veth0
хотя бы частично работает.
Как я могу настроить так, чтобы широковещательное сообщение проходило? Код сервера / клиента извлекается из более крупной базы кода и его нелегко изменить, поэтому единственное, что я могу изменить, - это конфигурацию моей сети.
Эта конкретная проблема решается путем добавления маршрута по умолчанию в veth0
:
$nsexec ip route add default via 192.168.99.1 dev veth0
Добавьте эту строку сразу после строки, которая приносит veth0
вверх, и сценарий успешно выполняется.
Что ж, есть ряд причин, по которым это не работает.
255.255.255.255
как в вашем примере, вызовите поиск в таблице маршрутизации и отправьте пакет по маршруту по умолчанию.SO_BINDTODEVICE
чтобы указать, на какой интерфейс вы действительно хотите отправить. Обратите внимание, что для этого требуются привилегии root, что во многих случаях не идеально.Кроме того, вы не установили никаких отношений маршрутизации между дочерним пространством имен и родительским пространством имен, поэтому он даже не будет работать с прямым пингом хоста.
В общем, использование универсального широковещательного адреса для чего-либо, кроме предоставления основных сетевых услуг, не является хорошей практикой. Вы должны использовать широковещательный адрес подсети, к которой вы стремитесь.
Я получил все, о чем вы упомянули, работая над подготовкой сетевого пространства имен.
# ip netns add TEST
# ip link add veth0 type veth peer name veth1
# ip link set dev veth1 netns TEST
# ip link set dev veth0 up
# ip netns exec TEST ip link set dev veth1 up
# ip netns exec TEST ip addr add 10.10.10.10/32 dev veth1
# ip route add 10.10.10.10/32 dev veth0
# ip netns exec TEST ip route add 192.168.1.3/32 dev veth1
# ping -c1 10.10.10.10
PING 10.10.10.10 (10.10.10.10) 56(84) bytes of data.
64 bytes from 10.10.10.10: icmp_seq=1 ttl=64 time=0.202 ms
--- 10.10.10.10 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.202/0.202/0.202/0.000 ms
Вот используемый сценарий. Обратите внимание на SO_BINDTODEVICE
вызов..
#!/usr/bin/python
import socket as sock
import sys, time, os
if __name__ == "__main__":
if sys.argv[1] == "server":
s = sock.socket(sock.AF_INET, sock.SOCK_DGRAM)
s.bind(('0.0.0.0', 50000))
data = s.recvfrom(50)
print "Got {0}".format(data)
elif sys.argv[1] == "client":
s = sock.socket(sock.AF_INET, sock.SOCK_DGRAM)
s.setsockopt(sock.SOL_SOCKET, sock.SO_BROADCAST, 1)
s.setsockopt(sock.SOL_SOCKET, sock.SO_BINDTODEVICE, "veth0")
s.connect(('255.255.255.255', 50000))
s.send("hello world\n")
А потом результат ..
# ip netns exec TEST python test.py server &
[1] 24961
# python test.py client
Got ('hello world\n', ('192.168.1.3', 41971))