C でプログラムを書くのはつかれる,Python のほうが楽だとおもって,Python による raw socket 通信のデモ・プログラムを書いた. おもったほど容易ではなかったが,ほぼ 1 秒ごとに Ethernet のパケットを送受信するプログラムをしめす. (この項目全体は通常の Creative Commons ライセンスによるが,プログラムは public domain とする. つまり,無制限にコピーしてよい.)
Raw socket の指定は容易だが,ほんとうはさらに promiscuous mode で通信できるようにしたかった. しかし,Python で promiscuous mode をまともに実現するのはけっこうめんどうなようだ. とりあえずはアドレスをしっかり指定するようにしている.
### Ethernet packet sender/receiver (for Linux raw socket) ### # # Public domain software # # Coded by Yasusi Kanada # 2014-7-29 import optparse, socket, time, binascii BUF_SIZE = 1600 # > 1500 ETH_P_ALL = 3 # To receive all Ethernet protocols # Interface = "eth0" Interface = "eth1" host = socket.gethostbyname(socket.gethostname()) ### Packet field access ### def SMAC(packet): return binascii.hexlify(packet[6:12]).decode() def DMAC(packet): return binascii.hexlify(packet[0:6]).decode() def EtherType(packet): return binascii.hexlify(packet[12:14]).decode() def Payload(packet): return binascii.hexlify(packet[14:]).decode() ### Packet handler ### def printPacket(packet, now, message): # print(message, len(packet), "bytes time:", now, # "\n SMAC:", SMAC(packet), " DMAC:", DMAC(packet), # " Type:", EtherType(packet), "\n Payload:", Payload(packet)) # !! Python 3 !! print message, len(packet), "bytes time:", now, \ "\n SMAC:", SMAC(packet), " DMAC:", DMAC(packet), " Type:", \ EtherType(packet), "\n Payload:", Payload(packet) # !! Python 2 !! def terminal(): # Parse command line parser = optparse.OptionParser() parser.add_option("--p", "--port", dest = "port", type="int", help = "Local network port id") parser.add_option("--lm", "--lmac", "--localMAC", dest = "lmac", type="str", help = "Local MAC address") parser.add_option("--rm", "--rmac", "--remoteMAC", dest = "rmac", type="str", help = "Remote MAC address") parser.add_option("--receiveOnly", "--receiveonly", dest = "receiveOnly", action = "store_true") # parser.add_option("--promiscuous", dest = "promiscuous", action = "store_true") parser.set_defaults(lmac = "ffffffffffff", rmac = "ffffffffffff") opts, args = parser.parse_args() # Open socket sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(ETH_P_ALL)) sock.bind((Interface, ETH_P_ALL)) sock.setblocking(0) # Contents of packet to send (constant) sendPacket = binascii.unhexlify(opts.rmac) + binascii.unhexlify(opts.lmac) + \ b'\x88\xb5' + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' # Repeat sending and receiving packets interval = 1 lastTime = time.time() while True: now = time.time() try: packet = sock.recv(BUF_SIZE) except socket.error: pass else: dmac = DMAC(packet) printPacket(packet, now, "Received:") if not opts.receiveOnly: if now > lastTime + interval: sendBytes = sock.send(sendPacket) printPacket(sendPacket, now, "Sent: ") lastTime = now else: time.sleep(0.001001) else: time.sleep(0.001001) terminal()
送信するパケットには Ethernet type として 0x88b5 がつき,内容は 0x000102… となるが,これらは容易にかえられる.
これは Python 2 用だが,printPacket のところだけなおす (Python 2 用のコードをコメントアウトして Python 3 用のコードをいかす) と Python 3 でもつかえる. Ethernet では通信の際にインタフェース名を指定する必要があるが,ここでは eth1 を指定している. "Interface" という変数の値をかきかえることで容易に変更できる.
つかいかたはつぎのとおりだ. 上記のプログラムに eterm.py というファイル名がついているとする. かんたんなユニキャストの通信 (送信 + 受信) はつぎのようにする.
python eterm.py --lm 00123456789a --rm 00a987654321
ここで MAC アドレスはかならず手でいれる必要がある. (ifconfig コマンドでしらべてコピーする).
つぎのように受信者のアドレスを指定しないとブロードキャストになる (送信アドレスも省略可能).
python eterm.py --lm 00123456789a
送信せず受信だけするときはつぎのようにする.
python eterm.py --receiveOnly
プログラムを 2 台のマシンで実行して相互にパケットを送受信することもできるし,片方は受信だけ,もう片方は送受信することで,一方向の通信をすることもできる. 一方向の通信は Ethernet スイッチの機能をたしかめるのに有効だ.