本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。
今回はPythonのsocketモジュールを使用したUDP通信(クライアント)の基本について学んでいきます。
UDPクライアントの実装
前回ではTCPクライアントの実装について学びました。
今回のUDP通信についても、前回と同様にPythonのsocketモジュールを用いて実装をしていきます。
以下が今回のコードになります。
# udp_client.py import socket buf_size = 4096 target_host = "127.0.0.1" target_port = 8000 soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) soc.sendto(b"this is a test message.", (target_host, target_port)) data, addr = soc.recvfrom(buf_size) print("[*] Received:{} from {}:{}".format(data.decode('utf-8').strip(), addr[0], addr[1]))
socketの生成
8行目でsocketを生成します。
"AF_INET"はアドレスファミリ(Address Family)と呼ばれるネットワークの種類を表すもので、"AF_INET"はIPv4を意味し、AF_INET6はIPv6を意味します。
インターネット上では、基本的にはまだIPv4が主流なので、"AF_INET"を指定しています。
また"SOCK_DGRAM"はUDPを使用することを指定しています(TCPの場合は"SOCK_STREAM"を指定します)。
データ送信
TCPの際は、データ送受信の前にconnect()を呼び出し、通信を行う準備(3 Way Handshake)をしていましたが、UDPの場合はいきなりデータを送信します。
10行目のsendto()を呼び出し、データおよび送信先アドレスとポート番号をタプルにした引数を指定します。
なお、Python2では文字列とバイト列が区別されていないため、データの送受信はそのまま文字列としていましたが、Python3ではバイト列としなければなりません。
データ受信
11行目で、サーバからのデータを受信します。
受信データは、bytesオブジェクトおよびタプルされた送信元(サーバ)のアドレスとポート番号が、タプルとして返されます。
socketオブジェクトからrecvfrom()で指定したバッファサイズを読み取り、このバッファサイズは基本的に2の累乗の値を指定します(ここでは4096byteを指定)。
レスポンスの表示
13行目でサーバからのレスポンスを表示しています。
"data"はbytesオブジェクトの為、decode()でstr型に変換し、strip()で改行("\n")を取り除いています。
"addr"には、サーバのアドレスとポート番号がタプルで格納されているので、それぞれインデックスを使用して表示しています。
データ送受信を行ってみる
WSLのUbuntu上でnc(netcat)コマンドを実行し、簡易UDPサーバを起動します。
なお、echoコマンドにパイプしてあげると、接続してきたクライアントにメッセージを送信することができます。
そして、コマンドプロンプトからPythonスクリプトを実行すると、以下のメッセージが送受信されます。
# server > echo "OK!" | nc -ul 8000 this is a test message. # client > python udp_client.py [*] Received:OK! from 127.0.0.1:8000
最後に
以上がUDPクライアントをPythonで実装したものになります。
UDPはTCPのような信頼性や順序性、またデータ完全性が保証されていない分、オーバーヘッドも少なく、そのヘッダ構造もとてもシンプルなものとなっています。
次回はサーバの実装について学んでいきます。