はじめに
Raspberry Pi を WiFi アクセス ポイントとして設定するのはよくあるのですがufwを使いたかったので、ufwを使う場合の設定のメモです.
手順は、AdafruitのとRaspberry Piのドキュメントを参考にしています。
2023/6/17 更新
dnsmasqのリースのログをjournalから確認する方法を追加しました。
2023/6/22 更新
bottleからdnsmasqのリースのログが参照できるようにしました。
2023/6/26 更新
WiFiアクセスポイントに接続されている端末の一覧を確認する方法を追加しました。
2024/5/4 更新
Network Manager版のWiFiアクセスポイント設定とufw設定を追加しました。
2024/5/20 更新
Network Manager版のband aの時にはchannelも指定する必要があることを追加しました。
raspberr pi の OS で NetworkManager がデフォルトで有効になりました。そのため WiFi アクセス ポイントの手順がここに書いてある手順と異なっています。WiFi アクセス ポイント化については以下のサイトを参照してください。
Network Manager版のWiFiアクセスポイント設定とufw設定(2024/5/4追記)
簡易ですが、私が設定した内容を乗せておきます。
nmcliでは、以下のような感じで設定することになります。
sudo nmcli device wifi hotspot ssid <example-network-name> password <example-password>
私は、wlan1に設定したかったので、このようにしました。
sudo nmcli device wifi hotspot ifname wlan1 ssid <example-network-name> password <example-password>
band a (5Ghz) を使うときは、channel も指定する必要がありました。指定しないと band bg で channel 1 になっていました。
NetworkManager は wifi デバイスに IP アドレス 10.42.0.1 を使用し、 10.42.0.0/24 サブネットからの IP アドレスをクライアントに割り当てられるようです。また、connectionの名前は、Hotspot になっていました。下記コマンドで、設定を確認することができます。
sudo nmcli device show wlan1
NetworkManagerでは、IPv4ルーティングを有効に自動で設定してくれました。
自動接続の設定をしないと、Raspberry piを再起動すると消えてしまいます。自動接続のために connection.autoconnect を yes に設定します。
sudo nmcli connect modify Hotspot connection.autoconnect yes
nmcliでも、ufw を使っている場合は、ufwの設定に追加ルールが必要でした。
ufwのsshを有効にしてからWiFiアクセスポイント用のルールを設定しています。
sudo ufw allow SSH
sudo ufw enable
sudo ufw allow in on wlan1 to 0.0.0.0/0 port 67:68 proto udp
sudo ufw allow in on wlan1 to 10.42.0.1 port 53 from 10.42.0.0/24
sudo ufw route allow in on wlan1 out on eth0 from 10.42.0.0/24
dhcpのIP払い出しされたIPアドレスは以下のファイルで確認できます。ファイルのパスが NetworkManager の下になっています。
sudo cat /var/lib/NetworkManager/dnsmasq-wlan1.leases
journalctl や iw は、以前と同じように使えました。
journalctl -t dnsmasq-dhcp -S yesterday
iw dev wlan1 station dump
以下は、Network Managerが利用されていない場合の手順
AdafruitのとRaspberry Piの手順もご確認ください
作りたい環境
ソフトウェアをインストールする
Pi のオペレーティング システムとユーティリティが新しくて最新であることを確認します。
sudo apt update
sudo apt -y upgrade
hostapd と dnsmasq、ufw をインストールしAdafruitの手順にしたがって再起動します。
ufwを利用するため、手順にあるnetfilter-persistent
とiptables-persistent
は不要です。
sudo apt install -y hostapd dnsmasq ufw
sudo systemctl unmask hostapd
sudo systemctl enable hostapd
sudo reboot
ネットワークルーターをセットアップする
ワイヤレスインターフェイスのIP構成を定義する
IP アドレスを構成するには、次のように構成ファイルを編集します。ここではnanoエディターを使用していますが、選択した別のエディターに置き換えることもできます。
sudo nano /etc/dhcpcd.conf
dhcpcd.confには数十行の内容があります。ファイルの末尾に移動し、最後に次の行を追加します。
interface wlan0
static ip_address=192.168.4.1/24
nohook wpa_supplicant
ワイヤレス ネットワークのDHCPサービスとDNSサービスを構成する
sudo nano /etc/dnsmasq.conf
interface=wlan0 # Listening interface
domain-needed
bogus-priv
dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
# Pool of IP addresses served via DHCP
dhcp-option=option:router,192.168.4.1
domain-needed
は、ドメインの無いホスト名のみ問い合わせの場合、上位DNSサーバに転送しないようにするための設定です。
bogus-priv
は、プライベートIPアドレスの逆引きを上位DNSサーバに転送しないための設定です。
dhcp-option=option:router,192.168.4.1
は、DHCPクライアントに通知するルータのIPアドレスの設定です。
IPv4ルーティングを有効にする
/etc/ufw/sysctl.conf
を編集してIPv4ルーティングを有効にします。
sudo nano /etc/ufw/sysctl.conf
下記の #net/ipv4/ip_forward=1
のコメントを外します。
# Uncomment this to allow this host to route packets between interfaces
#net/ipv4/ip_forward=1
#net/ipv6/conf/default/forwarding=1
#net/ipv6/conf/all/forwarding=1
# Uncomment this to allow this host to route packets between interfaces
net/ipv4/ip_forward=1
#net/ipv6/conf/default/forwarding=1
#net/ipv6/conf/all/forwarding=1
IPマスカレードを有効にする
/etc/ufw/before.rules
を編集してIP マスカレードを有効にします。
#
# rules.before
#
# Rules that should be run before the ufw command line added rules. Custom
# rules should be added to one of these chains:
# ufw-before-input
# ufw-before-output
# ufw-before-forward
#
# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines
(以下省略)
以下の数行を*filter
の前に挿入します。
# NAT
*nat
-F
-A POSTROUTING -s 192.168.4.0/24 -o eth0 -j MASQUERADE
COMMIT
編集後は以下のようになります。
#
# rules.before
#
# Rules that should be run before the ufw command line added rules. Custom
# rules should be added to one of these chains:
# ufw-before-input
# ufw-before-output
# ufw-before-forward
#
# NAT
*nat
-F
-A POSTROUTING -s 192.168.4.0/24 -o eth0 -j MASQUERADE
COMMIT
# Don't delete these required lines, otherwise there will be errors
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines
(以下省略)
ufw を有効にする
ufw を有効にしますが、sshでアクセスしている場合は、SSHをルールに追加しから有効にしてください。
sudo ufw allow SSH
sudo ufw enable
ufw の転送を有効にする
いろいろ試した結果下記の記述が最低限必要そうです。
sudo ufw allow in on wlan0 to 0.0.0.0/0 port 67:68 proto udp
sudo ufw allow in on wlan0 to 192.168.4.1 port 53 from 192.168.4.0/24
sudo ufw route allow in on wlan0 out on eth0 from 192.168.4.0/24
APソフトウェアの設定
sudo nano /etc/hostapd/hostapd.conf
以下の情報を/etc/hostapd/hostapd.conf
に設定します。
この設定は、ネットワーク名NameOfNetwork
、パスワードAardvarkBadgerHedgehog
、チャネル 7 を使用していることを前提としています。名前とパスワードを引用符で囲んではいけないことに注意してください。パスフレーズの長さは 8 ~ 64 文字にする必要があります。
ネットワーク名とパスワードは、変更してください。
私は日本在住なのでcountry_code
は、JPを設定しています。
country_code=JP
interface=wlan0
ssid=NameOfNetwork
hw_mode=g
channel=7
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=AardvarkBadgerHedgehog
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
Adafruitの手順には、raspi-config nonint do_wifi_country
の設定があったので念のため設定しました。
その後再起動します。
sudo raspi-config nonint do_wifi_country JP
sudo systemctl reboot
WiFiアクセスポイントに接続された端末の確認
dnsmasqからリースされた端末の確認
dhcpのIP払い出しを確認するとWiFiアクセスポイントに接続された端末の確認することができます。
cat /var/lib/misc/dnsmasq.leases
dnsmasqのログがjournalにあります。次のコマンドで昨日からのリースのログが確認できます。
journalctl -t dnsmasq-dhcp -S yesterday
WiFiアクセスポイントに接続してる端末の確認
WiFiアクセスポイントに接続されている端末の一覧は次のように取得することができます。
iw dev wlan0 station dump
web経由で確認
IP払い出しをWeb経由で確認するため、python3とbottleをつかってWebサーバを立ち上げます。
bottleをインストールします。
apt install python3-bottle
bottleを使ったサーバ
#!/usr/bin/python3
import grp
import pwd
import os
from bottle import template, route, run
from datetime import datetime
import json
import socket
import subprocess
from wsgiref.simple_server import WSGIServer
# Bottleサービスをカスタマイズするため WSGIServer を利用する
class wsgi_server(WSGIServer):
# サーバを終了した直後にもう一度サーバを起動しようとすると、
# bindがエラーで終了することがある。
# その問題を回避するためにSO_REUSEADDRを有効にする。
# allow_reuse_address = True にすると SO_REUSEADDR が有効になる。
allow_reuse_address = True
def server_bind(self):
WSGIServer.server_bind(self)
self._drop_privileges()
def _drop_privileges(self):
# 1024ポート以下のポート(特権ポート)を利用するために、
# root ユーザで起動される場合がある。
# root ユーザで起動された時は、ユーザを変更して権限をドロップする。
name = 'www-data'
group = 'www-data'
groups = ['systemd-journal']
if os.getuid() == 0:
uid = pwd.getpwnam(name)[2]
gid = grp.getgrnam(group)[2]
gids = [grp.getgrnam(g)[2] for g in groups]
os.setgid(gid)
os.setgroups(gids)
os.setuid(uid)
def _hostname():
return socket.gethostname().capitalize()
def _rpi_model():
with open('/proc/device-tree/model') as f:
model = f.read().rstrip('\x00')
return model
INDEX_HTML = """<!DOCTYPE html>
<html lang="ja">
<head>
<title>{{hostname}}</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<main>
<h1>{{hostname}}</h1>
<p>モデル: {{rpi_model}}</p>
<article>
<header>
<h2>Dnsmasq リース情報</h2>
<p>
DnsmasqでリースされたIPアドレス一覧
<button onclick="update_dhcp_leases()">更新</button>
</p>
</header>
<table>
<thead>
<tr>
<th>リース期間</th>
<th>MAC アドレス</th>
<th>IP アドレス</th>
<th>名前</th>
<th>クライアントID</th>
</tr>
</thead>
<tbody id="dhcp_leases"></tbody>
</table>
</article>
<article>
<header>
<h2>Dnsmasq ログ</h2>
<p>
DnsmasqのDHCPのログ
<button onclick="update_dhcp_logs()">更新</button>
</p>
</header>
<table>
<thead>
<tr>
<th>日時</th>
<th>メッセージ</th>
</tr>
</thead>
<tbody id="dhcp_logs"></tbody>
</table>
</article>
</main>
<script type="text/javascript">
function update_dhcp_leases() {
fetch("/dnsmasq/dhcp/leases")
.then((res) => res.json()).then((json) => {
let tbody = '';
json.leases.forEach((element) => {
tbody += `<tr>
<td>${element.lease_time}</td>
<td>${element.mac_address}</td>
<td>${element.ip_address}</td>
<td>${element.name}</td>
<td>${element.client_id}</td>
</tr>`;
});
document.getElementById("dhcp_leases").innerHTML = tbody;
});
}
function update_dhcp_logs() {
fetch("/dnsmasq/dhcp/logs")
.then((res) => res.json()).then((json) => {
let tbody = '';
json.logs.forEach((log) => {
tbody += `<tr>
<td>${log.time}</td>
<td>${log.message}</td>
</tr>`;
});
document.getElementById("dhcp_logs").innerHTML = tbody;
});
}
window.onload = function() {
update_dhcp_leases();
update_dhcp_logs();
}
</script>
</body>
</html>
"""
@route('/')
def index():
return template(
INDEX_HTML,
hostname=_hostname(),
rpi_model=_rpi_model(),
)
def _dnsmasq_dhcp_leases():
leases = []
with open('/var/lib/misc/dnsmasq.leases') as f:
for line in f:
element = line.split()
if len(element) != 5:
continue
lease_time = int(element[0])
if lease_time == 0:
lease_time = 'Never'
else:
lease_time = datetime.fromtimestamp(
lease_time).strftime('%Y-%m-%d %H:%M:%S')
leases.append({
'lease_time': lease_time,
'mac_address': element[1],
'ip_address': element[2],
'name': element[3],
'client_id': element[4],
})
return {'leases': leases}
@route('/dnsmasq/dhcp/leases')
def dnsmasq_dhcp_leases():
# dict だと jsonに変換される。list は json にはならない
return _dnsmasq_dhcp_leases()
def _dnsmasq_dhcp_logs():
completed = subprocess.run(
['journalctl', '-t', 'dnsmasq-dhcp', '-S', 'yesterday', '-r',
'-o', 'json',
'--output-fields=_SOURCE_REALTIME_TIMESTAMP,MESSAGE'],
stdin=subprocess.DEVNULL,
capture_output=True,
text=True,
)
logs = []
if completed.returncode == 0:
for s in completed.stdout.splitlines():
j = json.loads(s)
tm = datetime.fromtimestamp(
int(j['_SOURCE_REALTIME_TIMESTAMP'])/1000000)
logs.append({
'time': tm.strftime('%Y-%m-%d %H:%M:%S'),
'message': j['MESSAGE'],
})
return {'logs': logs}
@route('/dnsmasq/dhcp/logs')
def dnsmasq_dhcp_logs():
# dict だと jsonに変換される。list は json にはならない
return _dnsmasq_dhcp_logs()
run(host='::', port=80, server_class=wsgi_server)
80番ポートを使っているのでsudo python3から起動します。
$ sudo python3 rpi_service.py
Bottle v0.13-dev server starting up (using WSGIRefServer(server_class=<class '__main__.wsgi_server'>))...
Listening on http://:::80/
Hit Ctrl-C to quit.
ブラウザでアクセスすると下記のようになります。
参考
Dnsmasqの設定は以下のページを参考にさせていただきました。