UbuntuをMAP-Eルータにする試み

目次

v6プラス(MAP-E)を処理してくれるPCルータが欲しかったので構築してみたメモ。

環境

  • OS: Ubuntu Server 22.04
  • 回線: フレッツ光クロス + JPNE (v6プラス)

背景

NTTからレンタルできる10G対応のルータは例えばSyslogが転送できなかったりと、なかなか機能が少なくて辛い。
しかし10Gbps対応かつMAP-Eの処理をできる多機能なルータは、そこそこお値段が張るし小さいファンがついているものが多く動作音が気になりそうで辛い。
そこで今回は10G NICを2つ積んだPC(Hyper-V VM)をルータにして、多機能 & 安価 & 静音なMAP-E対応10Gルータを構築することにした。

まだまだ中途半端な部分があるため、恐らくそのうち色々追記する…。

試み

NGNからIPv6アドレスを受け取る (DHCPv6-PD)

フレッツ光クロスではひかり電話契約の有無に関わらず、割り当てられるIPv6アドレスが/56になっており、ルータ(CE)はDHCPv6-PDでIPアドレスを受け取る必要がある。

Ubuntuに標準インストールされているnetplanでDHCPv6-PDを使う方法を調べたが、オプションが見つからなかっため、どうやら現時点ではnetplanを使うことはできないようだった。
そこで、今回はnetplanを無効化し、netplanのバックエンドであるsystemd-networkdを直接触って各インターフェースにIPアドレスを設定していく。

まず現時点でnetplanが作成した設定をコピーした後、netplanによる設定を無効化する。

# netplanによる設定を確認
$ ls -al /run/systemd/network/
total 12
drwxr-xr-x  2 root root 100 Nov 18 07:03 .
drwxr-xr-x 26 root root 620 Nov 18 07:03 ..
-rw-r--r--  1 root root 162 Nov 18 07:03 10-netplan-eth0.network
-rw-r--r--  1 root root  99 Nov 18 07:03 10-netplan-eth1.network
-rw-r--r--  1 root root  83 Nov 18 07:03 10-netplan-eth2.network

# netplanで実施している設定を移行
$ sudo cp -rav /run/systemd/network/*.network /etc/systemd/network/
'/run/systemd/network/10-netplan-eth0.network' -> '/etc/systemd/network/10-netplan-eth0.network'
'/run/systemd/network/10-netplan-eth1.network' -> '/etc/systemd/network/10-netplan-eth1.network'
'/run/systemd/network/10-netplan-eth2.network' -> '/etc/systemd/network/10-netplan-eth2.network'

# netplanの設定を無効化
$ sudo mv /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak

次にmanを参考に以下のような設定を入れ、DHCPv6-PDでDUID-LLを使ってNGNからIPアドレスを取れるようにする。
また同時にLAN側にも色々設定を入れた。これでLAN側に繋いだ端末へDHCP(v4)でIPv4アドレスを配ったり、RAでIPv6アドレスを配ったりできる。
今回は、まだ(IPv4の)DNSキャッシュサーバを立てていないため、DHCPで配布するDNSは1.1.1.1を指定している。 今後、DHCPv6-PDでNGNから取得したIPv6アドレスのDNSサーバで名前を解決するキャッシュサーバを立てて、ルータのIPを配布しようと考えている。

  • NGN側 (WAN側)

    [Match]
    Name=eth1
    
    [Link]
    RequiredForOnline=no
    
    [Network]
    LinkLocalAddressing=ipv6
    IPv6AcceptRA=no
    DHCP=ipv6
    Address=[ 計算したCEアドレスを入れる ]/128
    IPForward=ipv4
    
    [DHCPv6]
    DUIDType=link-layer
    WithoutRA=solicit
    
  • LAN側

    [Match]
    Name=eth2
    
    [Link]
    RequiredForOnline=no
    
    [Network]
    DHCP=no
    DHCPServer=yes
    Address=192.168.1.1/24
    Address=fe80::1/64
    LinkLocalAddressing=ipv6
    IPv6AcceptRA=no
    IPv6DuplicateAddressDetection=1
    IPv6SendRA=yes
    DHCPv6PrefixDelegation=yes
    
    [DHCPv6]
    #EmitDNS=yes
    DNS=1.1.1.1
    EmitRouter=yes
    Timezone=Asia/Tokyo
    
    [DHCPv6PrefixDelegation]
    SubnetId=0
    
    [IPv6SendRA]
    Managed=no
    OtherInformation=yes
    RouterLifetimeSec=86400
    

ここまで設定を入れたら一度サーバを再起動して思った通りの動作をするか確認する。

正しく設定ができていれば、 sudo journalctl -b -u systemd-networkd --no-pager を実行すると DHCP6: received PD Prefix 2001:db8::/56 のように /56のアドレスを取得したログが見えたり、eth2(LAN側)に取得したプレフィックスを使ったIPv6のグローバルアドレスが割り当てられている。

なお、この段階でインターネットからIPv6アドレスで本サーバやLAN側に繋がった端末へ直接アクセスできるようになるため注意する。

nftablesでNATルールを入れる

NAT設定は以下サイトのスクリプトをほぼそのまま利用させていただいた。(素晴らしいスクリプトをありがとうございます。)
フレッツ光クロス:MAP-E ROUTER by Debian Box (nftables)

BR=[ 計算したBRアドレスを入れる ]
CE=[ 計算したCEアドレスを入れる ]
IP4=[ 計算したIPv4アドレスを入れる ]
LANDEV='eth2'
WANDEV='eth1'
MANAGEDEV='eth0'
TUNDEV='ip6tnl1'

# TYPE: [ OCN | V6P ]
TYPE='V6P'

if [ "$TYPE" = "OCN" ]; then \
  lp=63
  nxps=1024 # next port set
elif [ "$TYPE" = "V6P" ]; then \
  lp=15
  nxps=4096 # next port set
else
  echo Unknown TYPE: $TYPE
  exit 1
fi

# カーネルパラメータ設定
sysctl -w net.ipv4.ip_forward=1
sysctl -w net.ipv6.conf.all.forwarding=1

# IPv4 over IPv6トンネル作成
ip -6 tunnel add $TUNDEV mode ip4ip6 remote $BR local $CE dev $WANDEV encaplimit none
ip link set dev $TUNDEV mtu 1460 up
ip a add $IP4/32 dev $TUNDEV
ip r add default dev $TUNDEV

## configure nftables

## clear nftables first of all
nft flush ruleset

## add map_e_filter table
nft add table ip map_e_filter
nft add chain ip map_e_filter POSTROUTING { type filter hook postrouting priority 0\; }
nft add rule  ip map_e_filter POSTROUTING iifname $TUNDEV tcp flags \& syn == syn tcp option maxseg size set rt mtu
nft add rule  ip map_e_filter POSTROUTING oifname $TUNDEV tcp flags \& syn == syn tcp option maxseg size set rt mtu

## add map_e_nat table
nft add table ip map_e_nat
nft add chain ip map_e_nat POSTROUTING { type nat hook postrouting priority 0 \; }

## add my_vmap
nft add map ip map_e_nat my_vmap { type mark : verdict \; }

## add POSTROUTING rules to map_e_nat table
for proto in tcp udp icmp udplite sctp dccp; do
nft add rule map_e_nat POSTROUTING oifname $TUNDEV meta l4proto $proto mark set numgen inc mod $lp offset 1
done

nft add rule map_e_nat POSTROUTING oifname $TUNDEV meta mark vmap @my_vmap

## add map_e_chains to map_e_nat table and add elements into my_vmap

rule=1
while [ $rule -le $lp  ] ; do
  mark=`expr $rule`
  portl=`expr $rule \* $nxps + $PSID \* 16`
  portr=`expr $portl + 15`
  nft add chain ip map_e_nat map_e_chain$mark
  for proto in tcp udp icmp udplite sctp dccp; do
    nft add rule ip map_e_nat map_e_chain$mark meta l4proto $proto snat to $IP4:$portl-$portr persistent
  done
  nft add element ip map_e_nat my_vmap { $mark : goto map_e_chain$mark }
  rule=`expr $rule + 1`
done

ここまで設定が終わるとIPv4の通信もできるようになる。

この段階でSpeedtest.netを回したところ、ダウンロードスピードは6Gbpsを軽く超える十分な速度が出た。(NTTルータを使っている場合とほぼ同等)
構築中ということもあり、CPU 4コア割り当て、メモリ4GB割り当てとルータにしては結構高スペックなVMになっているため、どれくらいリソースを減らせるかも検証したい。
ただ、アップロードスピードは4Gbps程度とNTTルータと比べて明らかに速度が落ちてしまった。何か設定に問題があるか確認したい。

ファイアウォール設定

IPv4はNATがかかるのでまだしも、現段階ではIPv6に一切ファイアウォールがかかっておらず、本サーバだけでなく配下の全クライアントへインターネットからアクセスし放題になっている。
IPv6はアドレス空間が広いので無作為な攻撃はほぼ受けないが、例えばどこかにアクセスした瞬間にそのIPへ攻撃をかけられると普通に攻撃が成立してしまうため、ファイアウォールはかけておいた方が良い。

ICMPv6のファイアウォールはRFC4890を参考に設定を書いた。

ルール不足があるかもしれないので、このまま利用する場合は内容を確認してから利用してください。

# firewall
nft add table inet filter
nft add chain inet filter input { type filter hook input priority 0\; }
nft add chain inet filter forward { type filter hook forward priority 0\; }
nft add chain inet filter output { type filter hook output priority 0\; }

# input rules
nft add rule inet filter input iifname lo accept
nft add rule inet filter input ct state established,related counter accept

# input - IPv4 Services
nft add rule inet filter input ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } tcp dport 53 iifname $LANDEV counter accept
nft add rule inet filter input ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } udp dport { 53, 123 } iifname $LANDEV counter accept
nft add rule inet filter input udp dport 67 iifname $LANDEV counter accept

# input - DHCPv6
nft add rule inet filter input ip6 saddr fe80::/64 udp dport dhcpv6-client iifname $WANDEV counter accept
nft add rule inet filter input ip6 saddr fe80::/64 udp dport dhcpv6-server iifname $LANDEV counter accept

# input - ICMPv6
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply } counter accept
nft add rule inet filter input ip6 nexthdr icmpv6 ip6 saddr fe80::/64 icmpv6 type { nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } iifname $WANDEV counter accept
nft add rule inet filter input ip6 nexthdr icmpv6 ip6 saddr fe80::/64 icmpv6 type { 148, 149 } iifname $WANDEV counter accept
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { 148, 149 } iifname $LANDEV counter accept
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, ind-neighbor-solicit, ind-neighbor-advert } iifname $LANDEV counter accept

# input - ICMPv6 for multicast
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { mld-listener-query, mld-listener-report, mld-listener-done, mld2-listener-report } iifname $LANDEV counter accept
nft add rule inet filter input ip6 nexthdr icmpv6 icmpv6 type { 151, 152, 153 } iifname $LANDEV counter accept

# input - management
nft add rule inet filter input ip saddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } ct state new tcp dport ssh iifname $MNGDEV counter accept

# input - drop
nft add rule inet filter input drop

# forward rules
# forward - drop private address
nft add rule inet filter forward ip daddr { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 } oifname { $WANDEV, $TUNDEV } counter drop
# forward - drop netbios
nft add rule inet filter forward tcp dport { 139, 445 } oifname { $WANDEV, $TUNDEV } counter drop
nft add rule inet filter forward udp dport { 137, 138 } oifname { $WANDEV, $TUNDEV } counter drop

# forward - accept ICMPv6
nft add rule inet filter forward ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply } counter accept

# 個別許可設定用
#nft add rule inet filter forward ip6 daddr 2001:db8::/32 accept

# forward - accept from LAN
nft add rule inet filter forward ct state related,established accept
nft add rule inet filter forward iifname $LANDEV accept

# forward - drop
nft add rule inet filter forward drop

# output - accept all