tproxy 構築メモ


http proxy を使わないと外に出れない環境で、事実上 proxy 設定を入れることができない場合、transparent proxy (intercepting proxy) を設置することで解決できる。通称 tproxy。その構築メモ。tproxy とは直接は関係ないけれど、ここでは MASQUERADE の機能も同時に入れている。

raspi2にarchlinux入れる。USB NIC追加する(`eth1`)。`eth0` が uplink とする。

とりあえず満たしたい要件は dual-stack masquerade, http tproxy の二つ。

sysctl
-----
`/etc/sysctl.d/nat.conf` に以下の内容を保存する。これは ipv6 forwarding が有効になった時に、uplink から貰った経路を使い続けるため。

```text
net.ipv6.conf.eth0.accept_ra=2
```

systemd-networkd
----------------
`/etc/systemd/network/eth.network/eth0.network` に `IPForward=yes` を追加する。ちなみにこれは sysctl より後に実行されて設定を上書きすることに注意。「sysctlに設定したからオッケー」とはならない。また uplink から受け取った DNS サーバを使いたいので `UseDomains=yes` を追加する。

eth1 に static アドレス割り当てする。[マニュアル](http://www.freedesktop.org/software/systemd/man/systemd.network.html)を見ながら、同様に `/etc/systemd/network/eth1.network` を作る。内容はこんな感じ。IPMasqueradeオプションもあるけど、iptablesで他のものを設定するついでがあるので、ここではやらなかった。

```text
[Match]
Name=eth1

[Network]
Address=192.168.0.1/24
Address=fd00::1/64
IPForward=yes
```

systemd-resolved
------------------
`FallbackDNS=` という行を追加しておく。さもないと[マニュアル](http://www.freedesktop.org/software/systemd/man/resolved.conf.html)にある通り組み込みのDNSサーバが参照されてしまうから。そしてなぜか組み込みのDNS サーバは google のものだったりする。uplink proxy server を名前で登録する場合には、引けない名前を引きに行って timeout するとか起こるので、明示的に無効化しておく。

dnsmasq
--------
IPv4, IPv6 dual stack で対応してくれる。`eth1` に割り当てた IP アドレスと対応をとり、同一アドレス帯になるようにすること。

```text
dhcp-range=192.168.0.50,192.168.0.150,12h
dhcp-range=fd00::, ra-stateless
```

`systemctl enable dnsmasq` で起動登録する。

iptables
------
通常の MASQUERADE の設定にさらに、[squid の設定](http://wiki.squid-cache.org/Features/Tproxy4)を参考に TPROXY の設定を入れる。どちらも tproxy 的には dev `lo` port 3128 で待ち受けるようにした。

```bash
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i eth1 -o eth0 -j ACCEPT

ip6tables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
ip6tables -A FORWARD -i eth0 -o eth1 -m state --state RELATED,ESTABLISHED -j ACCEPT
ip6tables -A FORWARD -i eth1 -o eth0 -j ACCEPT

iptables -t mangle -N DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 1
iptables -t mangle -A DIVERT -j ACCEPT
iptables -t mangle -A PREROUTING -d 10.0.0.0/8 -j ACCEPT
iptables -t mangle -A PREROUTING -d 172.16.0.0/12 -j ACCEPT
iptables -t mangle -A PREROUTING -d 192.168.0.0/16 -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 1 --on-port 3128
iptables -t mangle -A PREROUTING -p tcp --dport 443 -j TPROXY \
  --tproxy-mark 1 --on-port 3128
iptables -t mangle -A PREROUTING -p tcp --dport 11371 -j TPROXY \
  --tproxy-mark 1 --on-port 3128

ip6tables -t mangle -N DIVERT
ip6tables -t mangle -A DIVERT -j MARK --set-mark 1
ip6tables -t mangle -A DIVERT -j ACCEPT
ip6tables -t mangle -A PREROUTING -d fec0::/10 -j ACCEPT
ip6tables -t mangle -A PREROUTING -d fc00::/7 -j ACCEPT
ip6tables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT
ip6tables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY \
  --tproxy-mark 1 --on-port 3128
ip6tables -t mangle -A PREROUTING -p tcp --dport 443 -j TPROXY \
  --tproxy-mark 1 --on-port 3128
ip6tables -t mangle -A PREROUTING -p tcp --dport 11371 -j TPROXY \
  --tproxy-mark 1 --on-port 3128

# 起動登録
iptables-save > /etc/iptables/iptables.rules
ip6tables-save > /etc/iptables/ip6tables.rules
systemctl enable iptables
systemctl enable ip6tables
```

https_tproxy
-----
無かったので作った。

```bash
$ export GOPATH=$HOME
$ go get github.com/hkwi/https_tproxy
```

tproxy.service
----
[squid の設定](http://wiki.squid-cache.org/Features/Tproxy4)を参考にした際のiptables以外、ip rule, ip route や ebtables のエントリを入れる。起動用のファイルは次のように整えてみた。`systemctl enable tproxy` で有効にする。

```text:/etc/systemd/system/tproxy.service
[Unit]
Description=tproxy

[Service]
Type=oneshot
ExecStart=/etc/tproxy
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target
```

ebtables は Linux kernel で、DST が自分自身でないパケットを自分自身に向かうようにする（tproxy）ために必要。逆向きが必要ないのは、MASQUERADE しているため。

```bash:/etc/tproxy
#!/bin/bash
ip -4 rule add fwmark 1 lookup 100
ip -4 route add local default dev lo table 100
ip -6 rule add fwmark 1 lookup 100
ip -6 route add local default dev lo table 100

ebtables -t broute -A BROUTING -i eth1 -p ipv6 \
  --ip6-proto tcp --ip6-dport 80 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv4 \
  --ip-proto tcp --ip-dport 80 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv6 \
  --ip6-proto tcp --ip6-dport 443 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv4 \
  --ip-proto tcp --ip-dport 443 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv6 \
  --ip6-proto tcp --ip6-dport 11371 -j redirect --redirect-target DROP
ebtables -t broute -A BROUTING -i eth1 -p ipv4 \
  --ip-proto tcp --ip-dport 11371 -j redirect --redirect-target DROP

GOMAXPROCS=4 /root/bin/https_tproxy -out=$HTTP_PROXY 2>&1 |& logger &
```

解説
----
本家本元の解説は Linux kernel source tree の `Documentation/networking/tproxy.txt` にある。ポイントは、TCP socket を通常と同じように開いて扱うことができる＋Listen しているポートはダミーで、本来の宛先を socket name として取得できる、というところでしょう。

squid でできるんじゃないか…というのは、http proxy 時に [cache_peer](http://www.squid-cache.org/Doc/config/cache_peer/) 設定で parent cache server を指定できる、ということだと思いますが、これはダメでした。parent cache server に接続できなかった場合に、自分で外部に接続しに行こうとします。それから https の tproxy は実装がありません。ということで、私は自作に切り替えました。

tproxy にもデメリットはあります。プロクシ通信路でエラーがあった場合に、ブラウザがエラーを認識できないのが原因で 1) エラーをユーザに提示できない 2) ブラウザがリトライを試行しない というものがあります。

IPv6 には masquerade なんて無い！という意見があるかもしれません。が、実際現実問題として DHCPv6 でステートフルにアドレスが割り当てられていて、かつ、Prefix delegation が無かった時にどうするかというと、masquerade せざるを得ません。

port 11371 は `hkp` で、gpg key server から鍵をダウンロードする際に使用されます。特に docker build の最中に key ダウンロードがある場合など、これも tproxy に入れておいたほうが簡単です。

追記：tproxy の部分は、今までに見つけた範囲では、次のプログラムでも実現できそうです。他にもありそうでしたら教えてください！

- [transproxy](https://qiita.com/wadahiro/items/7fa852b7217940e6ef26)
- https://github.com/tinyproxy/tinyproxy