birdiptablesのnatテーブルを使わずに透過プロキシ(transparent proxy)を実現する, Outbound port25 blocking (OP25B) 対応, きょうのつぶやき@digitune

iptablesのnatテーブルを使わずに透過プロキシ(transparent proxy)を実現する

自宅のインターネット回線を自宅までEthernetで配線されていたUCOM spaaqs光からフレッツ網+VDSLを利用するmioひかりに乗り換えたことで、実行スループットが100Mbps→50Mbps以下へと激減してしまった1点を少しでも挽回すべく、家庭内LAN内に透過プロキシーを立ててみようと思い立ちました。

おうちLANはこれまで、当然ですがprivate addressを使った1セグメントで構成されていて、このサイトもサービスしているおうちサーバはあれどDHCPサーバやDNSなどは全て、NECのwifiルータ、Aterm WG1800HPにより実現されていました。当初の構想では、DHCPサーバの設定を変えておうちLANにつながる全てのclientのdefault routeをおうちサーバに向け2、その上で透過プロキシとしてsquidを動かせば比較的簡単に実現出来るのではないか、と考えました。

「squid 透過プロキシ」などでググると例えばこちらこちらのページなどがヒットしますが、どちらもiptablesのnatテーブルを利用しています。そこでとりあえずおうちサーバ上でiptablesが使えるかどうかを確認してみると、2年前にビルドしたカーネルにはiptables関連のmoduleが含まれていないことがわかりました。というわけでまずはカーネルをリビルドするところから開始。ところが今回最初にビルドしたカーネルは、前回と全く同じ手順を踏んだにも関わらず、起動はしたもののxfsファイルシステムである/や/homeをマウントしようとすると「ファイルシステムが壊れている」と言われてしまってまともに動かない、という代物だったため、慌てて元に戻しました。原因を考えてみたんですが、前回ビルドした2年前から比べると、システム標準のgccのバージョンが4.4から4.6に上がっています。昔からlinux kernelはgccのversionに依存があると言われていましたので、おそらくそれが原因と当たりをつけ、シンボリックリンクである/usr/bin/gccをgcc-4.6からgcc-4.4に切り替えた上で再ビルドしたところ、今度はこれまでどおり動くカーネルが出来ました。今回、どうせなら、ということでnetwork/router系のmoduleを軒並み追加してみましたが3、参考までに最終的な構成となったおうちサーバでlsmodした結果は以下の通りです。意外にたくさんmodule使ってますね(^^;。

$ lsmod  
Module                  Size  Used by  
xt_tcpudp               1916  5  
xt_TPROXY               2269  1  
nf_tproxy_core           817  1 xt_TPROXY  
xt_socket               1799  1  
nf_defrag_ipv4           979  2 xt_socket,xt_TPROXY  
xt_mark                  814  1  
iptable_mangle          1027  1  
iptable_filter           903  0  
ip_tables              10204  2 iptable_filter,iptable_mangle  
x_tables               11726  7 ip_tables,iptable_filter,iptable_mangle,xt_mark,xt_socket,xt_TPROXY,xt_tcpudp  
usblp                   9694  0  
usb_storage            35718  1  
uhci_hcd               19597  0  
ohci_hcd               16648  0  
ehci_hcd               35116  0  
usbcore               117168  6 ehci_hcd,ohci_hcd,uhci_hcd,usb_storage,usblp  
usb_common               592  1 usbcore  

さて、カーネルのコンパイルも無事に完了し、aptitude/apt-getでsquidも入れて、さあ透過プロキシを設定しようと前述したページなどを見つつiptablesの設定を入れようとしたところ、natテーブルに触った途端システムが無反応になる、という現象が発生しました。おうちサーバはディスプレイ等は繋がらないNASをベースにしたサーバなので、ネットワークが唯一のインターフェイスです。そのインターフェイスが無反応になってしまうため、再起動するしか手が無くなってしまいます。ちょっと調べてみたところ、iptable_natカーネルモジュールをmodprobleにてロードしただけで固まる、iptable_natモジュールロード後即リブートするように連続してコマンドを実行しても再起動してこないため、単にinterfaceがdownしているだけでなく、どうやらkernel panicしている様子、などが分かりました。うーむ困った。必要なモジュールが足りてない?とかnetwork interfaceが一つしかないとダメ?など、いろいろ当たりを付けて試してみましたが(後者はaliasを作るくらいしか方法ありませんでしたが…これがこの先の伏線に)、この問題はどうやっても回避できず、仕方なく別の方法を模索することにしました。これが、タイトルの「iptablesのnatテーブルを使わずに透過プロキシ(transparent proxy)を実現する」の由来です。ググると分かりますが、透過プロキシを作る手順はほぼnatテーブルを利用するものなので、なかなか望みの情報に行き当たるのに苦労しました。

iptablesの他のテーブル、filterテーブルやmangleテーブルは正常に利用出来ることが分かっていましたから、natテーブルを使わずに実現する方法を探して、まずはsocatやredirのようなuserlandのツールを使う方法(これらは基本port forwarderなので今回のおうちLAN用透過プロキシを立てるという意味では使えなかったかも)を探してみたのですがうまい手が見つからず、うーむといろいろ考えている中で、そういえばカーネルをリビルドしたとき、カーネルモジュールの中に「TPROXY」というものがあったことをふと思い出しました。

そういえばあれはどういうものなんだろう?と思い調べてみると、こんな文書が、またそこからリンクされているこんなそのものズバリな文書も見つかりました。おお!これを読む限り、iptablesのmangleテーブルのみでいけそうです。

というわけで、まずは後者のページに載っていた手順をほぼそのまま使い、カーネルパラメータはそのままで書かれた通りの設定だったためskip、squid3に以下の設定を追加、

http_port 3129 tproxy  

policy routingの設定をし、

ip -f inet rule add fwmark 1 lookup 100  
ip -f inet route add local default dev eth0 table 100  

最後にiptablesの設定を入れて、おうちサーバを透過プロキシに仕立て上げ4、おうちLAN内のclientを一つ、デフォルトルートをおうちサーバに向けて試してみました。と、いきなりloopが発生してしまったようでsquidのログが大量に出力され、慌てて「iptables -t mangle -F」にてルールをフラッシュ。ふぅ。

おうちサーバは普通のDebianサーバなので、自身がWebサーバにもなればそこから外部に向かってHTTP通信を行うことも定期的にあります。iptablesに設定するルールではそのあたりも考慮しなければならなかった模様。というわけで、最終的には以下のようなルールとしました。

# DIVERT chainを作るところは同じ  
iptables -t mangle -N DIVERT  
iptables -t mangle -A DIVERT -j MARK --set-mark 1  
iptables -t mangle -A DIVERT -j ACCEPT  
# 自分自身宛、また自分自身発のpacketはそのままACCEPT  
iptables -t mangle -A PREROUTING -p tcp -s 127.0.0.1/32 --dport 80 -j ACCEPT  
iptables -t mangle -A PREROUTING -p tcp -d 127.0.0.1/32 --dport 80 -j ACCEPT  
iptables -t mangle -A PREROUTING -p tcp -s 192.168.0.132/32 --dport 80 -j ACCEPT  
iptables -t mangle -A PREROUTING -p tcp -d 192.168.0.132/32 --dport 80 -j ACCEPT  
#  
iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT  
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 3129  

この状態でテスト用clientからアクセスしてみると、squidのログにはwebアクセスが記録されるのに全ての通信がその後connection timeoutになってしまう5、という状況となりました。先のページのFAQによると、こういう状態の時はsquidにちゃんとパケットが返ってきていないことが原因、routingを疑え、と書いてあります。ふむ。

上で書いたように、この時点ではまだおうちLANのネットワーク構成は192.168.0.0/24の単一サブネットとなっています。おうちサーバもテスト用clientも外へ出て行くwifiルータも同じ一つのサブネットに接続され、テストclientのみがdefault routerとしておうちサーバを向いているためpacketをそちらへ流す状態です。実はこの時点で僕はまだ、カーネルのTPROXYモジュールの動作を全く理解していませんでした。squidにまでtrafficが流れてきているとすると、外向けのHTTP connectionもproxyの常としてsquidが作成するはず、とすると、wifiルータとおうちサーバの通常通りの通信となって普通に通信出来るのではないか、と思っていました。

なんじゃらホイと思いつつ、自分でもTPROXYを使った上記設定の意味をほとんど理解していないことがちょっと気持ち悪かったため、まずはそちらを少し勉強してみることにしました。するとTPROXYを使ったtransparent proxyではNATを使った方法と違ってpacket上のclientのIPアドレスを変更しない、といった記述が目に入りました。なるほど、そうするともしかして今の状態では、テストclientから発信されたpacketはおうちサーバに届いた後squidを通ってwifiルータ経由で外へ出て行くものの、帰りのpacketはおうちルータを経由セずに直接テストclientに戻ってしまうためsquidがpacketを受け取れず、connection timeoutになってしまうのではないか?と予想しました。

仮にその予想が正しいとした場合、解決策としては戻りのpacketも全ておうちサーバを経由するようなネットワーク構成となっていればうまく動きそうです。そこで、おうちLANに新しいサブネット192.168.1.0/24を作成し、おうちサーバにnetwork interfaceのaliasを作成した上でそちらのセグメントのdefault routerとして設定、テスト用clientもそちらのネットワークに属するように構成してみました。具体的に行った作業としては下記の通り。

  1. まずそもそも、wifiルータのDHCPサーバ機能ではclientに配るdefault routerの設定を変更することも出来なかったので、wifiルータ側のDHCPサーバ機能を停止させた上でおうちサーバ上でisc-dhcp-serverを動作させました。その上で、テスト用clientのMACアドレスからのリクエストには192.168.1.0/24のネットワークのIPアドレスが割り当てられ、default routeもおうちサーバを向くように設定しました。
  2. 次に外から戻ってきたpacketがちゃんと192.168.1.0/24のネットワークにも届くように、wifiルータにスタティックルートとして「192.168.1.0/24ネットワークのゲートウェイはおうちサーバ」というルートを追加しました。

ここまで設定してから動作を確認すると、おお!ついにおうちサーバを透過プロキシとして正常に動作させることが出来るようになりました。パチパチパチ。

最後に、上記で行った設定が再起動したときにもちゃんと設定されるようにします。policy routingの設定はdebianの場合、/etc/network/interfacesファイルに書いておくのが流儀のようでしたので、以下のような記述を追加しました。

auto eth0:0  
iface eth0:0 inet static  
        address 192.168.1.1  
        netmask 255.255.255.0  
        post-up ip -f inet rule add fwmark 1 lookup 100  
        post-up ip -f inet route add local default dev eth0 table 100  

また、debianの場合iptablesの設定を保存するには「iptables-persistent」パッケージを使うのが流儀なようだったので、いつものようにaptitude/apt-getにてインストール後、rootで「service iptables-persistent save」を実行して設定を保存しました。設定は/etc/iptables/rules.v4に保存されます。

その後、おうちサーバ上のDHCPサーバの設定を変更して、おうちLANに繋がる全てのclientが192.168.1.0/24側のネットワークに属するようにして、今回の作業は完了。実際にはその後、local networkとして192.168.1.0/24をあちこちに追加し忘れていたことが分かって足して回ったり(/etc/hosts.allowとかpostfixやapacheの設定などなど)、ついでにおうちLAN内にDNSサーバ(bind9)も立ててキャッシュによる高速化+懸案だったおうちLAN内からのおうちサーバを直接叩けるようにしたりしましたが、それはまぁ別の話。

昔から一度立ててみたいと思っていつつ機会がなく試していなかった透過プロキシを今回立てることが出来て面白かった。実はLANに繋がっているclientが少ないと透過プロキシで行われるキャッシュよりもclient localでブラウザが行うキャッシュの方がずっと支配的になってほとんど効果がない、という結果になってしまうのが分かっていたためこれまで試すところまで行かなかったのですが、ゲーム機やスマホのような小さくリソースも限られたデバイスもたくさんInternetにアクセスするようになったこと、家族が複数デバイスを持つのが当たり前の状況になったことから、今ならそれなりに効果が出るのかもしれないと思ったりしています。

Outbound port25 blocking (OP25B) 対応

イマドキのインターネットプロバイダは、固定IPではない、一般会員向けの動的IP割り当てなclientに対しては、spam発信を防止するためにいわゆる「Outbound port25 blocking」という施策を行っています。つまり顧客側から外部のInternet上のhostに対して25番ポート6の通信は出来ないようにしている、という意味です。実はこれまで使っていたUCOMはイマドキのプロバイダでは無いらしく(^^;、その施策が実施されていなかったために、自宅LAN内で運用しているMTA (Postfix)ではまだその対応を入れていなかったのですね。

今回透過プロキシを立てる過程でおうちサーバに入り浸っていて、ふとmail queueに何通かメールが溜まっていることに気が付きました。/var/log/mail.logを読んでみるとoutboundなSMTP通信が全てtimeoutしていました。そこで初めて、Outbound port25 blockingの存在に気が付きました。

PostfixにおいてOP25B対応を行う方法 (というかSubmissionポート+SASL認証を使ってrelayhostへメール送信する方法) はググれば山ほど出てきますので省略しますが、当初Submissionポートを使ってもなおconnection timeoutになってしまってまたしてもなんじゃらホイと思っていたところ、よくよく調べたら僕が使っているプロバイダのメールサーバのFQDNが最近変更になっていた、というオチだったりしました(^^;。旧FQDNでも (おそらく過去からの互換性維持のために) 25番ポートによるメール送信は引き続き受け付けてくれているようなんですが、Submissionポート (587番ポート) を使った送信は新FQDNを使う必要があった、ということですね。ホントいろんなところに落とし穴があるなぁ…。

きょうのつぶやき@digitune

ISPを切り替えたことで超今さらながらoutbound port25 blockingに悩まされるなど(汗。言われてみればUCOMはイマドキblockしてなかったのですね。定跡通りrelayhostへの送信をSubmission+SASLへ切り替えることで対応。 (08:24 Twitter Web Clientから・詳細)

最初、単純に切り替えただけではうまくいかなくてなんじゃらホイと思ったら、実はもう20年近く使ってきた相手側メールサーバのホスト名がつい最近変更になっていたという…。全然気が付かんかった。古いホスト名でもまだ25番ポートだけは受け付けてくれていたんだね。 (08:45 Twitter Web Clientから・詳細)

3年ぶりくらいにおうちサーバにIMAPで接続しようとしたら、dovecotの設定が何故かMaildirではなくmboxを見るようになってて設定変更が必要だった。いつの間に元に戻っちゃったんだろ?たぶんOS updateしたときとかだろうなぁ。全然気がつかなかった(汗。 (10:12 Twitter Web Clientから・詳細)

未読が7万通とかあってビビる。お掃除しないと…。 (10:12 Twitter Web Clientから・詳細)

Gmailのメール転送機能は、「アーカイブしたメールだけを転送する」というオプションがあるべきだと思うよ。 (12:00 Twitter Web Clientから・詳細)

image 0まとめました>Digitune [memo] - iptablesのnatテーブルを使わずに透過プロキシ(transparent proxy)を実現する , Outbound port25 blocking (OP25B) 対応 http://memo.digitune.org/?date=20150412 (20:11 Twitter Web Clientから・詳細)

image 1アマチュアでも今はこのレベルで合成+マッチムーブな動画作れるんだなぁ。ゴイス>ぶれないカメラで撮りたかった、ぶれないアイで 【マッチムーブ】 - ニコニコ動画:GINZA http://www.nicovideo.jp/watch/sm25983565 (20:43 Twitter Web Clientから・詳細)


  1. 下りの値。上りはさらにひどく、100Mbps→20Mbps以下になってしまいました。最近のVDSLモデムは上り下り対称となっている、と聞いていたのですが、ウチの団地の場合はおそらく共用機材が古いのでしょう。うーむ。 ↩︎

  2. おうちサーバのdefault routeは当然wifiルータのままにしておきます。 ↩︎

  3. 依存するモジュールが無いことで生じる不具合があるのかもしれない、と考え。 ↩︎

  4. mangleテーブルに触るだけならば幸いなことに固まることもありませんでした。 ↩︎

  5. テストclientのブラウザにsquidからのconnection timeoutエラーが表示される、という状態。 ↩︎

  6. 25番はsmtp、つまりメール送信用のportですね。 ↩︎