Vagrant+DockerでPPPoEサーバーをさくっと構築する

# 初めに

プロバイダーとインターネット接続をする上で今だに現役なPPPoE(Point-to-Point Protocol over Ethernet)。YAMAHAルーターやCiscoルーター等のNW機材のキッティング時に、インターネット接続までカバーできるテスタブルな環境を作りたく、今回はPPPoEサーバーのローカル構築にチャレンジしてみました。

本記事では、PPPoEサーバーの構築からテスト、テスト中におけるパケットキャプチャの様子等を掲載しています。何か不備があればコメントいただけますと幸いです。

# 必要なもの

![20200224_Qiita記事.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128123/4fe4c3d2-dc94-3fd5-4581-f465cf947c09.png)

Fig 1: 今回構築する構成


- 使用していないNICがあるマシン (実機)
  - 本Qiita記事ではWindows10のインストールされたデスクトップPCを利用
  - 今回の記事では [CableCreation 超高速USB 3.0 to RJ45 ギガビットイーサネットアダプタ](https://www.amazon.co.jp/dp/B013G4C8RE/ref=cm_sw_r_tw_dp_U_x_KSOuEb9NSG16N)を利用してNICを増設

- VirtualBox (図中のVMを動かすためのベースになる)
  - https://www.virtualbox.org/ からダウンロード可能

- Vagrant (図中のVMを制御するのに必要になる)
  -  https://www.vagrantup.com/downloads.html　からダウンロード可能

- Docker (今回の主役であるPPPoEサーバーをさくっと動かすために必要)
  - ※後程手順にてインストール方法を明記

- PPPoEクライアント機能を有するルーター (構築後のテストで利用)
  - 今回は手元の[EdgeRouter ER-X](https://www.amazon.co.jp/Ubiquiti-Networks-ER-X-Edgerouter-ER-X%EF%BC%88%E6%97%A5%E6%9C%AC%E5%9B%BD%E5%86%85%EF%BC%89/dp/B010MZFH5A)を利用

# PPPoEサーバー構築手順

## Vagrantを用いた環境立ち上げ (実機内作業)

まず実機内部に仮想環境を立てるために、Vagrantfileを記述していきます。

- VM Boxとして`bento/ubuntu-18.04`を利用

- ホスト側のNIC `Realtek USB GbE Family Controller`とブリッジ設定
  - 下記設定だとVM上でeth1として認識されます。(eth0はVagrant側が自動生成)

```ruby:Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-18.04"
  config.vm.network "public_network", bridge: "Realtek USB GbE Family Controller"
end
```

上位Vagrantfileを作業用ディレクトリに配置し、`vagrant up`でVMの立ち上げを行います。

```shell-session
C:\Users\hiroyuki\ubuntu>vagrant up
C:\Users\hiroyuki\ubuntu>vagrant ssh
```

## VM環境のセットアップ (VM内作業)

実際の構築作業に入る前に、必要そうなモノやパッケージのアップデートを済ませちゃいます

```shell-session
vagrant@vagrant:~$ sudo apt update
vagrant@vagrant:~$ sudo apt upgrade
vagrant@vagrant:~$ sudo apt install -y vim curl wget tmux htop tcpdump
```

## Dockerのインストール (VM内作業)

さくっと構築したかったのでスクリプトによるインストールで進めます。

```shell-session
vagrant@vagrant:~$ curl -fsSL https://get.docker.com -o get-docker.sh
vagrant@vagrant:~$ sh get-docker.sh
```

※　全体的なインストールの方法に関しては下記の記事にて詳しく解説されています。
https://qiita.com/tkyonezu/items/0f6da57eb2d823d2611d

## PPPoEサーバー用のDockerイメージを生成 (VM内作業)

下記3つのファイルをVM上の作業用ディレクトリに配置します。
※一旦繋がる事ファーストでざくっと設定しています。本来であれば細かく設定する必要性があります。


```dockerfile:Dockerfile
FROM ubuntu:18.04
RUN apt update
RUN apt install pppoe iptables iproute2 iputils-ping -y
ADD ppp_options /etc/ppp/options
ADD pap_secrets /etc/ppp/pap-secrets
ADD chap_secrets /etc/ppp/chap-secrets
CMD pppoe-server -I eth1 -L 172.16.0.254 -O /etc/ppp/options -F
```

```:ppp_options
require-pap
require-chap
#login
lcp-echo-interval 10
lcp-echo-failure 2
#defaultroute
#noipdefault
```

```:pap_secrets
testuser1 * "password" 172.16.0.101
testuser2 * "password" 172.16.0.102
```

```:chap_secrets
testuser1 * "password" 172.16.0.101
testuser2 * "password" 172.16.0.102
```

- eth1でPPPoEサーバーをLISTEN
  - 便宜上、PPPoEサーバーのIPアドレスとして172.16.0.254を付与

- 今回はPPPoEのログインユーザーとして下記のテストアカウントを生成しています
  - Username: `testuser1` Password: `password`
  - Username: `testuser2` Password: `password`

- PAPとCHAP両方有効化しています
  - 参考: https://xtech.nikkei.com/it/article/COLUMN/20060424/236003/
  - ※PAPは確か平文で認証を進めるので、本番で使う認証設定からは外しておいたほうが良いです

ファイルの配置や設定チューニングを終えたら、下記のコマンドでDockerイメージのビルドを行います。

```shell-session
vagrant@vagrant:~/pppoe$ docker build -t pppoe-server .
Sending build context to Docker daemon  6.144kB
Step 1/9 : FROM ubuntu:18.04
18.04: Pulling from library/ubuntu
423ae2b273f4: Pull complete                                                                                                                                                                                                                  de83a2304fa1: Pull complete                                                                                                                                                                                                                  f9a83bce3af0: Pull complete                                                                                                                                                                                                                  b6b53be908de: Pull complete                                                                                                                                                                                                                  Digest: sha256:04d48df82c938587820d7b6006f5071dbbffceb7ca01d2814f81857c631d44df
Status: Downloaded newer image for ubuntu:18.04
 ---> 72300a873c2c
Step 2/9 : RUN apt update
 ---> Running in 423689a0ed6c

(超省略)

Successfully built de86a59e0727
Successfully tagged pppoe-server:latest
```

pppoe-server:latestというDockerイメージが生成できました。

## PPPoEサーバーの立ち上げ (VM内作業)

下記コマンドでPPPoEサーバー機能を有する先ほどビルドしたイメージを動かします。

```
vagrant@vagrant:~/pppoe$ docker run --privileged --name srv --rm -it --net=host pppoe-server
```

正常に立ち上がれば、何も出てこないでコンテナが待機状態になります。

※ある程度強いcapabilityをつけてあげないとPPPoE・PPPのリンク確立に必要なパケット送出ができないようだったので、一旦`--privileged`をつけています。

※ `--net=host`をつける事によってコンテナとホストのネットワーキングが等価的に扱えるようになります。

## NATの設定 (VM内作業)

PPPoEクライアントになるルーターがインターネットに接続できるようにするには、VM上のeth0からインターネットに抜けていく必要性があります。接続してきた複数のPPPoEクライアントがeth0(固定IP)を共有してインターネットに抜ける為には、IPマスカレードの設定が必要になります。

```shell-session
vagrant@vagrant:~/pppoe$ sudo su -
root@vagrant:~# iptables -P FORWARD ACCEPT
root@vagrant:~# iptables --table nat -A POSTROUTING -o eth0 -j MASQUERADE
```

※iptablesの操作にはroot権限が必要です

# PPPoE 実機テスト (/w EdgeRouter)

![EReKF02VAAEXIB8.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128123/5303833a-af77-13db-bdca-c5e183bf2817.jpeg)

画像真ん中の黒い箱は今回のテストで使うルーターのEdgeRouter ER-Xです。
(PCにUSB接続されているのが、USB3.0接続のイーサーネットアダプタです。これがVM内部のeth1とブリッジ接続されています。)

このルーターにPPPoEにかかわる設定を流し込み、サーバーとクライアント側の挙動を確認してみます。

## クライアント側のPPPoE設定 (EdgeRouter内作業)

下記は、EdgeRouterのeth0をPPPoEクライアントにする為の設定です。（※一部抜粋）
SSH経由で設定しています。

```
set interfaces ethernet eth0 description 'Internet (PPPoE)'
set interfaces ethernet eth0 duplex auto
set interfaces ethernet eth0 pppoe 0 default-route auto
set interfaces ethernet eth0 pppoe 0 firewall in name WAN_IN
set interfaces ethernet eth0 pppoe 0 firewall local name WAN_LOCAL
set interfaces ethernet eth0 pppoe 0 mtu 1492
set interfaces ethernet eth0 pppoe 0 name-server auto
set interfaces ethernet eth0 pppoe 0 password password
set interfaces ethernet eth0 pppoe 0 user-id testuser1
set interfaces ethernet eth0 speed auto
```

正しく設定できると、下記のようにEdgeRouter側の内部インターフェースにpppoe0が誕生し、PPPoEサーバーからIPアドレス(172.16.0.101)が付与されている事が確認できます。

```shell-session
ubnt@ubnt:~$ show interfaces
Codes: S - State, L - Link, u - Up, D - Down, A - Admin Down
Interface    IP Address                        S/L  Description
---------    ----------                        ---  -----------
eth0         -                                 u/u  Internet (PPPoE)
eth1         -                                 u/u  Local
eth2         -                                 u/D  Local
eth3         -                                 u/D  Local
eth4         -                                 u/D  Local
lo           127.0.0.1/8                       u/u
             ::1/128
pppoe0       172.16.0.101                      u/u
switch0      192.168.1.1/24                    u/u  Local
```

残念ながらこの時コンテナ側に直接ログはでませんが、PPPoEクライアントが接続してくるとVM上にppp0という仮想NICが誕生します。

```shell-session
15: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1492 qdisc fq_codel state UNKNOWN group default qlen 3
    link/ppp
    inet 172.16.0.254 peer 172.16.0.101/32 scope global ppp0
       valid_lft forever preferred_lft forever
```

接続自体は問題ないようです。

## PPPoE経由でのインターネット接続テスト (EdgeRouter内作業)

EdgeRouter内部では、pingコマンドを使用する事ができます。PPPoE機能とNAT機能が正常に働いていれば、下記のようにインターネット側とやり取りすることができるようになります。

※下記は、Google Public DNSに対してPingをうっているところ

```shell-session

ubnt@ubnt:~$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_req=1 ttl=53 time=4.77 ms
64 bytes from 8.8.8.8: icmp_req=2 ttl=53 time=5.07 ms
64 bytes from 8.8.8.8: icmp_req=3 ttl=53 time=6.78 ms
64 bytes from 8.8.8.8: icmp_req=4 ttl=53 time=4.77 ms
64 bytes from 8.8.8.8: icmp_req=5 ttl=53 time=5.09 ms
64 bytes from 8.8.8.8: icmp_req=6 ttl=53 time=4.66 ms
^C
--- 8.8.8.8 ping statistics ---
6 packets transmitted, 6 received, 0% packet loss, time 5005ms
rtt min/avg/max/mdev = 4.663/5.195/6.784/0.734 ms
```

# TCPDUMPの様子 (余談)

上記テストに合わせてVM側でパケットキャプチャを走らせてみました。

## PPPoEセッション確立時

下記は、PPPoEサーバーを立ち上げてクライアントをつないだ直後のパケットキャプチャログです。eth1にPPPoEクライアントとの接続がブリッジされているので、tcpdumpでeth1をキャプチャするとPPPoEのセッション確立の様子を確認する事ができます。

```
vagrant@vagrant:~/pppoe$ sudo tcpdump -i eth1                                                                                                                                                                                                tcpdump: verbose output suppressed, use -v or -vv for full protocol decode                                                                                                                                                                   listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes                                                                                                                                                                    14:49:28.474689 PPPoE PADI [Service-Name] [Host-Uniq 0xBB050000]                                                                                                                                                                             14:49:28.474803 PPPoE PADO [AC-Name "vagrant"] [Service-Name] [AC-Cookie 0x6DAF64B9F320639F6F4690641530F95606000000] [Host-Uniq 0xBB050000]                                                                                                  14:49:28.475344 PPPoE PADR [Service-Name] [Host-Uniq 0xBB050000] [AC-Cookie 0x6DAF64B9F320639F6F4690641530F95606000000]                                                                                                                      14:49:28.475878 PPPoE PADS [ses 0x1] [Service-Name] [Host-Uniq 0xBB050000]                                                                                                                                                                   14:49:28.479829 PPPoE  [ses 0x1] LCP, Conf-Request (0x01), id 1, length 16                                                                                                                                                                   14:49:29.504654 PPPoE  [ses 0x1] LCP, Conf-Request (0x01), id 1, length 21
14:49:29.505647 PPPoE  [ses 0x1] LCP, Conf-Ack (0x02), id 1, length 21
14:49:31.483980 PPPoE  [ses 0x1] LCP, Conf-Request (0x01), id 1, length 16
14:49:31.484384 PPPoE  [ses 0x1] LCP, Conf-Ack (0x02), id 1, length 16
14:49:31.484433 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 0, length 10
14:49:31.484512 PPPoE  [ses 0x1] CHAP, Challenge (0x01), id 26, Value 3ecfc61be143d0f6bf6c250e1b04d66e, Name vagrant
14:49:31.486300 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 0, length 10
14:49:31.486766 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 0, length 10
14:49:31.486937 PPPoE  [ses 0x1] CHAP, Response (0x02), id 26, Value 17fcbd460345eec4fd9c08dae0c5f8fc, Name testuser1
14:49:31.487002 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 0, length 10
14:49:31.487940 PPPoE  [ses 0x1] CHAP, Success (0x03), id 26, Msg Access granted
14:49:31.489485 PPPoE  [ses 0x1] IPCP, Conf-Request (0x01), id 1, length 24
14:49:31.522656 PPPoE  [ses 0x1] CCP, Conf-Request (0x01), id 1, length 17
14:49:31.522693 PPPoE  [ses 0x1] IPCP, Conf-Request (0x01), id 1, length 18
14:49:31.522703 PPPoE  [ses 0x1] IPCP, Conf-Reject (0x04), id 1, length 18
14:49:31.523804 PPPoE  [ses 0x1] CCP, Conf-Request (0x01), id 1, length 6
14:49:31.523813 PPPoE  [ses 0x1] CCP, Conf-Reject (0x04), id 1, length 17
14:49:31.524041 PPPoE  [ses 0x1] IPCP, Conf-Reject (0x04), id 1, length 12                                                                                                                                                                   14:49:31.524326 PPPoE  [ses 0x1] IPCP, Conf-Request (0x01), id 2, length 12                                                                                                                                                                  14:49:31.524442 PPPoE  [ses 0x1] CCP, Conf-Ack (0x02), id 1, length 6                                                                                                                                                                        14:49:31.524466 PPPoE  [ses 0x1] CCP, Conf-Request (0x01), id 2, length 6
14:49:31.524549 PPPoE  [ses 0x1] IPCP, Conf-Request (0x01), id 2, length 12
14:49:31.524688 PPPoE  [ses 0x1] IPCP, Conf-Nack (0x03), id 2, length 12
14:49:31.525465 PPPoE  [ses 0x1] CCP, Conf-Ack (0x02), id 2, length 6
14:49:31.525762 PPPoE  [ses 0x1] IPCP, Conf-Ack (0x02), id 2, length 12
14:49:31.526042 PPPoE  [ses 0x1] IPCP, Conf-Request (0x01), id 3, length 12
14:49:31.527468 PPPoE  [ses 0x1] IPCP, Conf-Ack (0x02), id 3, length 12
```

- PPPoE Active Discovery Initiation (PADI)
  - クライアント→サーバー側への接続要求パケット
  - Macアドレスの宛先がBroadcast (ff:ff:ff:ff:ff:ff)が送出されるらしい
- PPPoE Active Discovery Offer (PADO)
  - サーバ→クライアント
  - AC-Name等の情報を送る

- PPPoE Active Discovery Request (PADR)　クライアント→サーバ

- PPPoE Active Discovery Session-confirmation (PADS)
  - サーバ→クライアント
  - セッション確立に関する通知

- PPP Link Control Protocol (LCP)
  - リンク確立
- PPP Challenge Handshake Authentication Protocol (CHAP)
  - ユーザー認証を実施
- PPP Internet Protocol Control Protocol (IPCP)
  - IPアドレスの割り当て、ヘッダ圧縮する？等

## PPPoEセッション確立後

リンク確立後、無通信状態でもお互いのハートビートは継続しているようです。
（結構頻繁にEcho-RequestとReplyを投げ合っている)

```
14:49:41.490006 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 1, length 10
14:49:41.490981 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 1, length 10
14:49:41.493627 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 2, length 10
14:49:41.494045 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 2, length 10
14:49:46.500887 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 3, length 10
14:49:46.501698 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 3, length 10
14:49:51.495710 PPPoE  [ses 0x1] LCP, Echo-Request (0x09), id 2, length 10
14:49:51.496654 PPPoE  [ses 0x1] LCP, Echo-Reply (0x0a), id 2, length 10
```

PPPoE経由で通信が発生すると、下記のようにPPPoE(PPP)で包まれたパケットを確認する事ができます。

```
16:06:17.343282 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 4, length 64
16:06:17.348379 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 4, length 64
16:06:18.344719 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 5, length 64
16:06:18.349681 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 5, length 64
16:06:19.345474 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 6, length 64
16:06:19.349738 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 6, length 64
16:06:20.346956 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 7, length 64
16:06:20.352335 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 7, length 64
16:06:21.347666 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 8, length 64
16:06:21.353322 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 8, length 64
16:06:22.348577 PPPoE  [ses 0x2] IP 172.16.0.101 > dns.google: ICMP echo request, id 2610, seq 9, length 64
16:06:22.353444 PPPoE  [ses 0x2] IP dns.google > 172.16.0.101: ICMP echo reply, id 2610, seq 9, length 64                                                                                         
```

# まとめ

今回はVagrant+Dockerを用いてPPPoEサーバーを構築し、PPPoEのクライアント機能を有する実機(EdgeRouter)からの接続テストをしました。PPPoE接続に加えてNAT機能によってインターネットアクセスができている状態まで確認する事ができました。

また、Vagrant上のVMでTCPDUMPを用いる事でPPPoEセッションが確立される様子、パケットがカプセリングされてやり取りされる様子も確認できました。(当たり前ではありますが、PPPoEは暗号化機能を提供しないので、ある程度通信の中身っていうのは見れちゃうんだなと再認識。）

# 最後に

今回構築した環境を使うことでNTT東日本・NTT西日本等の回線事業者側の機能(PPPoEサーバー+NAT)を模擬できるようにったので、NW機器をフィールドに直接もっていく前にある程度ローカル環境でネットワーク接続のテストができるようになりました。趣味でも業務でも、この記事が何かの役に立てばと思います。