RaspberryPiとRaspbian(Jessie)で無線LAN NAPT箱を作る

もう既に何番煎じか分からないネタですが、RaspberryPiとUSBのWiFiアダプタを使用して無線LANのから有線LANに出て行くNAPT箱を作ってみたものです。

使用したRaspberryPiは2でも3でもなく初代RaspberyPi Model Bですが、この程度の用途でCLIで使うぶんには性能的な問題はなさそうです。ただ、使用したSDカードがアレだったのかI/Oが全般的に本当に遅く、そっちの方が足を引っ張っていました。

Raspbian自体のインストール過程は省略しますが、RaspbianはNOOBSから、デスクトップ環境のないminimalなRaspbian LITEをインストールしました。

流れとしては

 1. USB WiFiアダプタの認識
 2. RaspberryPiへの固定アドレス設定
 3. hostapdの設定
 4. dnsmasqの設定 
 5. ipマスカレード設定

このように進めました。

## セットアップ直後のお約束
まずはeth0側で適当にDHCPでアドレスが取れてインターネットへ出ていけるという前提で、apt-get updateとapt-get upgradeをしておきます。SDカードが遅く、upgradeはかなり時間がかかる……気がします。

```bash
$ sudo apt-get update
(略)
$ sudo apt-get upgrade
(略)
```

また、raspi-configコマンドで、個人的に最低限と思える設定を施しておきます。
Locale生成(en_US.UTF-8)とTimezone(Tokyo)、ホスト名くらいは設定しておきました。

## USB WiFiアダプタ認識
今回使用したUSB WiFiアダプタがPLANEXのGW-USMicroNという割と古いもので、こやつはいきなりwlan0としては認識してくれませんでした。

物自体はrt2800usbというドライバを使うRealtek製のようですが、ベンダIDとプロダクトIDがrt28000usbでは認識されないみたいです。また、rt2800usbドライバもロードされていない状態でした。

```bash
$ lsusb
Bus 001 Device 004: ID 2019:ed14 PLANEX GW-USMicroN
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter
Bus 001 Device 002: ID 0424:9512 Standard Microsystems Corp. LAN9500 Ethernet 10/100 Adapter / SMSC9512/9514 Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
```

どうしようとググったところ、_/etc/modprobe.d_以下にファイルを置き、起動時にrt2800usbをロードし、ロード時にベンダIDとプロダクトIDを_/sys/bus/usb/drivers/rt2800usb/new_id_にechoしてしまうことで認識させる、という動作を行わせているようです。

[noobsから導入したraspbianでPLANEX GW-USMicroNを使うメモ | 徒労日記](http://dolls.orz.hm/?p=8794 "noobsから導入したraspbianでPLANEX GW-USMicroNを使うメモ | 徒労日記")

こちらを参考に、同様に_/etc/modprobe.d/usmicron.conf_を作成しました。

```bash
$ sudo bash -c 'echo "install rt2800usb /sbin/modprobe --ignore-install rt2800usb; /bin/echo "2019 ed14" > /sys/bus/usb/drivers/rt2800usb/new_id" >> /etc/modprobe.d/usmicron.conf'
$ cat /etc/modprobe.d/usmicron.conf
install rt2800usb /sbin/modprobe --ignore-install rt2800usb; /bin/echo 2019 ed14 > /sys/bus/usb/drivers/rt2800usb/new_id
```

modprobe.confにおけるinstallコマンドについては、[modprobe.conf - ファイルのフォーマットと規約の説明 - Linux コマンド集 一覧表](http://kazmax.zpp.jp/cmd/m/modprobe.conf.5.html "modprobe.conf - ファイルのフォーマットと規約の説明 - Linux コマンド集 一覧表")などを参照してみてください。

ファイル作成後、OS再起動をかけるとrt2800usbドライバがロードされ、wlan0インタフェースが生えてきていることが確認できました。

```bash
$ lsmod | grep rt
rt2800usb              18877  0
rt2800lib              82155  1 rt2800usb
rt2x00usb              12680  1 rt2800usb
rt2x00lib              48998  3 rt2x00usb,rt2800lib,rt2800usb
mac80211              608645  3 rt2x00lib,rt2x00usb,rt2800lib
cfg80211              499234  2 mac80211,rt2x00lib
crc_ccitt               1732  1 rt2800lib
```

## 固定IPアドレスの設定
今回は有線側のeth0、無線側のwlan0どちらもIPアドレスをDHCP取得ではなく固定設定とすることにしました。

RaspbianのベースとなっているDebianがJessieになって以来、dhcpcdでアドレス固定の設定を行うのがよくあるやり方のようですが、個人的にはdhcpcdでのアドレス設定には*どうしても馴染めないところ* [^dhcpcd]があったので、昔ながらの_/etc/network/interfaces_でできるだけ設定するようにします。

_/etc/dhcpcd.conf_に以下の設定を追記します。

```bash
denyinterfaces eth0 wlan0
```

これで、dhcpcdがeth0とwlan0を手放してくれるようになるはずです。いったんdhcpcdを再起動するなり、OSごと再起動するなりで、設定を反映させました。

再起動後、_/etc/network/interfaces_側ではeth0とwlan0に以下のような設定を行います。

```bash
auto eth0
iface eth0 inet static
    address 192.168.0.100
    netmask 255.255.255.0
    network 192.168.0.0
    broadcast 192.168.1.255
    gateway 192.168.0.1

allow-hotplug wlan0
auto wlan0
iface wlan0 inet static
    address 10.100.0.1
    netmask 255.255.255.0
    network 10.100.0.0
    broadcast 10.100.0.255

#allow-hotplug wlan1
#iface wlan1 inet manual
#    wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf
```

eth0側は192.168.0.100/24で固定、wlan0側は10.100.0.1/24で固定という想定です。wlan1についての設定はコメントアウトしてしまいます。

ここで再度OSを再起動して、アドレスが反映されているか`$ ip addr show`などで確かめました。

## hostapdの設定
### インストール
無線LAN APとして動作させるためにhostapdを導入します。

```bash
$ sudo apt-get -y install hostapd
(snip)
$ sudo /usr/sbin/hostapd -v
hostapd v2.3
User space daemon for IEEE 802.11 AP management,
IEEE 802.1X/WPA/WPA2/EAP/RADIUS Authenticator
Copyright (c) 2002-2014, Jouni Malinen <j@w1.fi> and contributors
```

hostapdの設定は_/etc/hostapd/hostapd.conf_に記載しますが、インストール直後には設定ファイル自体が存在していません。そのため、以下のようにテンプレートとなるファイルをコピーしてきます。

```bash
$ sudo bash -c "zcat /usr/share/doc/hostapd/examples hostapd.conf.gz > /etc/hostapd/hostapd.conf"
```

コピーしたhostapd.confから、以下の設定を有効化、変更しました。

```bash
$ grep -v '^#' /etc/hostapd/hostapd.conf | grep -v '^$'
interface=wlan0
driver=nl80211
logger_syslog=-1
logger_syslog_level=2
logger_stdout=-1
logger_stdout_level=2
ctrl_interface=/var/run/hostapd
ctrl_interface_group=0
ssid=XXXXXXXX
country_code=JP
ieee80211d=1
local_pwr_constraint=3
spectrum_mgmt_required=1
hw_mode=g
channel=5
beacon_int=100
dtim_period=2
max_num_sta=255
rts_threshold=2347
fragm_threshold=2346
macaddr_acl=0
auth_algs=3
ignore_broadcast_ssid=0
wmm_enabled=1
wmm_ac_bk_cwmin=4
wmm_ac_bk_cwmax=10
wmm_ac_bk_aifs=7
wmm_ac_bk_txop_limit=0
wmm_ac_bk_acm=0
wmm_ac_be_aifs=3
wmm_ac_be_cwmin=4
wmm_ac_be_cwmax=10
wmm_ac_be_txop_limit=0
wmm_ac_be_acm=0
wmm_ac_vi_aifs=2
wmm_ac_vi_cwmin=3
wmm_ac_vi_cwmax=4
wmm_ac_vi_txop_limit=94
wmm_ac_vi_acm=0
wmm_ac_vo_aifs=2
wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0
eapol_key_index_workaround=0
eap_server=0
wpa=2
wpa_passphrase=XXXXXXXX
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
```

### hostapd.confの設定項目について
主に変更する必要があるのは下記の項目だと思います。

- interface
  - hostapdがリッスンするインタフェースを設定します。
- driver
  - hostapdが使用するドライバを指定します。
  - これはLinuxがロードするデバイスドライバ(今回だとrt2800usb)のことではなく、802.11に関するL2以下の面倒を見るドライバを指定します。lsmodの結果を見るとrt2800usbからmac80211とcfg80211の2つのドライバが呼ばれているのがわかります。
  - mac80211が使われている場合は、hostapdのドライバとして**nl80211**を指定します。
  - Linuxの無線LANに関係するスタックはこちらが参考になりました。普段意識したことがなかったので興味深かったです。[Linuxにおける無線LANのソフト構成 - Qiita](http://qiita.com/Yoko303/items/8ee1f454c857d09db5bb "Linuxにおける無線LANのソフト構成 - Qiita")
- ssidは
  - hostapdが名乗るssidを指定します。
- country_code
  - 法によって定められている出力等を超えない範囲で運用するため、ここでは**JP**を指定します。[^country_code]
- hw_mode
  - 2.4GHz帯を使用するので**g**を指定します。
  - 今回は使用しませんでしたが、802.11nで使用する場合でも**g**を指定します。
- wpa
  - 使用するwpaのモードを設定します。
  - WPA2だから**2**を指定しているわけではなく、ここで指定しているのは2ビットの数値の10進指定です。設定値は以下のようになります。
    - 1(01)はWPAのみ有効
    - 2(10)はWPA2(RSN)のみ有効
    - 3(11)はWPA/WPA2両方とも有効
- wpa_passphrase
  - WPA/WPA2動作時にssidへのアクセスに使用するパスフレーズを指定します。
  - wpa_pskを設定することで、パスフレーズではなくPSKを直接指定することもできます。PSKはssidとパスフレーズからwpa_passphraseコマンドで生成することができます。
- wpa_key_mgmt
  - WPAの鍵認証モードに関してWPA-PSKとWPA-EAPのいずれかを設定します。
  - 上記のとおり、PSKモードを使用しますので**WPA-PSK**を設定します。
- wpa_pairwise
  - WPAで使用する暗号化方式を指定します。
  - **TKIP**は暗号化アルゴリズムにRC4を使うWEPの拡張版のようなもので、WPAではよく用いられる暗号化方式です。鍵長を長くし、鍵も一定時間で変更するようにしていますが、RC4を使用している以上、最近は非推奨のようです。[^wpa_pairwise]
- rsn_pairwise
  - WPA2で使用する暗号化方式を指定します。
  - WPA2では暗号化アルゴリズムにRC4を使っていたTKIPではなく、AESを使う**CCMP**を使うのが標準のようです。
  - TKIP/CCMPについてはこちらが参考になりました。[無線LANの暗号化方式について整理してみた - トリコロールな猫/セキュリティ](http://security.nekotricolor.com/entry/difference-in-wireless-lan-protocol-wep-wpa-wpa2-tkip-ccmp-aes "無線LANの暗号化方式について整理してみた - トリコロールな猫/セキュリティ")

### デーモンとしての動作

また、デーモンとして動作する際のコンフィグファイルを指定するため、_/etc/default/hostapd_の**DAEMON_CONF**に、_/etc/hostapd/hostapd.conf_を設定してコメントを外します。

```bash
$ sudo sed -i -e "s/#DAEMON_CONF=\"\"/DAEMON_CONF=\"\/etc\/hostapd\/hostapd.conf\"/g" /etc/default/hostapd
$ sudo systemctl enable hostapd.service
$ sudo systemctl start hostapd.service
```

これでhostapdがデーモンとして動作するようになるはずですが、まずはdaemonとしてではなくフォアグラウンドな感じで起動してみて、きちんと動作しているかと標準出力にどのようなログが出るかを確認しました。hostapd.confの設定内容を修正して確認するといった際も、とりあえずフォアグラウンド動作させた方がお手軽に確認できると思います。

```bash
$ sudo systemctl stop hostapd.service
$ sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf
```

これで、hostapdがフォアグラウンドで起動して、標準出力にバラバラとログが出てくるようになるはずです。

WPA接続時とWPA2接続時で、それぞれログを確認しました。

```bash
# WPA接続時
$ sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf
Configuration file: /etc/hostapd/hostapd.conf
Using interface wlan0 with hwaddr 00:22:cf:XX:XX:XX and ssid "XXXXXXXX"
wlan0: interface state UNINITIALIZED->ENABLED
wlan0: AP-ENABLED
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: authenticated
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: associated (aid 1)
wlan0: AP-STA-CONNECTED 50:46:5d:XX:XX:XX
wlan0: STA 50:46:5d:XX:XX:XX RADIUS: starting accounting session 581F4672-00000000
wlan0: STA 50:46:5d:XX:XX:XX WPA: pairwise key handshake completed (WPA)
wlan0: STA 50:46:5d:XX:XX:XX WPA: group key handshake completed (WPA)
wlan0: AP-STA-DISCONNECTED 50:46:5d:XX:XX:XX
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: disassociated
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: deauthenticated due to inactivity (timer DEAUTH/REMOVE)

# WPA2接続時
$ sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf
Configuration file: /etc/hostapd/hostapd.conf
Using interface wlan0 with hwaddr 00:22:cf:XX:XX:XX and ssid "XXXXXXXX"
wlan0: interface state UNINITIALIZED->ENABLED
wlan0: AP-ENABLED
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: authenticated
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: associated (aid 1)
wlan0: AP-STA-CONNECTED 50:46:5d:XX:XX:XX
wlan0: STA 50:46:5d:XX:XX:XX RADIUS: starting accounting session 581F47BC-00000000
wlan0: STA 50:46:5d:XX:XX:XX WPA: pairwise key handshake completed (RSN)
wlan0: AP-STA-DISCONNECTED 50:46:5d:XX:XX:XX
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: disassociated
wlan0: STA 50:46:5d:XX:XX:XX IEEE 802.11: deauthenticated due to inactivity (timer DEAUTH/REMOVE)
```

AP-ENABLEDのログがあれば、ひとまずAPとしては動作しており、RSNのログが確認できれば、クライアントがWPA2で接続されていると考えて良いようです。

## dnsmasqの設定
### インストール
今回はDNSフォワーダとDHCPサーバを兼ねてもらいたかったので、お手軽なdnsmasqを使用しました。

```bash
$ sudo apt-get -y install dnsmasq
```

_/etc/dnsmasq.conf_はいろいろ設定項目がありますが、今回コメントアウトを外して有効にしたのは以下の設定項目です。用途としてはdnsmasq自体では名前解決を行うつもりはなく、ローカルドメインの設定も行わないので、よく使われる_/etc/hosts_からの解決も行いません。

```bash
$ grep -v "^#" /etc/dnsmasq.conf | grep -v "^$"
domain-needed
bogus-priv
no-resolv
no-poll
server=192.168.0.1
interface=wlan0
no-hosts
dhcp-range=10.100.0.200,10.100.0.249,1h
```
### dnsmasq.confの設定項目について
- domain-needed
  - ドメイン部分がないホスト名解決の要求が来た際には、上位のDNSサーバへクエリを転送しません。上位DNSサーバに対して、明らかにプライベートな名前を引かせることを避けるためです。
- bogus-priv
  - プライベートIPアドレスの逆引き問い合わせを上位のDNSサーバに転送しません。こちらも、上位DNSサーバに不用意にプライベートゾーンのクエリを送らないようにするためです。
- no-resolv
  - _/etc/resolv.conf_で指定されているDNSサーバを上位のDNSサーバとして使用しません。これは、resolvconfが_/etc/resolv.conf_をどう書き換えるのか、自分自身よくわかっていないため、あえてこうしました。
- no-poll
  - _/etc/resolv.conf_の変更をポーリングして追っかけません。これも、resolvconfの挙動がよくわかっていないためです。
- server=\<nameserver-addr\>
  - dnsmasqが解決できないドメインのクエリを転送する上位DNSサーバを指定します。
  - 8.8.8.8へ飛ばしている場合もあるかと思いますが、自分は真上のブロードバンドルータを指定しています。そのルータもただのフォワーダなので、段数が増えていくだけですが……
- interface=\<interface\>
  - dnsmasqがリッスンするインタフェースを指定します。
  - これを指定しないとdnsmasqが全インタフェースに対してリッスンしてしまうため、特にDHCPサーバとして動作させるときは注意が必要です。
- no-hosts
  - _/etc/hosts_内に書かれたホスト名で、名前解決を行いません。
  -上に書いたとおり、今回はDNSフォワーダに徹してもらうため名前解決をさせないつもりで設定しています。
- dhcp-range=\<start-addr\>,\<end-addr\>,\<lease-time\>
  - dnsmasqをDHCPサーバとして動作させます。
  - DHCPで払い出すアドレスの開始アドレスと最終アドレス、リース期間を設定します。


## iptablesでIPマスカレード設定を行う
NAPT動作させるために、iptablesでIPマスカレード設定を行います。設定自体はLinuxでIPマスカレードを行うための一般的な方法と全く同じです。

Linuxのip_forward設定を有効にし、インタフェース間のパケット転送を可能にしたのち、iptablesコマンドでIPマスカレードの設定を行い、iptablesの設定内容をファイルへ書き出し、起動時にロードされるように設定しています。

```bash
$ sudo sed -i -e "s/^#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/" /etc/sysctl.conf
$ sudo bash -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
$ less /etc/sysctl.conf
$ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
$ sudo iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
$ sudo iptables -A FORWARD -i wlan0 -o eth0 -j ACCEPT
$ sudo bash -c "iptables-save > /etc/iptables.ipv4.nat"
$ sudo bash -c "cat <<EOS> /etc/network/if-pre-up.d/iptables
> #!/bin/sh
> /sbin/iptables-restore < /etc/iptables.ipv4.nat
> EOS
> "
$ sudo chmod +x /etc/network/if-pre-up.d/iptables
```

[習うより慣れろ！ iptablesテンプレート集（2）：natテーブルを利用したLinuxルータの作成 (1/6) - ＠IT](http://www.atmarkit.co.jp/ait/articles/0505/17/news131.html "習うより慣れろ！ iptablesテンプレート集（2）：natテーブルを利用したLinuxルータの作成 (1/6) - ＠IT")

ここまででできたら、完了のはずです。おつかれさまでした！

## 補足
### sshdのlisten address設定
無線側からRaspbianにsshアクセスするということは認めないことにしたかったので、sshdのリッスンアドレスを0.0.0.0ではなくeth0側アドレスのみに絞りました。

_/etc/ssh/sshd_config_の以下の点を変更してsshdをrestartすればOKです。

```
$ grep Listen /etc/ssh/sshd_config
#ListenAddress ::
ListenAddress 192.168.0.100
```

## 参考文献
構築中、全面的に参考にさせていただきました。ありがとうございます :+1: 

- [Raspberry Pi 3 Model Bの WiFiを無線LANアクセスポイント化 hostapd + dnsmasq編 (ラズパイ３に内蔵の WiFiを無線LANアクセスポイントにして通信環境を設定する方法（広告ブロック機能付き）)](http://www.neko.ne.jp/~freewing/raspberry_pi/raspberry_pi_3_wifi_wireless_access_point_dnsmasq/ "Raspberry Pi 3 Model Bの WiFiを無線LANアクセスポイント化 hostapd + dnsmasq編 (ラズパイ３に内蔵の WiFiを無線LANアクセスポイントにして通信環境を設定する方法（広告ブロック機能付き）)")
- [無線LAN AP化 ~ 偉大なるノッポとプリン](http://norikyu.blogspot.jp/p/lan-ap.html "無線LAN AP化 ~ 偉大なるノッポとプリン")
- [hostapd.conf 覚書 - Qiita](http://qiita.com/JhonnyBravo/items/5df2d9b2fcb142b6a67c "hostapd.conf 覚書 - Qiita")
- [Raspberry Pi をWi-Fiアクセスポイント＆ルーター化する - 猫ぱーんち！](http://d.hatena.ne.jp/penkoba/20130806/1375742819 "Raspberry Pi をWi-Fiアクセスポイント＆ルーター化する - 猫ぱーんち！")
- [hostapd を使用した簡単な方法で WiFi に強力な暗号化を実装する](https://www.ibm.com/developerworks/jp/linux/library/l-wifiencrypthostapd/ "hostapd を使用した簡単な方法で WiFi に強力な暗号化を実装する")

----

[^dhcpcd]: dhcpcdはDHCPクライアントという性質上からか？インタフェースが物理的にリンクアップしている状態でないとアドレスをインタフェースへ付与した状態にならないところがちょっと癖を感じてしまい、あまり積極的に使いませんでした。staticなアドレスアサインに慣れていた自分からすると、インタフェースの物理的なリンクアップ状態に関係なく、インタフェース自体がOS上でUPしていればアドレスも付与されている状態だと考えていたためです。
[^country_code]: どういった形で出力制限等が効いているのか理解していないため、自分にとってはおまじないですが……
[^wpa_pairwise]: 今回はWPA2のみの動作としているため、WPAは使用していませんがWPAでの試験も行っていたのでその名残です……