Назад | Перейти на главную страницу

Как настроить сетевое пространство имен Linux, которое разрешает широковещательную рассылку UDP

Я пытаюсь использовать 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 вверх, и сценарий успешно выполняется.

Что ж, есть ряд причин, по которым это не работает.

  1. Вы создаете пару veth, а затем не можете добавить одну ее сторону в новое сетевое пространство имен.
  2. Одна из сторон ветки не поднята.
  3. Указание широковещательного адреса как 255.255.255.255 как в вашем примере, вызовите поиск в таблице маршрутизации и отправьте пакет по маршруту по умолчанию.
  4. Следовательно, вы не используете 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))