背景
会社と自宅などで作業するときに簡易的なサーバをラズパイで立てることがある。
ラズパイを無線LANに有線で接続し、リモートPCからラズパイにsshで接続してサーバを立ち上げている。
が、そのssh接続するときのIPアドレスが接続するWifi機器によって違う。
いちいちラズパイにディスプレイやキーボードなどをつないでIPを確認して接続するのが面倒。
固定IPにしたら楽だけど環境が頻繁に変わる(=無線LAN機器が頻繁に変わる)可能性があるので、できたら固定IP設定なしで簡単にラズパイのIPが知りたい。
と思って次のようなものを作ってみた
基本
- ラズパイは特定のポートxxxxでudpで受信待ちをする
- リモートPCがブロードキャストで任意の文字列を送信する
- ラズパイがブロードキャストされた文字列を受け取る
- 受け取った文字列が特定の文字列yyyyだった場合、ラズパイはリモートPCに対して答えとなる文字zzzzを返す。
リモートPCはラズパイから返答を返してもらえるが、どのIPから返されたかがわかるため、ラズパイのIPが何なのかがわかるという仕組み。
(安全なネットワーク内で利用する前提で、セキュリティ云々は考慮していませんのでご注意ください)
受信側(ラズパイ)で実装するコード
udpを受信して、特定の文字列yyyyが来たら、送ってきた端末に対してudp送信するコードになる。
下記のコードでは「Hello!!!」という文字列が送られてきたなら、「Hi!!!」と返すようになっている。
import socket
import datetime
def log(logstr:str):
with open('/home/hogehoge/user_reciever_log.log','a') as f:
timestamp = datetime.datetime.now().strftime('[%Y%m%d %H:%M:%S] ')
f.write('{} {}\n'.format(timestamp,logstr))
def main():
port = 8080 # ブロードキャストを受信するポート番号
prompt = 'Hello!!!' # 「特定の文字列**yyyy**」に該当する呪文
sock = socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)
sock.bind(('',port))
log('activate answer_daemon')
while True:
message, send_addr = sock.recvfrom(4096)
log('Recv {}: {}'.format(send_addr,message.decode(errors='ignore')))
if message == prompt.encode():
sock.sendto('Hi!!!'.encode(),send_addr)
if __name__ == '__main__':
main()
送信側の実装コード
ブロードキャストを投げて、その返答を待つプログラムになる。
答えが返ってこない可能性があるので5秒でタイムアウトしている。
import socket
import argparse
def broadcast_sender(port:int, prompt:str):
sock = socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) # ブロードキャスト送信設定
sock.settimeout(5) # タイムアウト設定
sock.bind(('',0))
try:
sock.sendto(prompt.encode(),('255.255.255.255',port))
msg,address = sock.recvfrom(4096)
print('Recv {}: {}'.format(address,msg))
except TimeoutError:
print('Timeout')
def main():
parser = argparse.ArgumentParser(prog='sender')
parser.add_argument('port',type=int,help='port number')
parser.add_argument('prompt',help='prompt string')
args = parser.parse_args()
broadcast_sender(args.port,args.prompt)
if __name__ == '__main__':
main()
実行
ラズパイ側では下記のコマンドを実行して受信プログラムを実行する
止めたかったらCtrl+CでOK.
python udp_reciever.py
ラズパイ側を起動したら今度はリモートPC側を実行する。
受信側は8080で待っているので、それ宛にブロードキャストを投げる
python udp_sender.py 8080 Hello!!!
下のような結果になったら成功
Recv ('192.168.**.**', 8080): b'Hi!!!'
この結果を以てラズパイのIPがわかる。
udp_recieverのデーモン登録
udp_reciever.py
はラズパイ起動時に自動で実行させたい。
systemdでデーモンとして登録すると、それが可能とのこと。
udp_recieverにSheBang(シバン)を設定する
デーモン登録する時にシバンを設定しておく必要がある。
udp_reciever.py
の先頭に下記を追加しておく
#!/usr/bin/env python3
自分はvenvで仮想環境を作っていたので、下記のようなシバンを追加した。
#!/home/hogehoge/.env/bin/python
(任意)また、下記を実行してファイルアクセス権限を755にしておいた。
sudo chmod 0755 udp_reciever.py
サービスファイルの記述
udp_reciever.pyをデーモン登録するにあたって特定の場所に設定ファイルを記述する
下記のコマンドを実行して、ファイルを新規作成する。
udp_recieverd
の箇所は好きに名前を変えていい。
sudo vi /usr/lib/systemd/system/udp_recieverd.service
vimで開いたら下記のような内容を記述する。
[Unit]
Description = udp_reciever_daemon
[Service]
ExecStart = /home/hogehoge/udp_reciever.py
Restart = always
Type = simple
[Install]
WantedBy = multi-user.target
ExecStartに書いてあるpythonファイルのパスは自分の環境に合わせて変えたらいい。
サービスの稼働
service設定ファイルが書けたら下記を実行する
sudo systemctl daemon-reload # デーモンの再読み込み
sudo systemctl start udp_recieverd # デーモンの実行
sudo systemctl status udp_recieverd # デーモンのステータス確認(表示を見てなんか動いてそうだったらOK)
sudo systemctl enable udp_recieverd # デーモンの自動起動の設定
参考