Privoxyでプロキシのブリッジを行う

世界中で新型コロナウイルスが拡がっていますが、私の住むバンコクでも、[娯楽施設を中心として人が集まる施設の封鎖が命じられ](https://www.nikkei.com/article/DGXMZO57073890R20C20A3000000/)、会社はリモートワーク(Work From Home)に切り替え、[タイ入国には非感染証明書を求められ（日本では健常者に発行していない）](https://www.thaich.net/news/20200320wr.htm)、事実上国外へ移動できない生活が始まりました。

外食もできず、夜な夜な飲み歩くこともできず、家に引きこもり生活になったので、[以前トライしたものの実用性が低すぎたVPN接続](https://qiita.com/kakken1988/items/6e4a4ae0a9cdf49564a3)に代わって、興味本位でプロキシのブリッジを組んでみました。

# 作りたいネットワーク
![SOCKS Proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/4c6e292b-e30d-b0b2-3c73-523d5f5e47a0.png)

# 用意したもの

## サーバー側（日本）

すでに構築済みになりますが、Linuxが入っているマシンと、インターネット環境を用意しました。（前回と同じ）

* インターネット回線（NTTフレッツ光、固定IPがもらえる某プロバイダ）
* ルーター（中古の[RTX1100](https://network.yamaha.com/products/routers/rtx1100/index)）
* 自作PC（Debian Linux 10）

## ブリッジ側（海外）

Raspberry Pi以外は前回のものを流用しました。

* インターネット回線（[AIS Fibre HomeBOADBAND 50/20Mbps](http://www.ais.co.th/fibre/en/package_homebroadband.html) ※英語ページ）599THB/month（2,000円強/月）
* Raspberry Pi (新しく買ったModel 4）Raspbianインストール済み
* USB-LANアダプタ（余ってた[Logitec LAN-GTJU3](http://logitec.co.jp/products/lan/langtju3/index.php)）
* 無線LAN中継器（余ってた[TP-Link RE450](https://www.tp-link.com/jp/products/details/cat-10_RE450.html)）

※Raspberry Pi 4には無線LANアダプタも搭載しているので、下2つはそれに代えられると思います。

# SSHによるSOCKS Proxyの接続

あらかじめ、SSHサーバー側にユーザー `socks` を作成し、SSH鍵ペアを生成しておき、秘密鍵をRaspberry Piに転送しておきます。
`~/.ssh/id_rsa_server.example.com_socks` に保存しました。

SSH接続時にDynamic Forwardを有効にするには、 -D スイッチを付けます。

```sh
ssh socks@server.example.com -D 1080 -i ~/.ssh/id_rsa_server.example.com_socks
```
これで `localhost:1080` にSOCKSプロキシを立てることができましたが、さすがにこれで終わらせるわけにはいかないので、サービス化します。

```ini:/etc/systemd/system/socks-proxy-client.service
[Unit]
Description=SOCKS Client (SSH DynamicForward)
ConditionPathExists=|/usr/bin
After=network.target

[Service]
Type=simple
User=pi
ExecStart=/usr/bin/ssh -gNTC -D 1080 -i /home/pi/.ssh/id_rsa_server.example.com_socks -o 'ServerAliveInterval=60' -o 'ExitOnForwardFailure=yes' -o 'StrictHostKeyChecking=no' socks@server.example.com
Restart=always
RestartSec=3

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

`ssh` コマンドにいくつかオプションを設定していますが、以下のとおりです。

| オプション  | 説明 |
|:----------|------|
| `-g`      | port forward実施時（Local/Remote/Dynamicすべて）、 `0.0.0.0` でLISTENするようになる（※もしかしたら必要ないかも？）|
| `-N`      | リモートコマンドを実行しない（通常ログイン後の /bin/bash などを実行しない）ポートフォワード目的のみの場合に有用 |
| `-T`      | 擬似端末の割当を無効にする |
| `-C`      | 圧縮を有効にする |
| `-D 1080` | Dynamic port forwardingを1080ポートで開く |
| `-i ファイル名` | 秘密鍵 |
| `-o ServerAliveInterval=60`   | 60秒ごとにKeep Alive |
| `-o ExitOnForwardFailure=yes` | ポートフォワードに失敗したら終了する |
| `-o StrictHostKeyChecking=no` | ホストキーのチェックを無効にする（不要かも？） |

これを作成し、サービスを有効化、起動します。

```sh
$ sudo systemctl daemon-reload
$ sudo systemctl start socks-proxy-client
```

1080がLISTENできているかを確認します。

```sh
$ lsof -i:1080
COMMAND PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
ssh     983   pi    4u  IPv4  19905      0t0  TCP *:socks (LISTEN)
ssh     983   pi    5u  IPv6  19906      0t0  TCP *:socks (LISTEN)
```

# Privoxy

## インストール

```sh
$ sudo apt install privoxy
```

## 設定

Privoxyには、広告ブロックなどの機能もありますが、今回はそれを使うつもりがないので、無効化します。

コメント部分がとても長いので、変更した設定項目だけ抜粋します。

```nginx:/etc/privoxy/config
#  2.5. actionsfile
#  ================
#
#actionsfile match-all.action # Actions that are applied to all sites and maybe overruled later on.
#actionsfile default.action   # Main actions file
#actionsfile user.action      # User customizations
```
↑これら3行をコメントアウト

```nginx:/etc/privoxy/config
#  2.6. filterfile
#  ================
#
#filterfile default.filter
#filterfile user.filter      # User customizations
```
↑これら2行をコメントアウト

```nginx:/etc/privoxy/config
#  4.1. listen-address
#  ====================
#
#listen-address  127.0.0.1:8118
#listen-address  [::1]:8118
listen-address 0.0.0.0:8118
```
localhostをコメントアウトし、0.0.0.0を追加

```nginx:/etc/privoxy/config
#  5.2. forward-socks4, forward-socks4a, forward-socks5 and forward-socks5t
#  =========================================================================
#
forward-socks5 / localhost:1080 .
forward 10.*.*.*/    .
forward 127.*.*.*/   .
forward 172.16.*.*/  .
forward 192.168.*.*/ .
```

↑追加
一度すべてのリクエストを、SOCKSに転送するが、ローカルアドレス宛の場合は転送しない。

すべてのコメント行を省くと、このようなファイルになります。

```nginx:/etc/privoxy/config
user-manual /usr/share/doc/privoxy/user-manual
confdir /etc/privoxy
logdir /var/log/privoxy
logfile logfile
listen-address 0.0.0.0:8118
toggle  1
enable-remote-toggle  0
enable-remote-http-toggle  0
enable-edit-actions 0
enforce-blocks 0
buffer-limit 4096
enable-proxy-authentication-forwarding 0
forward-socks5 / localhost:1080 .
forward 10.*.*.*/    .
forward 127.*.*.*/   .
forward 172.16.*.*/  .
forward 192.168.*.*/ .
forwarded-connect-retries  0
accept-intercepted-requests 1
allow-cgi-request-crunching 0
split-large-forms 0
keep-alive-timeout 5
tolerate-pipelining 1
socket-timeout 300
```

## 起動

```sh
$ sudo systemctl enable privoxy
$ sudo systemctl start privoxy
```

8118がLISTENできているかを確認します。

```sh
$ netstat -an | grep "0.0.0.0:8118"
tcp        0      0 0.0.0.0:8118            0.0.0.0:*               LISTEN
```

# 二重ルーター化

これまでの手順でも、ブラウザやOSの設定で `192.168.1.105:1080` に向けてSOCKSプロキシを、もしくは `192.168.1.105:8118` に向けてHTTP(S)プロキシを設定すれば利用できます。
が、以下の二点の手間を省きたくて、二重ルーター化しました。

* 常にProxyを使いたいわけではない→ネットワークを分離したい
* クライアントにわざわざプロキシを設定するのが手間→透過プロキシにしたい

ネットワークは、 `eth0` がRaspberry Pi内蔵のLANコネクタであり `192.168.1.0/24` 、 `eth1` が外付LANアダプタであり `192.168.2.0/24` となります。
`192.168.2.0/24` に属しているクライアントは、プロキシ経由でのアクセスができるようになります。

※ADSL時代はよく「二重ルーターは悪だ！」みたいな流れをよく見ましたけど、今回はありかなと思ってます。

## IPフォワード許可設定

```sh
$ sudo sysctl -w net.ipv4.ip_forward=1
```

## DNSサーバー

### インストール

```sh
$ sudo apt install bind9
```

### 設定

`forwarders` のコメントアウトを外し、インターネット側ネットワークのDNSサーバーを指定したのみです。

```nginx:/etc/bind/named.conf.options
options {
        forwarders {
                192.168.1.1;
        };
}
```

IPv4しか使わないので、 `OPTIONS` に ` -4` を追加します。

```ini:/etc/default/bind9
#
# run resolvconf?
RESOLVCONF=no

# startup options for the server
OPTIONS="-u bind -4"
```

### 起動

```sh
$ sudo systemctl enable bind9
$ sudo systemctl start bind9
```

## DHCPサーバー

### インストール

```sh
$ sudo apt install isc-dhcp-server
```

### 設定ファイル例

```nginx:/etc/dhcp/dhcpd.conf
default-lease-time 600;
max-lease-time 7200;
ddns-update-style none;

authoritative;
option wpad code 252 = string;

subnet 192.168.2.0 netmask 255.255.255.0 {
  range 192.168.2.100 192.168.2.200;
  option subnet-mask 255.255.255.0;
  option routers 192.168.2.1;
  option broadcast-address 192.168.2.255;
  option domain-name-servers 192.168.2.1;
  option wpad "http://192.168.2.1/proxy.pac";  # ←後ほど解説
  ignore declines;
}
```

### 起動

```sh
$ sudo systemctl enable isc-dhcp-server
$ sudo systemctl start isc-dhcp-server
```

## nginx

DHCP設定で指定した、[WPAD (Web Proxy Auto-Discovery)](http://e-words.jp/w/WPAD.html)のためのサーバーです。

### インストール

```sh
$ sudo apt install nginx
```

### PACファイルの作成

PAC（プロキシ自動設定）ファイルについては、MDNが参考になります。
[プロキシ自動設定ファイル - HTTP | MDN](https://developer.mozilla.org/ja/docs/Web/HTTP/Proxy_servers_and_tunneling/Proxy_Auto-Configuration_(PAC)_file)

```javascript:/var/www/html/proxy.pac
function FindProxyForURL(url, host) {
    if (isInNet(host, "192.168.0.0", "255.255.0.0")) {
        return "DIRECT";
    }

    return "SOCKS 192.168.2.1:1080; PROXY 192.168.2.1:8118";
}
```

※ privoxyでローカルアドレスを避けているはずなので、if文はいらないかもです。

### 起動

```sh
$ sudo systemctl enable nginx
$ sudo systemctl start nginx
```

## iptables設定

iptablesがインストールされていなければインストールします。

```sh:/etc/iptables/rules.v4
# Generated by xtables-save v1.8.2 on Sun Mar 22 03:02:25 2020
*filter
:INPUT ACCEPT [8784577:1664198362]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [9256785:1527135805]
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -d 127.0.0.0/8 -j REJECT
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p tcp -m state --state NEW --dport 22 -j ACCEPT        # SSHサーバー
-A INPUT -p tcp -m state --state NEW --dport 53 -j ACCEPT        # DNSサーバー
-A INPUT -p udp --dport 53 -j ACCEPT                             # DNSサーバー
-A INPUT -i eth1 -p udp --sport bootpc --dport bootps -j ACCEPT  # DHCPサーバー(内側ネットワークのみ)
-A INPUT -p tcp -m state --state NEW --dport 80 -j ACCEPT        # Webサーバー
-A INPUT -p tcp -m state --state NEW --dport 1080 -j ACCEPT      # SOCKSプロキシ
-A INPUT -p tcp -m state --state NEW --dport 8118 -j ACCEPT      # Privoxy
-A INPUT -j REJECT
-A FORWARD -j REJECT
-A FORWARD -i eth1 -o eth0 -s 192.168.2.0/24 -j ACCEPT
-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -j ACCEPT
-A OUTPUT -o eth0 -d 10.0.0.0/8 -j DROP
-A OUTPUT -o eth0 -d 176.16.0.0/12 -j DROP
-A OUTPUT -o eth0 -d 192.168.0.0/16 -j DROP
-A OUTPUT -o eth0 -d 127.0.0.0/8 -j DROP
COMMIT
# Completed on Sun Mar 22 03:02:25 2020
# Generated by xtables-save v1.8.2 on Sun Mar 22 03:02:25 2020
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A PREROUTING -i eth1 -p tcp -m multiport --dports 21,80,443 ! -d 192.168.2.0/24 -j DNAT --to-destination 127.0.0.1:8118  # 内側ネットワークより、ローカルアドレス以外宛にポート21,80,443への通信があれば、Privoxyに渡す
-A POSTROUTING -o eth0 -s 192.168.2.0/24 -j MASQUERADE  # NAT設定
COMMIT
# Completed on Sun Mar 22 03:02:25 2020
```

## iptables適用

```sh
$ sudo iptables-apply /etc/iptables/rules.v4
```

# Raspberry Pi上で開いているポートの確認

```sh
$ netstat -an | grep "LISTEN "
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 192.168.1.105:53        0.0.0.0:*               LISTEN
tcp        0      0 192.168.2.1:53          0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:8118            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:1080            0.0.0.0:*               LISTEN
tcp6       0      0 :::5900                 :::*                    LISTEN
tcp6       0      0 :::80                   :::*                    LISTEN
tcp6       0      0 :::22                   :::*                    LISTEN
tcp6       0      0 :::1080                 :::*                    LISTEN
```

# 接続確認

iPadで試しました。

## Proxy非経由 (192.168.1.0/24)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/2eac8c1c-59f1-b5ef-9a3b-471e0c986a9e.png)

ちゃんとプロバイダのIPアドレスになっており、国判定も正しいです。

## Proxy経由 (192.168.2.0/24)
<img width="1112" alt="File copy 2.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/c290a80f-c8a9-c580-3849-2c18434f3f5f.jpeg">

あれ、うまくいっていない…。

はい、DHCPからWPADを使って教えたところで、どうもOSのプロキシ設定までは自動で変わらないようです。
なので、ちょっとだけWi-Fiの設定をいじります。
iOSの場合、Wi-Fi→アクセスポイント名→プロキシを構成、とたどると、初期値は「オフ」となっているので、「自動」に変更して「保存」をクリックします。
なお、この際、URL入力欄が現れますが、WPADのおかげで、未入力のままでも問題ありません。WPADを設定していない場合は、ここで先程のPACがファイルのアドレス `http://192.168.2.1/proxy.pac` を入力します。
<img width="1112" alt="File copy 3.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/d1664d83-e480-0797-5f5d-91c77a3514d9.jpeg">

この結果…

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/c96c066c-1c50-9c97-d8e5-8f5ee347398f.png)

無事に接続できました。
IPアドレスが、SSHサーバーのものとなっており、国判定も正しいです。

# 速度測定

以前VPNを組んだときは100Kbps程度の遅さで使い物にならなかったのですが…

<img width="834" alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/143240/5668b8e0-029c-8567-cf64-4ba4a2463983.png">

なぜ50Mbps以上出るのか謎ですけど、必要十分な速度は出そうです。
某IPサイマルラジオ放送も途切れず聞けました。

# 注意事項

セキュリティには注意してください。万が一、これを実践してトラブルがありましても、責任はとれませんので…。