最近、PythonでSocketプログラミングすることにハマっていて、Pythonでプロミスキャス・モードで通信をキャプチャしたいなぁと思っていました。まあ、サードパーティのライブラリを使用すれば簡単に実装できるのですが、今回はそれらに頼らない方向で実装してみることにしました。
実際に方法を調べていると、標準ライブラリのfcntlのioctlメソッドで実装している例がいくつかありました。その1つが python-sniffer/snifferCore.py at master · zeigotaro/python-sniffer です。
でも、この方法めんどくね?w
C言語だったら、まずこの方法はとらないと思います。setsockoptとpacket_mreq構造体で実装するでしょう。
ということで、fcntlではなくstructを使用して、C言語ライクな実装をしてみました!
ちなみにstructライブラリとは、C言語で定義されている構造体をPythonでも扱えるように変換してくれる優れものです。
環境
- Ubuntu 18.04
- Python 3.6
Unix環境に依存している関数を使用している部分があるので、Windowsでは正常動作しません。気が向いたらWindows対応させます。
実装
プロミスキャス・モードの実装にはPythonの標準ライブラリのみを使用しているので、パッケージの追加インストールは必要ありません。socketとstructの2つの標準ライブラリがインポートされていればOKです。
バイト列オブジェクトの作成
C構造体を使用してバイト列オブジェクトを作成します。これは、後に出でくるsetsockopt関数で使用します。
使用するC構造体の定義はlinux/if_packet.h
で以下のようにされています。
struct packet_mreq {
int mr_ifindex;
unsigned short mr_type;
unsigned short mr_alen;
unsigned char mr_address[8];
};
変数 | 説明 |
---|---|
mr_ifindex | プロミスキャス・モードを有効化させるインターフェース番号 |
mr_type | 実行するアクション (PACKET_MR_MULTICAST、PACKET_MR_PROMISC etc) |
mr_alen | アドレス長 |
mr_address | MACアドレス |
プロミスキャス・モードで使用するのは、mr_ifindex
とmr_type
です。
上記の構造体を基に、structライブラリでバイト列オブジェクトを作成します。
import struct
iface = 'lo' # loopbackインターフェース
PACKET_MR_PROMISC = 1 # プロミスキャス・モード
ifindex = socket.if_nametoindex(iface)
action = PACKET_MR_PROMISC
alen = 0
address = b'\0'
# バイト列オブジェクトを作成
packet_mreq = struct.pack('iHH8s', ifindex, type, alen, address)
C言語とPythonで互換性があるように、struct.packの第一引数で型の指定を行っています。意味は以下のようになります。
フォーマット | Cの型 | Pythonのタイプ |
---|---|---|
i | int | int |
H | unsigned short int | int |
8s | char[8] | bytes |
ソケットオブジェクトの作成
Ethernetヘッダーからキャプチャするために、ソケットタイプにはSOCK_RAWを指定します。
import socket
ETH_P_ALL = 3 # 全てのプロトコル
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL))
sock.bind((iface, 0))
プロミスキャス・モードの有効化(ソケットオプションの設定)
プロミスキャス・モードを有効にするためにsetsockopt関数を使用してソケットオプションを設定します。
レベルには全プロトコルをキャプチャするためにSOL_PACKET(263)、オプション名にはPACKET_ADD_MEMBERSHIP(1)、オプション値には先に作成したバイト列オブジェクトpacket_mreq
を指定します。
SOL_PACKET = 263
PACKET_ADD_MEMBERSHIP = 1
sock.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, packet_mreq)
while True:
sock.recv(1024) # パケット受信
sock.close()
これでプロミスキャス・モードが有効になりました。
ソースコード
ここにはソースコードの中で、プロミスキャスモードの実装部分を載せておきます。
完全なソースコードはこちらにあります。パケットのパーサーも開発途中ですが書いてあるので、本当にプロミスキャスモードが有効になっているのか検証しつつパケット解析してみてはいかがでしょうか?
import socket
import struct
interface = 'lo'
# For proto argument of socket object
ETH_P_ALL = 3 # All protocol pakcet
# For socket.setsockopt()
SOL_PACKET = 263 # Socket level
PACKET_ADD_MEMBERSHIP = 1 # Socket option name
# For mr_type (action) of packet_mreq structure
PACKET_MR_PROMISC = 1 # Promiscuous mode
# For packet_mreq structure
mr_ifindex = socket.if_nametoindex(interface) # c_type is int
mr_type = PACKET_MR_PROMISC # c_type is unsigned short
mr_alen = 0 # c_type is unsigned short
mr_address = b'\0' # c_type is unsigned char[8]
packet_mreq = struct.pack('iHH8s',
mr_ifindex,
mr_type,
mr_alen,
mr_address)
with socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL)) as sock:
sock.bind((interface, 0))
sock.setsockopt(SOL_PACKET, PACKET_ADD_MEMBERSHIP, packet_mreq)
try:
while True:
packet = sock.recv(1024)
display_packet(packet) # Function to display parsed packet
except KeyboardInterrupt:
print("EXIT")
except:
from traceback import print_exc
print_exc()
プロミスキャスモードでパケットキャプチャするにはroot権限が必要です。
これを実行すると、kernelログにdevice lo entered promiscuous mode
が出力されます。
Pythonでプロミスキャスモードを実装してみましたが、低レイヤーの処理をやるにはC/C++の方がいいですねw