Linux マシンを IPv6 ルータにする(Ubuntu 16.04)

Interlink で IPv6 オプションがリリースされ、「ほぼ固定だけど長期間使用しないと変更される可能性がある」 IPv6 prefix が PPPoE で降ってくるようになりました。(「IPv6接続サービス」提供開始のご案内

ということで、Ubuntu 16.04 の VM 経由で PPPoE に繋いでルーターにしてみました。

Raspberry PiでIPv6 PPPoE対応ルータを作る – ももいろテクノロジーフレッツ光ネクスト用IPv6 PPPoEアダプターを作ってみる — SOUM/misc を参考に実施したので、こちらを参照するとよいと思いますが、自分用のメモを兼ねて残します。

なお、色々試してうまく行ったあと記憶を基に再現しているので、追加の手順が必要だったなどあればコメントでお願いします。

VM 構成

  • ens160: フレッツ網に繋がる NIC (ONU 直結、またはそれと等価な状態)
  • ens192: 今回の設定に関係させてはいけない NIC (実験のため設置)
  • ens224: 部屋のネットワークに繋がる NIC。ここに繋いだ端末を IPv6 インターネットに繋ぐのがこの記事の目標

パケットが適切に届けば VM でも特に変わらない。

(余談ですが ens160 みたいなのは、 eth0 みたいなのと同じく NIC のデバイス名。Predictable Network Interface Device Name を Ubuntu 16.04 などのシステムが導入しているのでこういう名前になっている模様。)

フレッツ網からの RA ブロック

NTT東西のフレッツ網は日本国内で IPv6 イントラネットを構成しており、 PPPoE しようとしてフレッツ網に接続すると IPv6 の RA (イントラネットにも関わらずグローバルアドレス)が降ってきてしまう。

状況次第ではあるが、 ISP から直接フレッツ網に IPoE と呼ばれるものを引き込んでくれる契約をしていなければ、この IPv6 網に繋がってしまう.
もし他のプロバイダが IPoE 設定になっていると今度は複数のインターネットに繋がるプレフィックスに同一マシンがつながってしまうことになる。いずれも(理解してそうなっているのでなければ)よろしくない。(IPv6 – Wikipediaに詳しく書かれている)

今回はプロバイダに対して DHCPv6 接続するので、全ての RA の受信を遮断する設定にした。/etc/sysctl.conf に追記。ens160 が、フレッツ網(PPPoEや、フレッツのイントラネットに接続)に繋がる NIC だとする。

$ cat /etc/sysctl.conf | tail -n 4 | head -n 2
net.ipv6.conf.all.accept_ra = 0
net.ipv6.conf.ens160.accept_ra = 0

$ sudo sysctl -p /etc/sysctl.conf

IPv6 over PPPoE (pppoeconf)

apt で pppoeconf をインストールして pppoeconf コマンドで設定。ダイアログが出てくるのに答えれば PPPoE セッション自体は張ってくれるが、 IPv4 と違って少し追加設定が必要。/etc/ppp/peers/dls-provider を修正。

$ cat /etc/ppp/peers/dsl-provider
# Minimalistic default options file for DSL/PPPoE connections

noipdefault
defaultroute
replacedefaultroute
hide-password
#lcp-echo-interval 30
#lcp-echo-failure 4
noauth
persist
#mtu 1492
#persist
#maxfail 0
#holdoff 20
plugin rp-pppoe.so
nic-ens160
user "***@***"
usepeerdns
+ipv6
ipparam ipv6default

修正したら、

$ sudo poff dsl-provider
$ sudo pon dsl-provider

で再接続して反映。ifconfig ppp0 でリンクローカルアドレスがインターフェイスに付与されていることを確認。

デフォルトルートの設定

PPPoE を起動した際、ルーティングテーブルに追加されるようにする。なお、 /etc/ppp/ipv6-up.d/ 以下のファイルを新規追加する場合は実行権限が必要。

$ cat /etc/ppp/ipv6-up.d/v6route
#!/bin/sh
if [ -z "${CONNECT_TIME}" ]; then
    if [ "${PPP_IPPARAM}" = "ipv6default" ]; then
        ip -6 route add default dev ${PPP_IFACE}
    fi
fi

$ ip -6 route
(snip.)
default dev ppp0  metric 1024  pref medium

DHCPv6 クライアント (wide-dhcpv6)

このままではまだインターリンクとの間に貼られた PPPoE セッションでのみ使えるリンクローカルアドレスしか手に入っていないので、 DHCPv6-PD クライアントを実行し、グローバル IPv6 アドレスの /64 prefix の移譲を受ける。

apt で wide-dhcpv6-client をインストールした。

/etc/wide-dhcpv6/dhcp6c.conf を設定

profile default
{
  information-only;

  request domain-name-servers;
  request domain-name;

  script "/etc/wide-dhcpv6/dhcp6c-script";
};

interface ppp0 {
  send ia-pd 0;
  request domain-name-servers;
  script "/etc/wide-dhcpv6/dhcp6c-script";
};

id-assoc pd 0 {
  prefix-interface ens224 {
    sla-id 1;
    sla-len 8;
  };
};

なお、 ens224 は PPPoE で使っている NIC ですらなく、 LAN 側で使っているインターフェイス。不思議だが、 ppp0 に指定してもうまくいかない上、逆にちゃんと MAC アドレスを持つ NIC なら IPv6 接続に関係のないインターフェイスを指定してもアドレス自体は取得できた。DHCPv6 を含む IPv6 のアドレス取得メカニズム自体にも MACアドレスを利用するものはあるので関係があっても不思議ではないが、検証するのが大変そうなので今のところ深追いはしていない。

ただ、どうもこの後の設定に影響はするらしく、最終的には室内 LAN に繋がる NIC を指定することとなった。

ファイヤーウォール (ip6tables)

RedHat 系だと標準で保存した iptables を起動時に読み込む機能が備わっているが、Debian 系はそういうわけでもない。と思っていたら、 netfilter-persistent なるパッケージがあり、これをインストールすると似たことができる模様。

apt で netfilter-persistent をインストールする。iptables や ip6tables をいじったら、 sudo service netfilter-persistent save などとして保存できる。/etc/iptables/rules.v[46] に保存するようなので、これを直接編集することもできなくはないと思われる。

というわけで、あとは ip6tables を設定する。LAN 内がプライベートアドレスのため変換が必要となり iptables でマスカレードを指示していた IPv4時代と異なり、基本的に全てのマシンがグローバルアドレスを持ち、普通に end to end で通信するのを中継するだけなので、 iptables はなくとも通信は可能ではある(中継設定自体は後述)。が、安全のため、とりあえず従来と同様外から中に通信を開始するのをブロックしてみた。

ip6tables 自体は以下の感じ。ens224 が IPv6 を提供する部屋LAN、 ens192 が今回の設定に関与させない(IPv6 に繋がない)NIC。

# Initialize
ip6tables -X
ip6tables -F

# Default Rule
ip6tables -P OUTPUT ACCEPT
ip6tables -P FORWARD DROP
ip6tables -P INPUT DROP

# Established
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# LAN
ip6tables -A INPUT -i ens224 -j ACCEPT
ip6tables -A INPUT -i ens192 -j DROP

# loopback
ip6tables -A INPUT -i lo -j ACCEPT
ip6tables -A OUTPUT -o lo -j ACCEPT

# ICMPv6
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type router-advertisement -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -j ACCEPT
ip6tables -A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -j ACCEPT

# FORWARD
ip6tables -A FORWARD -i ens192 -j DROP
ip6tables -A FORWARD -o ens192 -j DROP
ip6tables -A FORWARD -i ens224 -j ACCEPT
ip6tables -A FORWARD -i ppp0 -m state --state RELATED,ESTABLISHED -j ACCEPT

# OUTPUT
ip6tables -A OUTPUT -o ens192 -j DROP

転送設定

ルータなのでここが基幹部分と言える(といっても Linux の機能を呼び出すだけ)。ファイヤーウォールの設定が済んだら設定する。/etc/sysctl.conf に追記。

$ cat /etc/sysctl.conf | tail -n 1
net.ipv6.conf.all.forwarding = 1

$ sudo sysctl -p /etc/sysctl.conf

Router Advertisement (radvd)

RA を用いて接続するマシンの自動設定を行うため radvd を入れた。ただ、現状を踏まえると DHCPv6 を導入した方がいいかもしれない(次のクライアント側の設定の問題)。

cat /etc/radvd.conf

interface ens224 {
  AdvSendAdvert on;
  MinRtrAdvInterval 3;
  MaxRtrAdvInterval 10;
  prefix ::/64 {
    AdvOnLink on;
    AdvAutonomous on;
    AdvRouterAddr on;
  };

  RDNSS 2001:db8::1 2001:db8::2
  {
  };
};

RDNSS の部分の二つの IPv6 アドレスは実際にはプロバイダが公知している DNS キャッシュサーバの IPv6 アドレスを直書きした。書いたら radvd を再起動。

sudo radvdump ens224 などとすると、配布内容を確認できた。

クライアント側

ここでは IPv6 のみに繋がるクライアントを一台構成した。IPv4 を設定する場合は基本的に独立して設定してよいはず。

RFC5006RFC6106などで RDNSS プロトコルが定められており、DHCPv6 を用いなくとも RA で DNS キャッシュサーバのアドレスを通知することができ、radvd も対応している。しかし、少なくとも Ubuntu 16.04 標準の機能でこれを有効にする方法は見つからなかった。そのため、この説明では DNS キャッシュサーバのアドレスをクライアント側で直書きしている。微妙。今後調査したい。

それ以外は自動設定で問題なかった。

/etc/network/interfaces

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).

source /etc/network/interfaces.d/*

# The loopback network interface
auto lo
iface lo inet loopback

# The primary network interface
# This is an autoconfigured IPv6 interface
auto ens160
iface ens160 inet6 auto
    dns-nameservers 2001:db8::1 2001:db8::2

しかし、現状では DHCPv6 のほうがよさそうな気もするので、今後試してみたい。

ルーターで競技プログラミング

この記事は、Competitive Programming Advent Calendar Div2013の11日目の記事です。

さて、以前にTeXで競技プログラミングは可能か?という記事を見たことがあります。TeXで競技プログラミングはジャッジサーバーさえあれば可能であろうという結論でしたが、ではルーターではどうでしょう。

というわけで、今回はルーターで競技プログラミングを行うことは可能か?について考えていきます。他の日のみなさんが書いているような有益な記事を期待している方には申し訳ありません。また、そのため、競技プログラミングの知識は必要ありません。

まずは入出力について考えましょう。ルーターで扱える形式でなければなりませんので、今回は情報のやりとりにUDPのパケット(IP(インターネット・プロトコル)上で情報をやりとりする単位)を用い、情報はそのポート番号を用いることにしましょう。ポート番号は本来、サービスの種類を識別したりするもので、パケットごとに行き先のポート番号が設定されており、それを見ながら必要なアプリケーションにパケットを渡したり、

たくさんのルーターの間でポート番号を情報としたパケットをやりとりすることで、処理が行えるかどうかについて考えます。

さて、まずはルーターとして何を使うか決めましょう。ルーターといってもそのへんで売っているようなブロードバンドルーターだとつらそうな気がしますし(可能かどうかはわかりませんが)、どちらにしても数をたくさん用意するのは大変そうに見えます。ということで、Linuxのiptablesを用います。iptablesはLinuxをルーターとして使ったことがある方なら聞いたことがあるかもしれませんが、この記事はiptablesを見たことがないことを一応前提として書くことにします(ソースコード一行一行の説明はしないので、ソースコードは無視してください)。

iptablesはLinuxカーネルの機能として存在するパケットフィルタを設定したりするためのツールですが、便宜上、パケットを制御する機能をiptablesが持つものとして扱いましょう。iptablesでは、到着したパケットの行き先のIPアドレスやポート番号を書き換え(てその後のルーティングで目的地に迎えるようにする)たり、破棄したりといったことができます。また、フラッグを記憶することができるので、例えば不正アクセスを試みようと一分間に何回もssh(遠隔操作に用いるプロトコル)のポートにアクセスしてきたりする人を弾く、といったことや、特定のポートを事前に叩くことで別のポートを開ける、といったこともできます。これはメモリ上の変数として利用できますね。

今回はiptablesのみで実現可能なもののみを行う、ただしiptablesのみで実現可能だがより短く書くために外部コマンドを使うのはありとします(iptablesの実行は初回一回走るのみで、計算時間には関係ないので……)

ではまず、環境を構築しましょう。余計なパケットが飛び交っていると色々面倒なので、専用のネットワークを用意します。今回は家のESXi上に仮想スイッチを用意し、全ての仮想マシンをつなぎました。家にESXiがある人は画面の「構成」から「ネットワーク」を選択し「ネットワークの追加」をクリックすることで追加できますし、ESXiではないものの似た機能を持つソフトウェアであれば似たような方法でできますが、家にはそういうものはないなぁ、という場合で、かつこのページの内容をどうしても再現した場合はさくらのクラウドやAmazon EC2などのクラウドサービスを利用しましょう。
ESXi上に追加された仮想ネットワーク

続いてこのネットワーク(と便宜上インターネットに繋がるネットワーク)に接続したUbuntu仮想マシンを何台か作りましょう。画像では7台ありますが、実際には5台しか使いませんでした。それぞれのメモリ割り当ては最低限で大丈夫です。

それぞれに、プライベートIPアドレスを割り当てて、このスイッチ上でパケットをやりとりできるようにしましょう。今回は、5台それぞれに192.168.150.1から192.168.150.5を割り当てました。それぞれのマシンに手動で設定しましょう。

それぞれのマシンではIPアドレスに加えて、IPの転送を許可する設定をします。Ubuntuの場合は /etc/sysctl.conf にある「net.ipv4.ip_forward = 1」のコメントアウトを外した後、rootで「sysctl -p /etc/sysctl.conf」します。

さて本題に入りましょう。

今回は問題として、Topcoder SRM 596のDiv 1の250点問題を取り上げます。問題の解説が目的ではないので詳しい解説は省略しますが、入力それぞれについて2進で表記した時に立っているビット数の合計と、それぞれについて2進で表記した時の桁数の最大値の和から1を減じたものが答えとなります。

では、解いていきましょう。

まず、答えの入出力を行うノードを決めます。192.168.150.1のマシンにしました。このマシンから問題を含むUDPのパケットを飛ばし、問題を解くことを指示するパケットを飛ばし、答えが返ってくるか試してみます。
パケットは、

$ nc -u <宛先IPアドレス> <宛先ポート>

とコマンドを書き、Enterを一回押すととりあえず飛びます。飛んだら、Ctrl+Cを押してncから抜けましょう。

パケットの送受信を確認するために、パケットを傍受する必要があります。

$ sudo tcpdump -i eth1

などとコマンドを書くことで、そのコンピュータ上でやりとりされているパケットを見ることができます。eth1の部分はマシンごとに合わせてください。

まずは、「入力それぞれ2進表記した際に立っているビットの合計」を求める事を考えましょう。これは192.168.150.2に担当させます。
2進表記した時の立っているビットの数については、今回めんどくさいので埋め込むことにしましょう。
iptablesではフラッグを叩いた回数を記憶することができるので、別のノードに一旦叩いて欲しい回数を指示するパケットを投げ、1ずつ減らしていって0になるまでそのフラッグを叩くことにします。話は少し変わりますが、この方法で繰り返し処理も実現可能なように見えますね。

「2進の桁数の最大値」については、パケットが来たら桁数に該当するフラッグを叩き、解答を求められた時にフラッグを上から見ていくことで実現できます。

最後にその二つを足すことを考えましょう。一つパケットが到着したらそのパケットのフラッグを記憶し、次に到着したらそのフラッグを元に目的の値のポートで、答えを求めているノードに投げましょう。

方針が定まりました。実際のソースコードです(面倒くさいと思うので読み飛ばしてください)。

192.168.150.2では以下のものを実行しましょう

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

modprobe ipt_recent ip_pkt_list_tot=200

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -p udp --dport `expr $i + 32768` -m recent --name MEMORY --set -t nat -j DNAT --to 192.168.150.4:`expr $i + 32768`

	iptables -A PREROUTING -p udp --dport `expr $i + 16384` -t nat -j DNAT --to 192.168.150.4:`perl bits.pl $i 32769`

	iptables -A PREROUTING -p udp --dport $i -t nat -j DNAT --to 192.168.150.3:$i
done

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -m recent --name MEMORY --rcheck --seconds 60 --hitcount $i -p udp --dport 40000 -t nat -j DNAT --to 192.168.150.5:$i
done

だたしbits.plは

#/usr/bin/perl

my $ret = 0;

for(my $i=0; $i<20; $i++){
	if($ARGV[0]>>$i& 1){
		$ret++;
	}
}

print $ret + $ARGV[1];

192.168.150.3

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A PREROUTING -p udp --dport $i -m recent --name MEM`perl highest.pl $i 0` --set -t nat -j DNAT --to 192.168.150.2:`expr $i + 16384`
	iptables -A PREROUTING -m recent --name MEM$i --rcheck -p udp --dport 40000 -t nat -j DNAT --to 192.168.150.5:$i
	echo $i
done

ただしhighest.plは

#/usr/bin/perl

my $highest = 0;

for(my $i=0; $i<20; $i++){
	if($ARGV[0]>>$i& 1){
		$highest = $i;
	}
}

print $highest + $ARGV[1];

192.168.150.4では

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=32868; i>=32768; i--))
do
	iptables -A PREROUTING -p udp --dport $i -t nat -j DNAT --to 192.168.150.2:`expr $i - 1`
	echo $i
done

192.168.150.5では

#!/bin/bash

iptables -F
iptables -t nat -F
iptables -X

iptables -P INPUT ACCEPT
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

for((i=100; i>=1; i--))
do
	iptables -A INPUT -p udp --dport 50000 -j DROP
done

for((i=100; i>=1; i--))
do
	for((j=100; j>=1; j--))
	do
		iptables -A PREROUTING -p udp --dport $i -m recent --name FIRST$j --seconds 60 --rcheck -t nat -j DNAT --to 192.168.150.1:`expr $j + $i`
	done

	echo $i
	iptables -A INPUT -p udp --dport $i -m recent --name FIRST$i --set  -j DROP
done

実行してみましょう。(18, 16, 14)を投げるので、答えは10になるはずです。仕様は省略しますが、今回の実装では解答を得るためには二箇所のノードにパケットを投げる必要があります。

nhirokinet@testvm1:~$ nc -u 192.168.150.2 18

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 16

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 14

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.2 40000

^C
nhirokinet@testvm1:~$ nc -u 192.168.150.3 40000

^C

事前に走らせておいたtcpdumpから該当するパケットを探しましょう。

22:53:04.278019 IP 192.168.150.1.55863 > 192.168.150.1.10: UDP, length 1

お、正しい答えが返ってきましたね。

あまりまとまっていませんが、そろそろまとめに入らないと日付が変わってしまうので、まとめに入ります。

とりあえず、ルーター(iptables)のみを用いて、計算を行うプログラムを書くことができました。より多くのノードを用いることにより、またより多くのフラッグを用いることによって、また16ビット以上の数字についても前半後半に分割することによって、一応複雑な計算もできるものと思われます。

しかし、ルーターはやはりパケットのルーティングに用いるべきであり、計算に使うにはあまり実用的でないように感じます。どうしても部屋にプログラマブルな装置がルーターしかなく、その環境で計算が行いたい場合でも、ルーターでプログラムを書くよりは、ポケットコンピュータを買いに行くか手で計算したほうが実用的だと思われます。

また当然ですが、おそらく世の中にはルーターを用いて競技プログラミングを行うようなジャッジサーバーは存在しないと思われるため、これで競技プログラミングをすることは現状ではできません。誰かが将来的に作ればもしかしたらできるようになるかもしれません。

こんな記事ですが、コメントや追加実験等あれば是非コメント欄にお願いします。

iptablesでLinuxをルーターに

表題の通り。といっても設定ファイルをいじりつつiptablesのスクリプトを書くだけなのですが。とりあえずの今の設定ファイルをコピペして編集したメモなので動かなかった等あればコメントお願いします。まあ、自分用にまとめただけで別に似たようなことを書いたウェブページは世にいくらでもあるのでぐぐると判ると思います。

まず、/etc/sysctl.confをいじります。

net.ipv4.ip_forward = 1

みたいなのをコメントアウト解除、なければ書き足し。

# sysctl -p /etc/sysctl.conf

してその設定を反映。次回は起動時に反映。

iptablesを実行するシェルスクリプトを書きます(実行属性を忘れない)。WAN側をppp0、LAN側をeth0で192.168.1.1/24とします。

#!/bin/sh

WAN=ppp0
LAN=eth0
WANIP=`ifconfig $WAN | awk '/inet addr:/ { sub(/addr:/, ""); ORS=""; print $2 }'` # Ubuntuのifconfigに特化した感じがあるので動かなければ書き換え、というかコメントアウトしてあったので何か動かない理由があったのかもしれません。
LANIP=192.168.1.1
LANSUBNET=192.168.1.0/24

hairpin_nat(){
        iptables -A FORWARD -i $WAN -d $1 -p $2 --dport $3 -j ACCEPT
        iptables -t nat -A PREROUTING -p $2 --dport $3 -d $WANIP -j DNAT --to $1
        iptables -t nat -A POSTROUTING -p $2 -s $LANSUBNET -d $1 --dport $3 -j SNAT --to-source $WANIP
}

# Flush & Reset
iptables -F
iptables -t nat -F
iptables -X

# Default Rule
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
iptables -P INPUT DROP

iptables -A INPUT -i $LAN -j ACCEPT

# Filter out packets with private IP addresses from the Internet
iptables -A INPUT -i $WAN -s 192.168.0.0/16 -j DROP
iptables -A INPUT -i $WAN -s 172.16.0.0/12 -j DROP
iptables -A INPUT -i $WAN -s 10.0.0.0/8 -j DROP
iptables -A FORWARD -i $WAN -s 192.168.0.0/16 -j DROP
iptables -A FORWARD -i $WAN -s 172.16.0.0/12 -j DROP
iptables -A FORWARD -i $WAN -s 10.0.0.0/8 -j DROP

# Established
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# FORWARD
iptables -A FORWARD -i $LAN -j ACCEPT
iptables -A FORWARD -i $WAN -m state --state ESTABLISHED,RELATED -j ACCEPT

# Loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Accept
iptables -A INPUT -p icmp --icmp-type 0 -j ACCEPT # この辺りは使うので許可してあげます。不要な場合は外します。
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT

iptables -A INPUT -p tcp --dport 1234 -j ACCEPT # 自身で待ち受けるポート。LAN内の別のサーバーに転送したいものはここに書く必要はありません。

# SNAT (masquerade)
iptables -t nat -A POSTROUTING -o $WAN -j MASQUERADE

# Static NAT
hairpin_nat 192.168.1.2 tcp 80 # Webサーバーがある場合の例、これを必要な数だけ
hairpin_nat 192.168.1.2 tcp 443

あとはこのスクリプトをネットワーク起動後に読み込みます。Ubuntuなら/etc/network/interfacesのpppの設定のところに

auto dsl
iface dsl inet ppp
        pre-up /sbin/ifconfig eth0 up # line maintained by pppoeconf
        post-up /etc/network/iptables.up
        provider dsl

的な感じで、post-upから呼び出します。post-upのファイル名は環境に応じて変えてください。更新した場合は直接ファイルを呼び出してあげれば大丈夫です。

ファイルの中身についての説明はめんどくさいので省略しますが、DEFAULT RULEは除くとして基本的に同じchainについて複数のルールが該当する場合、先に呼び出された方のルールが適用されるはずです。ソースは忘れました。なので、「○○したいが、例外的に□□な時は△△したい」時は後者を先に書きます。

一年後、そこにはVPNと外部ネットワーク、そして攻撃してくる輩をブロックする記述で秘伝のタレ化したiptables設定ファイルが!

# 基本が自分用のメモなのでこのブログを常体で書くか敬体で書くか微妙に悩ましかったのですが、敬体を基本にしてみました。違和感ないですね。

(一部仕様を勘違いしていた部分があり随時修正しています。)