送信元インターフェースにリンクローカルアドレスしかなく,デフォルトルートがない場合,scapyのsr1()が上手くいかなかったので,その状況を共有します.(こんな状況になる人は少ないとは思いますが)
状況
SRv6のテスト用でscapyを使って自作pingっぽいものを作っています.
そのプログラムでは,パケット送信するために,scapyのsr1()
関数を使っていました.
このとき,ルータとして動かしているLinuxノードからパケットを上手く送信できませんでした.
具体的には,以下のようなClosネットワークを動かしまして,各ノードはBGP unnumberedで設定しています.
このネットワークで,l1から実行したときにWARNING: No route found for IPv6 destination fdbb:a1:0:0:1:: (no default route?)
と出て,パケットを送信できませんでした.
以下は,mininet使って実験していたので,mininetのCLIから実行したやつです.
mininet> l1 srv6ping -c 2 -d fdbb:a1:0:0:1:: --src fdbb:c1:0:0:1::
WARNING: No route found for IPv6 destination fdbb:a1:0:0:1:: (no default route?)
WARNING: No route found for IPv6 destination fdbb:a1:0:0:1:: (no default route?)
WARNING: more No route found for IPv6 destination fdbb:a1:0:0:1:: (no default route?)
timeout.
timeout.
デフォルトルートは設定していませんでしたが,以下のようにルートを見てみると,到達先であるfdbb:a1::/64
へのルートは設定されていたので,原因が分からず詰まりました.
mininet> l1 ip -6 r
fdbb:a1::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:a2::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:a3::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:a4::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:a5::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:a6::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:b1::/64 nhid 34 via fe80::286c:1ff:fe64:39f6 dev l1_s1 proto bgp metric 20 pref medium
fdbb:b2::/64 nhid 16 via fe80::789b:afff:fe0f:8d1a dev l1_s2 proto bgp metric 20 pref medium
fdbb:b3::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:b4::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:b5::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:b6::/64 nhid 33 proto bgp metric 20 pref medium
nexthop via fe80::789b:afff:fe0f:8d1a dev l1_s2 weight 1
nexthop via fe80::286c:1ff:fe64:39f6 dev l1_s1 weight 1
fdbb:c1:0:0:100:: nhid 12 encap seg6local action End.DT6 table 10 dev group1 proto bgp metric 20 pref medium
(略)
原因
結論から言うと,送信元アドレスの候補がなかったためにscapyでルートの設定が上手く取れていないこととデフォルトルートがないことが原因でした.
というのも,scapyでは独自のネットワークスタックを持っており,Linuxで設定されたルートがscapyに反映されないと上手く動きません.
scapyのルートはconf.route6
に設定されており,l1では以下のルートが設定されてました.
>>> from scapy.all import *
>>> from scapy.config import conf
>>> conf.route6
Destination Next Hop Iface Src candidates Metric
fd00:1::/128 :: l1_h1 fd00:1::1 0
fd00:1::1/128 :: l1_h1 fd00:1::1 0
fd00:1::/64 :: l1_h1 fd00:1::1 256
fe80::/128 :: l1_h1 fe80::bca1:87ff:feb5:618b 0
fe80::bca1:87ff:feb5:618b/128 :: l1_h1 fe80::bca1:87ff:feb5:618b 0
fe80::/64 :: l1_h1 fe80::bca1:87ff:feb5:618b 256
fe80::/64 :: l1_s1 fe80::ec29:9ff:fe46:a00e 256
fe80::/64 :: l1_s2 fe80::805d:b6ff:feea:ac55 256
fe80::/64 :: l1_c1 fe80::ccdd:75ff:fefa:d2a8 256
::1/128 :: lo ::1 0
fdbb:c1::/128 :: lo ::1 0
fdbb:c1:0:0:1::/128 :: lo ::1 0
fe80::/128 :: l1_s1 fe80::ec29:9ff:fe46:a00e 0
fe80::/128 :: l1_s2 fe80::805d:b6ff:feea:ac55 0
fe80::/128 :: l1_c1 fe80::ccdd:75ff:fefa:d2a8 0
fe80::805d:b6ff:feea:ac55/128 :: l1_s2 fe80::805d:b6ff:feea:ac55 0
fe80::ccdd:75ff:fefa:d2a8/128 :: l1_c1 fe80::ccdd:75ff:fefa:d2a8 0
fe80::ec29:9ff:fe46:a00e/128 :: l1_s1 fe80::ec29:9ff:fe46:a00e 0
ここで表示されているルートですが,BGPで学習したルート等が取れてません.
はじめは,ECMPやBGPでのルート設定が原因かと思っていましたが,ソースコードを追っていくと,scapyでの送信元アドレスの候補(Src candidates)の選択が原因とわかりました.
送信元アドレスの候補とは,パケット送信時に送信元アドレスが指定されていない場合,デフォルトで選択されるアドレスを表すものです.RFC6724で詳しく説明されていますが,基本的には送信アドレスからアドレスの候補を選択します.scapyでは,この関数construct_source_candidate_set
において,同じスコープのアドレスを送信元インターフェースのアドレスから選択しています.
ルートが取れていないノードでは,ルートのインターフェースにリンクローカルアドレスのみが設定されておりグローバルアドレスを設定していません.そのため,グローバルスコープのアドレスを宛先とするルートの送信元アドレスの候補がない状態となり,ルートがscapyに設定されませんでした.
また,デフォルトルートもない上であればデフォルトルートに送信されますが,デフォルトルートがないのでパケットがconf.iface
に設定されたlo
に向けて送信されていました.
解決策
scapyにおいてルートが設定されていないのが基本的な原因なので,ルートを手動でconf.route6
に入れれば動くやろという解決策でやります.本当は,sr1()
以外を使うのがスマートだとは思いますが,良い解決策が思いつかなかったので,これでやります.
基本的には,以下のように,必要なルート設定してやることを考えます.
conf.route6.routes.append((dst_prefix, prefix_len, next_hop, dev, iface_addr, metric))
しかし,毎回ルートを手動で設定していくのは面倒です.
そのため,Linuxのルーティングテーブルから学習できなかったルートを取得し,送信アドレスの候補を設定した上で,conf.route6
に無理やり入れる方式でやります.
結果的には,以下のコードを作成しました.
from scapy.all import in6_getifaddr
from scapy.config import conf
from scapy.utils6 import in6_ptop, in6_isincluded
from scapy.layers.inet6 import neighsol, ICMPv6NDOptDstLLAddr, ICMPv6ND_NA
def neighsol_and_chache(addr, iface):
"""MACアドレスを学習"""
src = [ifaddr[0] for ifaddr in in6_getifaddr() if ifaddr[2] == iface][0]
if conf.netcache.in6_neighbor.get(addr) is None:
sol = neighsol(addr, src, iface, chainCC=True)
if ICMPv6ND_NA in sol:
taddr = sol[ICMPv6NDOptDstLLAddr].lladdr
conf.netcache.in6_neighbor[addr] = taddr
def parse_addr6(addr: str) -> str:
addr = ":".join([addr[i:i+4] for i in range(0, len(addr), 4)])
return in6_ptop(addr)
def add_target_routes6(dst: str, src: str):
"""送信元アドレスの候補がないsr1()の宛先のルートを追加する
Args:
dst (str): sr1で送信するパケットの宛先
src (str): sr1で送信するパケットの送信元
"""
with open("/proc/net/ipv6_route", "r") as f:
# 一行読み取る
line = f.readline()
while line:
line = line.strip().split()
# ルートのパラメータを読み取る
d, nh, dev = line[0], line[4], line[9]
prefix = int(line[1], 16)
metric = int(line[5], 16)
d = parse_addr6(d)
nh = parse_addr6(nh)
# 必要なルートを追加
if d != "::" and in6_isincluded(dst, d, prefix):
# 同じプレフィックスのルートがない場合
if (d, prefix) not in [(r[0], r[1]) for r in conf.route6.routes]:
# ルートを追加
conf.route6.routes.append((d, prefix, nh, dev, [src], metric))
# デフォルトルートがない場合,ICMPv6 NSも上手く送信できないので手動でMACアドレスをキャッシュ
if nh != "::":
neighsol_and_chache(nh, dev)
line = f.readline()
まず,scapyでは,/proc/net/ipv6_route
からルートを抜き出していたので,学習できなかった宛先へのルートを同様に抜き出します.
また,送信元アドレスが決定できないので,送信元アドレスを手動で決めます.
コードのadd_target_routes6
では,sr1()
で送信するための送信元アドレスを引数で取ります.(この記事では送信元アドレスを手動で指定するようにしていますが,loといった他のインターフェースから取ってきても良いと思います.)
上のadd_target_routes6
では,neighsol_and_chache
でネクストホップのMACアドレスを取得してconf.netcache.in6_neighbor
に設定しています.というのは,デフォルトルートがないので,Neighbor Solicitation (NS)が自動で送信できないためです.
以上のコードで,とりあえずこの状況ではsr1()
で送信できるようになりました.
おわりに
もっと上手い方法がありそう.