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 スイッチの機能をたしかめるのに有効だ.
