2012年12月03日

Python(Scapy)を使ってDHCP CLIENT(DISCOVER->ACK)を実装してみる

先日、PythonでDHCP DISCOVERを大量に送信するクライアント・アプリケーションを書いてみたんですが、折角なので今日はDHCP 1トランザクション(DHCP DISCOVER ~ DHCP ACKまで)を実装してみようと思います。

network-wired-4

しかし、こういう細かい話はリーチする人が極端に少ないので書いていて虚しさもありますね・・・。

DHCP 1トランザクションの概要


ここでは同一ネットワーク上に存在するクライアント・DHCPサーバー間の基本的なシーケンスについて説明します。

dhcp-transaction

まず初めに、ブロードキャストを使ってクライアントからDHCP DISCOVERメッセージを送信します。
 
このDISCOVERメッセージを受け取ったDHCPサーバーはアドレス・プールからリースするIPを選択し、あればクライアントに対しブローキャストでDHCP OFFERメッセージを送信します。

OFFERを受け取ったクライアントはIPアドレスを確認・問題なければDHCP REQUESTを送信し、最後にこれを受け取ったDHCPサーバーから成立を表すDHCP ACKメッセージ(拒否する場合はNAK)を送信します。

ここまでを1トランザクションとし、Python向けネットワーク・ライブラリ - Scapyを使ってDHCPクライアント(IPv4)を実装してみます。


Scapyのインストール


本家サイトから最新版をダウロードしてインストールする場合には次のコマンドを実行します。

$ wget http://www.secdev.org/projects/scapy/files/scapy-latest.tar.gz
$ tar xzf scapy-latest.tar.gz 
$ cd scapy-.x.x/
$ sudo python setup.py install

もし、Ubuntuなのであれば、apt-getコマンドを使ってインストールすることも可能。

$ sudo apt-get install python-scapy

これで準備が出来ました。 早速簡単なDHCPクライアントを実装してみましょう。


DHCP Client written in Python, Scapy



コードはこちら。

#!/usr/bin/env python
 
from scapy.all import *
import threading
import time
 
MESSAGE_TYPE_OFFER = 2
MESSAGE_TYPE_REQUEST = 3
MESSAGE_TYPE_ACK = 5
MESSAGE_TYPE_NAK = 6
MESSAGE_TYPE_RELEASE = 7
 
conf.iface = "eth0"
 
num_offers = 0;
num_acks = 0;
num_naks = 0;
 
class DHCPDHandler(threading.Thread):
 
  def __init__(self):
    threading.Thread.__init__(self) 
 
  def callbak(self, pkt):
    global num_offers
    global num_acks
    global num_naks
    if DHCP in pkt:
      mtype = pkt[DHCP].options[0][1]
      your_ipaddr = pkt[BOOTP].yiaddr
      client_mac = pkt.dst
      if mtype == MESSAGE_TYPE_OFFER:
        num_offers = num_offers + 1
        print '%s DHCP OFFER(transaction:%s): %s for %s from %s' % (num_offers,pkt[BOOTP].xid,your_ipaddr,client_mac,pkt[IP].src)
        request = (
          Ether(src=client_mac,dst="ff:ff:ff:ff:ff:ff")/
          IP(src="0.0.0.0",dst="255.255.255.255")/
          UDP(sport=68,dport=67)/
          BOOTP(chaddr=pkt[BOOTP].chaddr,xid=pkt[BOOTP].xid)/
          DHCP(options=[('message-type','request'),('requested_addr',your_ipaddr),('end')])
          )
        print "Sending DHCP REQUEST..."
        sendp(request,verbose=0)
 
      elif mtype == MESSAGE_TYPE_ACK:
        num_acks = num_acks + 1
        print '%s DHCP ACK(transaction:%s): %s for %s from %s' % (num_acks,pkt[BOOTP].xid,your_ipaddr,client_mac,pkt[IP].src)
 
      elif mtype == MESSAGE_TYPE_NAK:
        num_naks = num_naks + 1
        print '%s DHCP NAK(transaction:%s): %s for %s from %s' % (num_acks,pkt[BOOTP].xid,your_ipaddr,client_mac,pkt[IP].src)
 
  def run(self):
    sniff(prn=self.callbak, filter="udp and (port 68 or port 67)", store=0)
 
 
dh = DHCPDHandler()
dh.daemon = True
dh.start()
time.sleep(0.5)

mac = str(RandMAC())
chaddr = ''.join([chr(int(x,16)) for x in mac.split(':')])
 
discover = (
    Ether(src=mac,dst="ff:ff:ff:ff:ff:ff")/
    IP(src="0.0.0.0",dst="255.255.255.255")/
    UDP(sport=68,dport=67)/
    BOOTP(chaddr=chaddr,xid=random.randint(0, 0xFFFF))/
    DHCP(options=[('message-type','discover'),('end')])
    )

print "Sending DHCP DISCOVER..."
sendp(discover,verbose=0)
 
input("")

DHCPサーバーとのやり取りが発生するので、先に DHCPDHandler というDHCP OFFER及びDHCP ACK, NAKメセージをハンドリングするスレッドを用意・開始しておきます。

このスレッドは指定したネットワーク・インタフェース(ここではeth0)をsniffし、指定した条件にマッチするEtherオブジェクトをcallbackメソッドで受け取り、DHCPメッセージ・タイプに応じてDHCPサーバーとのやり取りを行います。

DHCP REQUESTメッセージ送信時にはリクエストするIPアドレスをDHCPオプション50番で指定して送信するんですが、ScapyのDHCPオブジェクトでは文字列の’’requested_addr”でこのオプションを指定します。
えー、じゃあ他のオプション番号を表す文字列は何になるの?、と思いますよね。 この対応はscapyに含まれる dhcp.py で定義されています。

DHCPOptions = {
    0: "pad",
    1: IPField("subnet_mask", "0.0.0.0"),
    2: "time_zone",
    3: IPField("router","0.0.0.0"),
    4: IPField("time_server","0.0.0.0"),
    5: IPField("IEN_name_server","0.0.0.0"),
    6: IPField("name_server","0.0.0.0"),
    7: IPField("log_server","0.0.0.0"),
    8: IPField("cookie_server","0.0.0.0"),
    9: IPField("lpr_server","0.0.0.0"),
    12: "hostname",
    14: "dump_path",
    15: "domain",
    17: "root_disk_path",
    22: "max_dgram_reass_size",
    23: "default_ttl",
    24: "pmtu_timeout",
    28: IPField("broadcast_address","0.0.0.0"),
    35: "arp_cache_timeout",
    36: "ether_or_dot3",
    37: "tcp_ttl",
    38: "tcp_keepalive_interval",
    39: "tcp_keepalive_garbage",
    40: "NIS_domain",
    41: IPField("NIS_server","0.0.0.0"),
    42: IPField("NTP_server","0.0.0.0"),
    43: "vendor_specific",
    44: IPField("NetBIOS_server","0.0.0.0"),
    45: IPField("NetBIOS_dist_server","0.0.0.0"),
    50: IPField("requested_addr","0.0.0.0"),
    51: IntField("lease_time", 43200),
    54: IPField("server_id","0.0.0.0"),
    55: "param_req_list",
    57: ShortField("max_dhcp_size", 1500),
    58: IntField("renewal_time", 21600),
    59: IntField("rebinding_time", 37800),
    60: "vendor_class_id",
    61: "client_id",
    
    64: "NISplus_domain",
    65: IPField("NISplus_server","0.0.0.0"),
    69: IPField("SMTP_server","0.0.0.0"),
    70: IPField("POP3_server","0.0.0.0"),
    71: IPField("NNTP_server","0.0.0.0"),
    72: IPField("WWW_server","0.0.0.0"),
    73: IPField("Finger_server","0.0.0.0"),
    74: IPField("IRC_server","0.0.0.0"),
    75: IPField("StreetTalk_server","0.0.0.0"),
    76: "StreetTalk_Dir_Assistance",
    82: "relay_agent_Information",
    53: ByteEnumField("message-type", 1, DHCPTypes),
    #             55: DHCPRequestListField("request-list"),
    255: "end"
    }

えーっと、ここで定義されていないオプション番号を使いたい場合はどうすれば良いんだろう? と思ったんですが、調べては無いです・・・。

ちなみに、マイナーなオプションも含めて一覧で確認したいという人はこちらをどうぞ。

ISC DHCPv4 Options

このスレッドをstart()してから、0.5秒ほどsleepしているのはsniffが開始するまでの猶予時間。 これが無いとsniffが始まるまでにOFFERメッセージが届いてしまう可能性があります。

最後にDHCP DISCOVERメッセージを送信していますが、これは前回と同じ。

トランザクションID(xid)、MACアドレスはランダムに生成しています。


さあ、説明はおしまい。 このコードをsimple-dhcp-client.pyとして保存してから、次のコマンドで起動してみます。

$ sudo python simple-dhcp-client.py 

うまく行くと、こんなメッセージが出力されます。

Sending DHCP DISCOVER...
1 DHCP OFFER(transaction:34311): 10.0.0.42 for 5c:78:35:62:5c:78 from 10.0.0.1
Sending DHCP REQUEST...
1 DHCP ACK(transaction:34311): 10.0.0.42 for 5c:78:35:62:5c:78 from 10.0.0.1

DHCPサーバー (10.0.0.1)からIPアドレス(10.0.0.42)のOFFERを受け取り、DHCP ACKまで成立したようですね。

DHCPサーバー(ここではLinux上で動作するISC-DHCP-SERVERを利用)側を見ると、次のようなログが確認できるはずです。

23:55:09 isc-dhcp-server dhcpd: DHCPDISCOVER from 5c:78:35:62:5c:78 via eth0
23:55:10 isc-dhcp-server dhcpd: DHCPOFFER on 10.0.0.42 to 5c:78:35:62:5c:78 via eth0
23:55:10 isc-dhcp-server dhcpd: DHCPREQUEST for 10.0.0.42 from 5c:78:35:62:5c:78 via eth0
23:55:10 isc-dhcp-server dhcpd: DHCPACK on 10.0.0.42 to 5c:78:35:62:5c:78 via eth0

よーし、これでDHCP programing in Scapyに一区切り付きましたね。

次はリレーエージェントを実装してみようかなあ・・・。



DHCP Handbook, The (Kaleidoscope)
Ralph Lemon, Ted Droms 
Sams 
売り上げランキング: 180943

成功はゴミ箱の中に レイ・クロック自伝―世界一、億万長者を生んだ男 マクドナルド創業者 (PRESIDENT BOOKS)
レイ・A. クロック ロバート アンダーソン 野地 秩嘉 孫 正義 柳井 正 
プレジデント社 
売り上げランキング: 1886

Posted by netbuffalo at 23:00│Comments(0)TrackBack(0)ネットワーク | プログラミング


この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/netbuffalo/4291860

コメントする

名前
URL
 
  絵文字