Proxy ARPについて(proxy_arp, proxy_arp_pvlanの違い)

# Recap

Proxy ARPの説明に入る前に、念のためARPとは何か簡単にまとめてから、Proxy ARPについてまとめたいと思います。
ARPとは、[RFC826](https://tools.ietf.org/html/rfc826)で定義されている、IP AddressをEthernet Address(MACアドレス)に変換するプロトコルです。
一見、あーただのIPからMACを調べるやつね？って思いガチですが、例えばスイッチでいくつかのPORTでBroadcastを全てDropするような仕組み（現実的にあるのかは不明ですが）を導入すれば、BroadcastがDropされているPCへの通信を他所から制御(man in the middle attack)することが可能になったりと、面白い仕組みです。


ARPには大きく分けて

 * NICが所属しているL2ネットワークにおいてIPアドレスの持ち主を問うARP Request
 * 自身のNICがIPを所持していた場合に自分のMACアドレスに該当IPが割り当たっていることをARP Request発信元に知らせるARP Reply

があります。ClientがARP Requestを使ってIPアドレスからMACアドレスを調べるざっくりとした手順は下記です。

1. [IP(A)のMACを調べたい]
   * 下記のようなARP Requestパケットを作成し、送信します
      * source mac: 自身のMAC
      * dest mac  : ブロードキャスト or IP(A)のMACアドレス(ARP CacheのVerifyが目的の場合はMAC)
      * source IP : 自身のIP
      * dest IP   : IP(A)
2. [IP(A)を保持してるPC]
   * ARP送信元にunicastで、ARP Replayを返します
   * ARP Requestより、source IPと source macのペアがわかるのでARP Cacheのエントリに追加します
3. [IP(A)を保持してないPC] 
   * ARP Requestより、source IPと source macのペアがわかるのでARP Cacheのエントリに追加します

特徴として、ARP Requestをベースとして同じL2ネットワークに参加しているPCが自身のARP Cacheに追加するという挙動があるので、受動的にARP Cacheを構成することが可能です。

なので、Dummy IP宛てへのARP requestを虚偽Source IP, Source MACでブロードキャストで送ることで同じL2 Networkに参加しているPCに学習させることができます。これを踏まえるとSource IPを変更したARP requestを大量に送り続けることで途中かから参加したPCへ、実態と違うIPとMACのARP Cacheエントリを作成させることが可能です。

また、この特徴を生かして同じL2のPCのARP Cacheを無理やり更新させる(GARP)と呼ばれるARPも存在します。
こちらは、VIPで複数台で冗長化を組んでいる時に、仮想MACアドレスを設定していない場合にVIPが移動するとペアになるMACアドレスも変わるので、MACが変わったことを同じL2の全PCに知らせるためにこのGARPを飛ばすことがあります。このGARPは、特殊なタイプのARPパケットではなく、通常のARP Requestと同じようにパケットを作成し、dest IPを自身のsource IPに設定したものをGARPと呼びます。


# Proxy ARP

これは、ARP Requestを受け取ったPC自身は該当IPを所持していないが、到達可能なIPアドレス宛てへのARP Requestへ自身のMACで代理応答する仕組みです。

## モチベーション

多分いろんなモチベーションがあると思いますが、おそらく一番メジャーなモチベーションを説明したいと思います。例えば下記のような構成があったとして

<img width="592" alt="Screen Shot 2017-12-16 at 14.13.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/641ac891-d896-af59-7b7a-45e79ceaa431.png">

特殊なスイッチを用いていて、Router以外の端末間で通信(L2)ができない設定をしていたとします(図を書いてから思いましたが、全ServerのVLANを分けて同一サブネットにしてもよかったですね)
なので、下記のように、同一サブネットにも関わらず、192.168.0.2から192.168.0.3への通信ができません。というのも端末間での通信が制限されいているので、192.168.0.2からARP Requestを送信しても、192.168.0.3へは届きません。

<img width="569" alt="Screen Shot 2017-12-16 at 15.11.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/21ff1342-b823-3d33-994f-a89976dd54a7.png">

この構成で、Router(192.168.0.1)-端末(192.168.0.2)では通信ができるので、もし192.168.0.2 - 192.168.0.3への通信をRouterを介させることができたら、通信ができることになります。なのでやりたいことは、下記ような感じです

<img width="559" alt="Screen Shot 2017-12-16 at 15.12.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/96f1bf6a-16d2-ee33-cc93-64f05fecc8b2.png">

 
ただ、ここで問題なのがARP Recapで説明した通り、ARPは自身がIPを持っている場合のみARP Replyを返すという仕様ですので、192.168.0.2からARP Request(問192.168.0.3)が来ても、Routerは無視します。なので、通信ができません。

この問題を解決するために、あるのがProxy ARPです。Proxy ARPをOnにするとRouterは、192.168.0.2、192.168.0.3ともに **到達可能** ですので、それらのIP宛てのARP RequestにARP Reply(RouterのMAC)を返します。
そうすると、リクエスト元は、てっきりRouterがそのIPを持っていると思い込んでRouterに転送します。でそのあとRouterはもちろん該当IPを持っていないので、該当IPがあるInterfaceにそのパケットを転送し、本当にその該当IPを持っている端末へ転送され、上記の図のような通信が可能になります。

無線LANなどでは、端末間通信が制限されることが多いのでこのような仕組みが有効になります。


# Linuxの実装

実は、Linux Kernelで標準でProxy ARPを実施する機能が備わっているので特にソフトウェアをインストールせずに設定を有効にするだけで、Proxy ARPを動かすことができます。まぁ、それだけだったらこの記事は書いてないんですが、実は２つオプションがあって違いがわからなかったのでここにまとめました。

* /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp
* /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp_pvlan

簡潔に話すと、Proxy ARPする対象のIPが上記２つで違います。

## /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp


とりあえず、[Kernelドキュメント](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)から引用

> proxy_arp - BOOLEAN
>
>	Do proxy arp.
	proxy_arp for the interface will be enabled if at least one of
	conf/{all,interface}/proxy_arp is set to TRUE,
	it will be disabled otherwise

なるほど、情報が少ない。ので色々試してみると。下記のようなことがわかりました。

このproxy_arpを有効にするとLinux Routerから見てもARPの宛先IPには、フォワーディング(in interfaceとout interfaceが別になる転送とここでは定義します)が必要だと判断したARP Requestに対してのみProxy ARP Replyを返します。
下記のような、構成の場合このオプションをONにしても192.168.0.2から192.168.0.0/24のサブネットに所属するIP宛てには、Proxy ARPを返しません。


<img width="589" alt="Screen Shot 2017-12-16 at 16.59.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/07bca896-1adb-9c4b-3d03-58c9ff5b0e6b.png">


たとえスイッチ側で、端末間でのL2リーチャビリティがなかったとしても、Linux Router側からはルーティングテーブルより、L2リーチャビリティがあるように見えるので(in if.vlan10, out if.vlan10なので)、ARP Proxyを返さず放置します。逆にいうと、フォワーディングが必要な全IPに対するARPに関しては返します。例えば1.1.1.1向けのARPは、デフォゲでフォワーディング可能(in if.vlan10, out if.vlan1000)なので、ARP Replyを返します

<img width="583" alt="Screen Shot 2017-12-16 at 16.59.32.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/9f564e92-7286-a848-241a-f23ff03d5a60.png">


なのでこの構成で端末間で通信するために、ARP ProxyをLinux Routerに喋らせる方法は、Linux Router側のルーティングテーブルをいじり、フォワーディング可能と認識させる必要があります。色々方法はありますが例えば下記のような構成もできます(まぁ、ARP解決はしますが、フォワーディング先がデフォゲに向くので、L3リーチャビリティはありませんが)

<img width="600" alt="Screen Shot 2017-12-16 at 17.07.41.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/fd1ac0cf-9da4-20f3-7bbc-b0e0060b469e.png">

ちなみに、ARP Reply時に自身のルーティングテーブルを見てL2リーチャビリティがない(dev \<interface\>)経路には、ARP Replyを返せないので上記の構成では、192.168.0.3からはProxy ARPどころかどのIP向けもARP解決できないです。
上記環境では、192.168.0.2から192.168.0.3へのARP RequestのみProxy ARPされます。

長々と説明しましたが。要はルーティングテーブル上でL2リーチャビリティがないものだけProxy ARPを返すというのが  /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp です。

ですがここでまだ疑問が残ります、最後の構成のような環境があったら、通信する方法ないじゃん！って思いますよね。最後のルーティングテーブルの変更は、ARP Proxyはしますが、ルーティングテーブルを評価した際にデフォゲに飛ぶので、IPリーチャビリティがないです。このLinux Routing Tableからは、L2リーチャビリティがあるように見えるけど実際は特殊なスイッチ、アクセスポイントによる影響でないような環境ではどのように通信すればいいのでしょうか？

ここで次の機能が生きてきます。


## /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp_pvlan

定番のとりあえず、[Kernelドキュメント](https://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt)から引用

>proxy_arp_pvlan - BOOLEAN
>
>	Private VLAN proxy arp.
	Basically allow proxy arp replies back to the same interface
	(from which the ARP request/solicitation was received).
>
>	This is done to support (ethernet) switch features, like RFC
	3069, where the individual ports are NOT allowed to
	communicate with each other, but they are allowed to talk to
	the upstream router.  As described in RFC 3069, it is possible
	to allow these hosts to communicate through the upstream
	router by proxy_arp'ing. Don't need to be used together with
	proxy_arp.
>
>	This technology is known by different names:
	  In RFC 3069 it is called VLAN Aggregation.
	  Cisco and Allied Telesyn call it Private VLAN.
	  Hewlett-Packard call it Source-Port filtering or port-isolation.
	  Ericsson call it MAC-Forced Forwarding (RFC Draft).

これ、一度理解したらなんとなく何が言いたいのかわかりますが、最初僕は理解できませんでした。特に Basically allow proxy arp replies back to the same interface (from which the ARP request/solicitation was received). いや、ARP Requestが入ってきたinterfaceからARP Reply返さないとARPの意味なくない？って思って？？？？？となりました。まぁ、これは英語の問題かもしれませんが。。。

このProxy ARPは、先ほどの /proc/sys/net/ipv4/conf/\<dev\>/proxy_arp と逆の挙動をします。ルーティングテーブルを見てL2リーチャビリティがあると判断したARP RequestのみProxyします。

<img width="709" alt="Screen Shot 2017-12-16 at 17.27.22.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/3db5c700-f36b-5ed1-d140-a3b636c472c7.png">

ですので、先ほどはProxy ARPしてた下記のようなARP Requestはこのオプションでは、Proxy ARPしません

<img width="696" alt="Screen Shot 2017-12-16 at 17.29.47.png" src="https://qiita-image-store.s3.amazonaws.com/0/2242/18110628-95c7-4ef6-cd8a-17a370f04803.png">

両方ともおそらくユースケースはあると思うので、機能をきちんと理解して使い分けて行きたいですね。
network namespaceのおかげで、今回楽々テストして挙動調査できましたが、namespaceがない時代大変そうですね。。。。

## おまけ

試してみた方は、気づくと思うのですがデフォルトではProxy ARPすごく遅いです。というのも、デフォルトでは0.8秒の遅延を挿入しているからです。もしハードに使いたいという方は、下記のオプションを調整するといいです

* /proc/sys/net/ipv4/neigh/\<interface\>/proxy_delay