1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# By Peter V. Saveliev https://pypi.python.org/pypi/pyroute2. Dual licensed under the Apache 2 and GPLv2+ see https://github.com/svinota/pyroute2 for License details.
'''
IPv4 DHCP socket
================
'''
from pyroute2.common import AddrPool
from pyroute2.protocols import udpmsg
from pyroute2.protocols import udp4_pseudo_header
from pyroute2.protocols import ethmsg
from pyroute2.protocols import ip4msg
from pyroute2.protocols.rawsocket import RawSocket
from pyroute2.dhcp.dhcp4msg import dhcp4msg
def listen_udp_port(port=68):
# pre-scripted BPF code that matches UDP port
bpf_code = [[40, 0, 0, 12],
[21, 0, 8, 2048],
[48, 0, 0, 23],
[21, 0, 6, 17],
[40, 0, 0, 20],
[69, 4, 0, 8191],
[177, 0, 0, 14],
[72, 0, 0, 16],
[21, 0, 1, port],
[6, 0, 0, 65535],
[6, 0, 0, 0]]
return bpf_code
class DHCP4Socket(RawSocket):
'''
Parameters:
* ifname -- interface name to work on
This raw socket binds to an interface and installs BPF filter
to get only its UDP port. It can be used in poll/select and
provides also the context manager protocol, so can be used in
`with` statements.
It does not provide any DHCP state machine, and does not inspect
DHCP packets, it is totally up to you. No default values are
provided here, except `xid` -- DHCP transaction ID. If `xid` is
not provided, DHCP4Socket generates it for outgoing messages.
'''
def __init__(self, ifname, port=68):
RawSocket.__init__(self, ifname, listen_udp_port(port))
self.port = port
# Create xid pool
#
# Every allocated xid will be released automatically after 1024
# alloc() calls, there is no need to call free(). Minimal xid == 16
self.xid_pool = AddrPool(minaddr=16, release=1024)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
self.close()
def put(self, msg=None, dport=67):
'''
Put DHCP message. Parameters:
* msg -- dhcp4msg instance
* dport -- DHCP server port
If `msg` is not provided, it is constructed as default
BOOTREQUEST + DHCPDISCOVER.
Examples::
sock.put(dhcp4msg({'op': BOOTREQUEST,
'chaddr': 'ff:11:22:33:44:55',
'options': {'message_type': DHCPREQUEST,
'parameter_list': [1, 3, 6, 12, 15],
'requested_ip': '172.16.101.2',
'server_id': '172.16.101.1'}}))
The method returns dhcp4msg that was sent, so one can get from
there `xid` (transaction id) and other details.
'''
# DHCP layer
dhcp = msg or dhcp4msg({'chaddr': self.l2addr})
# dhcp transaction id
if dhcp['xid'] is None:
dhcp['xid'] = self.xid_pool.alloc()
data = dhcp.encode().buf
# UDP layer
udp = udpmsg({'sport': self.port,
'dport': dport,
'len': 8 + len(data)})
udph = udp4_pseudo_header({'dst': '255.255.255.255',
'len': 8 + len(data)})
udp['csum'] = self.csum(udph.encode().buf + udp.encode().buf + data)
udp.reset()
# IPv4 layer
ip4 = ip4msg({'len': 20 + 8 + len(data),
'proto': 17,
'dst': '255.255.255.255'})
ip4['csum'] = self.csum(ip4.encode().buf)
ip4.reset()
# MAC layer
eth = ethmsg({'dst': 'ff:ff:ff:ff:ff:ff',
'src': self.l2addr,
'type': 0x800})
data = eth.encode().buf +\
ip4.encode().buf +\
udp.encode().buf +\
data
self.send(data)
dhcp.reset()
return dhcp
def get(self):
'''
Get the next incoming packet from the socket and try
to decode it as IPv4 DHCP. No analysis is done here,
only MAC/IPv4/UDP headers are stripped out, and the
rest is interpreted as DHCP.
'''
(data, addr) = self.recvfrom(4096)
eth = ethmsg(buf=data).decode()
ip4 = ip4msg(buf=data, offset=eth.offset).decode()
udp = udpmsg(buf=data, offset=ip4.offset).decode()
return dhcp4msg(buf=data, offset=udp.offset).decode()
|