LoginSignup
7
6

More than 5 years have passed since last update.

Pythonでプロミスキャスモードを実装してみた

Last updated at Posted at 2019-01-02

最近、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_ifindexmr_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()

これでプロミスキャス・モードが有効になりました。

ソースコード

ここにはソースコードの中で、プロミスキャスモードの実装部分を載せておきます。
完全なソースコードはこちらにあります。パケットのパーサーも開発途中ですが書いてあるので、本当にプロミスキャスモードが有効になっているのか検証しつつパケット解析してみてはいかがでしょうか?

demo.py

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

参考

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6