ミルク色の記録

やったこと、やってみたこと

Raspberry PIをWifiアクセスポイントにしてWifiネットワーク設定をできるようにした

タイトルが変なので何言ってるんだコイツ状態になるけど。

時々見かける、最初はデバイス自身がWifiのアクセスポイントになって、PCなどから接続して実際に使うネットワーク設定を行う...というやつをRaspberry PIでやってみるという話。

作ったものはこれ。

github.com

環境準備

まずはhostpadをインストールする。

$ sudo apt-get update
$ sudo apt-get install -y hostapd

hostapd.serviceはmaskされているのでインストール時点では起動に失敗する。

設定ファイルをexampleからコピーしてきて/etc/hostapd/hostapd.confを編集する。

$ sudo cp /usr/share/doc/hostapd/examples/hostapd.conf /etc/hostapd/hostapd.conf

とりあえず、デフォルトから変更した項目は次の通り。

ssid=RasPi-AP
ieee80211n=1
ht_capab=[HT40][SHORT-GI-20][DSSS_CCK-40]
auth_algs=1
wpa=2
wpa_passphrase=raspberry
wpa_key_mgmt=WPA-PSK
rsn_pairwise=CCMP

続いて、dhcpサーバをインストールする。

$  sudo apt-get install -y isc-dhcp-server

こちらもインストール時点では起動に失敗する。

/etc/dhcp/dhcpd.confを編集して末尾に次の設定を追加する。

subnet 192.168.100.0 netmask 255.255.255.0 {
  range 192.168.100.100 192.168.100.105;
  option routers 192.168.100.1;
  option broadcast-address 192.168.100.255;
  default-lease-time 600;
  max-lease-time 7200;
}

/etc/default/isc-dhcp-serverを編集してwlan0インターフェイスとして有効にする。

INTERFACESv4="wlan0"

isc-dhcp-serverは自動起動せずに使うので自動起動を無効化しておく。

$ sudo update-rc.d isc-dhcp-server disable

とりあえず動作確認のために簡易起動スクリプトをざっと書いて実行してみる。

#!/bin/sh

sudo hostapd /etc/hostapd/hostapd.conf &
sudo ip addr flush dev wlan0
sudo ip addr replace 192.168.100.1/24 dev wlan0
sudo /etc/init.d/isc-dhcp-server start

スクリプトを実行したら、wlan0192.168.100.1が割り当てられるので、PCなどからアクセスポイントに接続してIPアドレスを貰えればOK。

ブラウザからWifi設定するためのWebサーバを用意

アクセスポイントに接続してPC側がクライアントになることはできるようになったので、アクセスポイントのIPアドレスにブラウザでアクセスしてWifi設定を行えるようにWebサーバを作る。

とりあえず前に作ったtornado使ったソースを流用してざっくり作成。

import os
import subprocess
import tornado.ioloop
import tornado.web

class SystemControl(object):

    def read_wpa_supplicant_conf(self, config_path):
        with open(config_path) as f:
            return f.read()

    def write_wpa_supplicant_conf(self, config_path, data):
        subprocess.check_call('sudo sh -c "echo -n \'%s\' > %s"' % (data, config_path) , shell=True)

    def auto_start_off(self):
        subprocess.run('sudo sed -i --follow-symlinks s/AUTO_START=ON/AUTO_START=OFF/g /etc/default/pi-wifi-admin', shell=True)

    def reboot(self):
        subprocess.run('sudo reboot', shell=True)


class MainHandler(tornado.web.RequestHandler):

    def initialize(self, sys_ctrl, wpa_supplicant_conf_path):
        self._sys_ctrl = sys_ctrl
        self._wpa_supplicant_conf_path = wpa_supplicant_conf_path

    def get(self):
        html = """<html>
  <head><title>Wifi Setting</title></head>
  <body>
    <form method='POST' action='/'>
      <textarea name='config' style='width:800px;height:600px;'>%s</textarea>
      <input type='submit' value='Save' />
    </form>
  </body>
</html>
"""
        conf = self._sys_ctrl.read_wpa_supplicant_conf(self._wpa_supplicant_conf_path)
        self.write(html % conf)
        self.set_status(200)

    def post(self):
        conf = self.get_body_argument('config')
        self._sys_ctrl.write_wpa_supplicant_conf(self._wpa_supplicant_conf_path, conf.replace('\r', '').replace('"', '\\"'))
        self._sys_ctrl.auto_start_off()
        self._sys_ctrl.reboot()
        self.redirect('/', permanent=True)

def make_app(sys_ctrl, wpa_supplicant_conf_path):
    return tornado.web.Application([
        (r'/', MainHandler, {
            'sys_ctrl': sys_ctrl,
            'wpa_supplicant_conf_path': wpa_supplicant_conf_path
            })
    ])

if __name__ == '__main__':
    try:
        port = int(os.getenv('PORT', '8080'))
        sys_ctrl = SystemControl()
        app = make_app(sys_ctrl, '/etc/wpa_supplicant/wpa_supplicant.conf')
        app.listen(port, '0.0.0.0')
        tornado.ioloop.IOLoop.instance().start()
    except KeyboardInterrupt:
        pass

やってることは/にアクセスがあったらwpa_supplicant.confの中身を書きだしたtextareaのあるフォームを表示する。フォームのPOSTでwpasupplicant.confを上書きして設定アプリケーションの自動起動停止、OSを再起動するだけ。ちゃんとAPI作る気力が今回はなかったので超ざっくりに。

これを/usr/local/lib/pi-wifi-adminディレクトリを作って設置し、venvでtornadoを入れて使えるようにしておく。

アクセスポイント化とWebサーバの起動をまとめてサービス化

環境準備で用意したhostapdやisc-dhcp-serverの起動と設定用Webサーバを起動するスクリプトを次の通り用意して/usr/local/sbin/pi-wifi-adminとして設置する。

毎回起動されても困るので、環境変数AUTO_STARTを見て完全に起動を行うかを判定している。

#!/bin/bash

AUTO_START=${AUTO_START:-OFF}
WIFI_IFNAME=${WIFI_IFNAME:-wlan0}
IP_ADDR=${IP_ADDR:-192.168.100.1/24}

APP_HOME_DIR=/usr/local/lib/pi-wifi-admin

if [ "$AUTO_START" == "OFF" ]; then
    echo "auto start disable"
    exit 0
fi

systemctl stop wpa_supplicant
systemctl stop dhcpcd
hostapd -B -P /tmp/hostapd.pid  /etc/hostapd/hostapd.conf
ip addr flush dev $WIFI_IFNAME
ip addr replace $IP_ADDR dev $WIFI_IFNAME
systemctl start isc-dhcp-server
$APP_HOME_DIR/venv/bin/python $APP_HOME_DIR/app.py &

使用する環境変数/etc/default/pi-wifi-adminに設定として置いておく。

AUTO_START=ON
WIFI_IFNAME=wlan0
IP_ADDR=192.168.100.1/24

これらをsystemd用に次の/etc/systemd/wifi-pi-admin.serviceとして設定を用意してサービス化する。

[Unit]
Description = PI Wifi Admin
After=network.target

[Service]
ExecStart = /usr/local/sbin/pi-wifi-admin
EnvironmentFile = /etc/default/pi-wifi-admin
Type = oneshot
RemainAfterExit = yes

[Install]
WantedBy = multi-user.target

あとはサービスを有効化して終わり。

$ sudo systemctl enable pi-wifi-admin.service

使い方

PCでRasPI-APWifi接続して、ブラウザでhttp://192.168.100.1:8080を開く。

f:id:ushiboy:20200125114153p:plain

wpa_supplicant.confの設定フォーマットで設定してSaveを押す。

レスポンスは返る前にOSが再起動してしまうのでタイムアウトすると思う。

課題

スマホWifi接続したら、外部に通信送れないせいか「このWifi駄目だ」と判断されて自動で切り替わってしまった。PCでももしかしたらそういうことあるかも。

ファイル直接触るのやめてちゃんとしたAPIで設定できるようにしたい。

設定保存した後にOSを再起動せずに変更を反映できるようにしたい。