Два дня борюсь с этой проблемой.
Предположения:
Что я хочу:
Что пробовал:
-A POSTROUTING -s 172.18.0.0/16 ! -o docknet -j MASQUERADE
iptables -t mangle -I PREROUTING -s 172.18.0.0/16 -j MARK --set-mark 1
ip rule add from all fwmark 1 table 2
Где таблица 2:
default via 192.168.2.1 dev eth1 proto static
При такой настройке, когда я пытаюсь выполнить эхо-запрос 8.8.8.8 из контейнера (172.18.0.2), подключенного к док-сети, происходит следующее:
отсюда должен произойти обратный перевод с 192.168.2.1 на 172.168.0.2, но работает tcpdump -i any host 8.8.8.8
об этом переводе не осталось и следа
Я проверил также conntrack -L, и вот результат:
icmp 1 29 src = 172.18.0.2 dst = 8.8.8.8 type = 8 code = 0 id = 9 src = 8.8.8.8 dst = 192.168.2.1 type = 0 code = 0 id = 9 mark = 0 use = 1
Полезная информация:
curl --interface eth1 ipinfo.io
работает как ожидалосьРЕДАКТИРОВАТЬ:
выход из ip -d link show eth1
eth1: mtu 1500 qdisc fq_codel state UNKNOWN mode DEFAULT группа по умолчанию qlen 1000 link / ether 00: b0: d6: 00: 00: 00 brd ff: ff: ff: ff: ff: ff promiscuity 0 addrgenmode eui64 numtxqueues 1 numrx36_size 1 gso_max gso_max_segs 65535
Я также предполагаю rp_filter
активируется и вызывает проблемы. При наличии метки он ведет себя не так, как ожидалось. Некоторые ссылки находятся в этом Q / A: Расширенная маршрутизация с отметками межсетевого экрана и rp_filter
.
Итак, хотя для исходящих пакетов установлена метка, которая выбирает table 2
, такой отметки нет для входящий пакеты. Таким образом, считается, что эти пакеты не используют table 2
и отбрасываются стеком маршрутизации ядра переадресация обратного пути фильтр rp_filter
, потому что те входящий пакеты не имеют обратного исходящий маршрут, глядя в main
стол.
Исправление должно быть:
ip rule add iif eth1 table 2
Но потому что rp_filter
работает не так, как ожидалось, необходимо добавить второе исправление: set rp_filter
в свободном режиме:
sysctl -w net.ipv4.conf.eth1.rp_filter=2
Теперь часть, для которой у меня нет объяснения: похоже, хост не находит запись 172.18.0.0/16 при поиске таблицы 2 и контейнера исходящий пакеты сбрасываются на хост. У него нет проблемы с поиском 192.168.2.0/24 в таблице 2 перед маршрутом по умолчанию. Итак, не зная точно почему (это работает для 192.168.2.0/24), окончательное исправление состоит в том, чтобы продублировать из основной таблицы отсутствующий маршрут:
ip route add table 2 172.18.0.0/16 dev docknet src 172.18.0.1
Я обычно дублирую их все и больше об этом не думаю. Теперь пинг из контейнера должен работать и проходить через eth1.
ОБНОВИТЬ:
Собственно вовлекать не нужно iptables
вообще в этом случае: ip rule
может делать это самостоятельно, и все ведет себя лучше без метки, потому что таблица 2 просматривается, когда это необходимо, а с меткой это не всегда (например: не нужно iif eth1
больше здесь). Итак, вот более простой ответ. Это заменяет настройки OP и предыдущий ответ (поэтому не добавляйте правило исключения):
ip rule add iif docknet table 2
ip route add table 2 172.18.0.0/16 dev docknet src 172.18.0.1
ip route add table 2 default via 192.168.2.1 dev eth1
Это заставляет контейнер использовать eth1, даже не изменяя rp_filter.
Теперь, чтобы это тоже работало с хоста в моем тесте, нужно снова ослабить rp_filter (и, конечно, нужно использовать oif):
sysctl -q -w net.ipv4.conf.eth1.rp_filter=2
ip rule add oif eth1 table 2
Кроме того, в отличие от OP, в моих тестах, чтобы иметь возможность использовать ping -I eth1 8.8.8.8
или например curl --interface eth1 8.8.8.8
с "хоста" мне тоже пришлось это сделать в дополнение к предыдущим командам:
ip rule add oif eth1 table 2
Что для локально сгенерированные пакеты, идущие через eth1. Без него, при принудительном использовании интерфейса eth1, хост выполняет прямые запросы ARP для 8.8.8.8 (у меня нет хорошего объяснения этого, за исключением того, что маршруты отсутствуют), который не будет работать, если карта 4g не выполняет прокси-ARP .
БОНУС: скрипт репродуктора макета
Зная, что искать (rp_filter, отсутствующие маршруты, отсутствующие правила ...), поиск рабочего решения был в основном методом проб и ошибок. Я сделал сценарий для воспроизведения всего интернет-макета с многосетевой настройкой, включая двух интернет-провайдеров и IP-адрес google 8.8.8.8. Используя приведенный ниже сценарий, я получаю эти результаты от (реального) хоста:
# ip netns exec dockerhost traceroute -n 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.1.1 0.160 ms 0.043 ms 0.031 ms
2 192.0.2.1 0.111 ms 0.055 ms 0.046 ms
3 203.0.113.11 0.112 ms 0.047 ms 0.041 ms
4 8.8.8.8 0.073 ms 0.048 ms 0.045 ms
# ip netns exec dockerhost traceroute -i eth1 -n 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.2.1 0.071 ms 0.017 ms 0.014 ms
2 198.51.100.1 0.044 ms 0.023 ms 0.020 ms
3 203.0.113.22 0.042 ms 0.025 ms 0.024 ms
4 8.8.8.8 0.032 ms 0.026 ms 0.025 ms
# ip netns exec container traceroute -n 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 172.18.0.1 0.081 ms 0.017 ms 0.012 ms
2 192.168.2.1 0.038 ms 0.024 ms 0.022 ms
3 198.51.100.1 0.035 ms 0.030 ms 0.028 ms
4 203.0.113.22 0.036 ms 0.040 ms 0.027 ms
5 8.8.8.8 0.046 ms 0.037 ms *
Скрипт, который я написал для создания макета частей сети Интернет (у меня закончились тестовые сети, поэтому использовал айпи адрес peer
синтаксис для адреса "без LAN" + маршрутизация в конце):
#!/bin/sh
if ip netns id | grep -qv '^ *$' ; then
printf 'ERROR: leave netns "%s" first\n' $(ip netns id) >&2
exit 1
fi
for ns in dockerhost container gw1 gw2 isp1 isp2 google inet; do
ip netns del $ns 2>/dev/null || :
ip netns add $ns
ip -n $ns link set lo up
ip netns exec $ns sysctl -q -w net.ipv4.conf.default.forwarding=1
ip netns exec $ns sysctl -q -w net.ipv4.conf.default.rp_filter=1
ip netns exec $ns sysctl -q -w net.ipv4.conf.all.rp_filter=1
ip netns exec $ns sysctl -q -w net.ipv6.conf.default.disable_ipv6=1
done
ip -n dockerhost link add docknet type bridge
ip netns exec dockerhost iptables -t nat -A POSTROUTING -s 172.18.0.0/16 ! -o docknet -j MASQUERADE
ip -n dockerhost link set docknet up
ip -n dockerhost address add 172.18.0.1/16 dev docknet
ip -n dockerhost link add veth-container type veth peer netns container name eth0
ip -n dockerhost link set veth-container master docknet
ip -n dockerhost link set veth-container up
ip -n dockerhost link add eth0 type veth peer netns gw1 name lan1
ip -n dockerhost link set eth0 up
ip -n dockerhost address add 192.168.1.100/24 dev eth0
ip -n dockerhost route add default via 192.168.1.1
ip -n dockerhost link add eth1 type veth peer netns gw2 name lan2
ip -n dockerhost link set eth1 up
ip -n dockerhost address add 192.168.2.100/24 dev eth1
ip -n container link set eth0 up
ip -n container address add 172.18.0.2/16 dev eth0
ip -n container route add default via 172.18.0.1
ip -n gw1 route add unreachable 172.16.0.0/12
ip netns exec gw1 iptables -t nat -A POSTROUTING -s 192.168.1.0/24 ! -o lan1 -j MASQUERADE
ip -n gw1 link set lan1 up
ip -n gw1 address add 192.168.1.1/24 dev lan1
ip -n gw1 link add wan0 type veth peer netns isp1 name client0
ip -n gw1 link set wan0 up
ip -n gw1 address add 192.0.2.100/24 dev wan0
ip -n gw1 route add default via 192.0.2.1
ip -n gw2 route add unreachable 172.16.0.0/12
ip netns exec gw2 iptables -t nat -A POSTROUTING -s 192.168.2.0/24 ! -o lan2 -j MASQUERADE
ip -n gw2 link set lan2 up
ip -n gw2 address add 192.168.2.1/24 dev lan2
ip -n gw2 link add wan0 type veth peer netns isp2 name client0
ip -n gw2 link set wan0 up
ip -n gw2 address add 198.51.100.100/24 dev wan0
ip -n gw2 route add default via 198.51.100.1
ip -n isp1 route add unreachable 192.168.0.0/16
ip -n isp1 link set client0 up
ip -n isp1 address add 192.0.2.1/24 dev client0
ip -n isp1 link add wan0 type veth peer netns inet name isp1
ip -n isp1 link set wan0 up
ip -n isp1 address add 203.0.113.101 peer 203.0.113.11 dev wan0
ip -n isp1 route add default via 203.0.113.11
ip -n isp2 route add unreachable 192.168.0.0/16
ip -n isp2 link set client0 up
ip -n isp2 address add 198.51.100.1/24 dev client0
ip -n isp2 link add wan0 type veth peer netns inet name isp2
ip -n isp2 link set wan0 up
ip -n isp2 address add 203.0.113.102 peer 203.0.113.22 dev wan0
ip -n isp2 route add default via 203.0.113.22
ip -n google link add wan0 type veth peer netns inet name google0
ip -n google link set wan0 up
ip -n google address add 203.0.113.103 peer 203.0.113.33 dev wan0
ip -n google route add default via 203.0.113.33
ip -n google address add 8.8.8.8 dev lo
ip -n inet link set isp1 up
ip -n inet address add 203.0.113.11 peer 203.0.113.101 dev isp1
ip -n inet route add 192.0.2.0/24 via 203.0.113.101
ip -n inet link set isp2 up
ip -n inet address add 203.0.113.22 peer 203.0.113.102 dev isp2
ip -n inet route add 198.51.100.0/24 via 203.0.113.102
ip -n inet link set google0 up
ip -n inet address add 203.0.113.33 peer 203.0.113.103 dev google0
ip -n inet route add 8.8.8.8 via 203.0.113.103
#OP's additional settings for goal
#ip netns exec dockerhost iptables -t mangle -I PREROUTING -s 172.18.0.0/16 -j MARK --set-mark 1
#ip -n dockerhost rule add from all fwmark 1 table 2
#ip -n dockerhost route add table 2 default via 192.168.2.1 dev eth1 proto static
#ip -n dockerhost route add table 2 default via 192.168.2.1 dev eth1
#Superseded initial fix
#ip netns exec dockerhost sysctl -q -w net.ipv4.conf.eth1.rp_filter=2
#ip -n dockerhost rule add iif docknet table 2
#ip -n dockerhost rule add iif eth1 table 2
#ip -n dockerhost route add table 2 172.18.0.0/16 dev docknet src 172.18.0.1
#Superseded initial host fix
#ip -n dockerhost rule add oif eth1 table 2
#Or instead proxy_arp on gw2 would work
#ip netns exec gw2 sysctl -q -w net.ipv4.conf.lan2.proxy_arp=1
#Final fix for container, without additional iptables rule, not using marks at all:
ip -n dockerhost rule add iif docknet table 2
ip -n dockerhost route add table 2 172.18.0.0/16 dev docknet src 172.18.0.1
ip -n dockerhost route add table 2 default via 192.168.2.1 dev eth1
#Final fix for host
ip netns exec dockerhost sysctl -q -w net.ipv4.conf.eth1.rp_filter=2
ip -n dockerhost rule add oif eth1 table 2