Skip to content

Commit a9bda29

Browse files
committed
Add support of Message-Authenticator AVP
* Packet class now extend OrderedDict to maintain correct AVP order and HMAC/MD5 check fo Message-Authenticator * Fix client_async for Accounting, Coa
1 parent 0ada747 commit a9bda29

File tree

8 files changed

+410
-55
lines changed

8 files changed

+410
-55
lines changed

example/acct_async.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/python
2+
3+
import asyncio
4+
5+
import logging
6+
import traceback
7+
from pyrad.dictionary import Dictionary
8+
from pyrad.client_async import ClientAsync
9+
from pyrad.packet import AccountingResponse
10+
11+
logging.basicConfig(level="DEBUG",
12+
format="%(asctime)s [%(levelname)-8s] %(message)s")
13+
client = ClientAsync(server="127.0.0.1",
14+
secret=b"Kah3choteereethiejeimaeziecumi",
15+
timeout=3, debug=True,
16+
dict=Dictionary("dictionary"))
17+
18+
loop = asyncio.get_event_loop()
19+
20+
21+
def create_request(client, user):
22+
req = client.CreateAcctPacket(User_Name=user)
23+
24+
req["NAS-IP-Address"] = "192.168.1.10"
25+
req["NAS-Port"] = 0
26+
req["Service-Type"] = "Login-User"
27+
req["NAS-Identifier"] = "trillian"
28+
req["Called-Station-Id"] = "00-04-5F-00-0F-D1"
29+
req["Calling-Station-Id"] = "00-01-24-80-B3-9C"
30+
req["Framed-IP-Address"] = "10.0.0.100"
31+
32+
return req
33+
34+
35+
def print_reply(reply):
36+
print("Received Accounting-Response")
37+
38+
print("Attributes returned by server:")
39+
for i in reply.keys():
40+
print("%s: %s" % (i, reply[i]))
41+
42+
43+
def test_acct1(enable_message_authenticator=False):
44+
45+
global client
46+
47+
try:
48+
# Initialize transports
49+
loop.run_until_complete(
50+
asyncio.ensure_future(
51+
client.initialize_transports(enable_auth=True,
52+
# local_addr='127.0.0.1',
53+
# local_auth_port=8000,
54+
enable_acct=True,
55+
enable_coa=True)))
56+
57+
req = create_request(client, "wichert")
58+
if enable_message_authenticator:
59+
req.add_message_authenticator()
60+
61+
future = client.SendPacket(req)
62+
63+
# loop.run_until_complete(future)
64+
loop.run_until_complete(asyncio.ensure_future(
65+
asyncio.gather(
66+
future,
67+
return_exceptions=True
68+
)
69+
70+
))
71+
72+
if future.exception():
73+
print('EXCEPTION ', future.exception())
74+
else:
75+
reply = future.result()
76+
77+
if reply.code == AccountingResponse:
78+
print("Accounting accepted")
79+
80+
print("Attributes returned by server:")
81+
for i in reply.keys():
82+
print("%s: %s" % (i, reply[i]))
83+
84+
# Close transports
85+
loop.run_until_complete(asyncio.ensure_future(
86+
client.deinitialize_transports()))
87+
print('END')
88+
89+
del client
90+
except Exception as exc:
91+
print('Error: ', exc)
92+
print('\n'.join(traceback.format_exc().splitlines()))
93+
# Close transports
94+
loop.run_until_complete(asyncio.ensure_future(
95+
client.deinitialize_transports()))
96+
97+
loop.close()
98+
99+
100+
#test_acct1()
101+
test_acct1(enable_message_authenticator=True)

example/auth_async.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
logging.basicConfig(level="DEBUG",
1212
format="%(asctime)s [%(levelname)-8s] %(message)s")
13-
client = ClientAsync(server="localhost",
13+
client = ClientAsync(server="127.0.0.1",
1414
secret=b"Kah3choteereethiejeimaeziecumi",
1515
timeout=3, debug=True,
1616
dict=Dictionary("dictionary"))
@@ -31,6 +31,7 @@ def create_request(client, user):
3131

3232
return req
3333

34+
3435
def print_reply(reply):
3536
if reply.code == AccessAccept:
3637
print("Access accepted")
@@ -41,6 +42,7 @@ def print_reply(reply):
4142
for i in reply.keys():
4243
print("%s: %s" % (i, reply[i]))
4344

45+
4446
def test_auth1():
4547

4648
global client
@@ -50,13 +52,11 @@ def test_auth1():
5052
loop.run_until_complete(
5153
asyncio.ensure_future(
5254
client.initialize_transports(enable_auth=True,
53-
#local_addr='127.0.0.1',
54-
#local_auth_port=8000,
55+
# local_addr='127.0.0.1',
56+
# local_auth_port=8000,
5557
enable_acct=True,
5658
enable_coa=True)))
5759

58-
59-
6060
req = client.CreateAuthPacket(User_Name="wichert")
6161

6262
req["NAS-IP-Address"] = "192.168.1.10"
@@ -107,6 +107,7 @@ def test_auth1():
107107

108108
loop.close()
109109

110+
110111
def test_multi_auth():
111112

112113
global client
@@ -117,12 +118,10 @@ def test_multi_auth():
117118
asyncio.ensure_future(
118119
client.initialize_transports(enable_auth=True,
119120
local_addr='127.0.0.1',
120-
#local_auth_port=8000,
121+
# local_auth_port=8000,
121122
enable_acct=True,
122123
enable_coa=True)))
123124

124-
125-
126125
reqs = []
127126
for i in range(150):
128127
req = create_request(client, "user%s" % i)
@@ -162,6 +161,7 @@ def test_multi_auth():
162161

163162
loop.close()
164163

164+
165165
def test_multi_client():
166166

167167
clients = []
@@ -189,8 +189,8 @@ def test_multi_client():
189189
enable_coa=False)))
190190

191191
# Send
192-
for i in range(n_req4client):
193-
req = create_request(client, "user%s" % i)
192+
for j in range(n_req4client):
193+
req = create_request(client, "user%s" % j)
194194
print('CREATE REQUEST with id %d' % req.id)
195195
future = client.SendPacket(req)
196196
reqs.append(future)
@@ -240,6 +240,64 @@ def test_multi_client():
240240
loop.close()
241241

242242

243-
#test_multi_auth()
244-
#test_auth1()
245-
test_multi_client()
243+
def test_auth1_msg_authenticator():
244+
global client
245+
246+
try:
247+
# Initialize transports
248+
loop.run_until_complete(
249+
asyncio.ensure_future(
250+
client.initialize_transports(enable_auth=True,
251+
# local_addr='127.0.0.1',
252+
# local_auth_port=8000,
253+
enable_acct=True,
254+
enable_coa=True)))
255+
256+
req = create_request(client, "wichert")
257+
req.add_message_authenticator()
258+
259+
future = client.SendPacket(req)
260+
261+
# loop.run_until_complete(future)
262+
loop.run_until_complete(asyncio.ensure_future(
263+
asyncio.gather(
264+
future,
265+
return_exceptions=True
266+
)
267+
268+
))
269+
270+
if future.exception():
271+
print('EXCEPTION ', future.exception())
272+
else:
273+
reply = future.result()
274+
275+
if reply.code == AccessAccept:
276+
print("Access accepted")
277+
else:
278+
print("Access denied")
279+
280+
print("Attributes returned by server:")
281+
for i in reply.keys():
282+
print("%s: %s" % (i, reply[i]))
283+
284+
# Close transports
285+
loop.run_until_complete(asyncio.ensure_future(
286+
client.deinitialize_transports()))
287+
print('END')
288+
289+
del client
290+
except Exception as exc:
291+
print('Error: ', exc)
292+
print('\n'.join(traceback.format_exc().splitlines()))
293+
# Close transports
294+
loop.run_until_complete(asyncio.ensure_future(
295+
client.deinitialize_transports()))
296+
297+
loop.close()
298+
299+
300+
# test_multi_auth()
301+
# test_auth1()
302+
# test_multi_client()
303+
test_auth1_msg_authenticator()

example/pyrad.log

Whitespace-only changes.

example/server_async.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@
2121

2222
class FakeServer(ServerAsync):
2323

24-
def __init__(self, loop, dictionary):
24+
def __init__(self, loop, dictionary, enable_message_authenticator=False):
2525

2626
ServerAsync.__init__(self, loop=loop, dictionary=dictionary,
2727
enable_pkt_verify=True, debug=True)
28+
self.enable_message_authenticator = enable_message_authenticator
2829

2930

3031
def handle_auth_packet(self, protocol, pkt, addr):
@@ -43,6 +44,10 @@ def handle_auth_packet(self, protocol, pkt, addr):
4344
})
4445

4546
reply.code = AccessAccept
47+
48+
if self.enable_message_authenticator and pkt.message_authenticator:
49+
reply.add_message_authenticator()
50+
4651
protocol.send_response(reply, addr)
4752

4853
def handle_acct_packet(self, protocol, pkt, addr):
@@ -53,6 +58,9 @@ def handle_acct_packet(self, protocol, pkt, addr):
5358
print("%s: %s" % (attr, pkt[attr]))
5459

5560
reply = self.CreateReplyPacket(pkt)
61+
62+
if self.enable_message_authenticator and pkt.message_authenticator:
63+
reply.add_message_authenticator()
5664
protocol.send_response(reply, addr)
5765

5866
def handle_coa_packet(self, protocol, pkt, addr):
@@ -63,6 +71,8 @@ def handle_coa_packet(self, protocol, pkt, addr):
6371
print("%s: %s" % (attr, pkt[attr]))
6472

6573
reply = self.CreateReplyPacket(pkt)
74+
if self.enable_message_authenticator and pkt.message_authenticator:
75+
reply.add_message_authenticator()
6676
protocol.send_response(reply, addr)
6777

6878
def handle_disconnect_packet(self, protocol, pkt, addr):
@@ -75,14 +85,18 @@ def handle_disconnect_packet(self, protocol, pkt, addr):
7585
reply = self.CreateReplyPacket(pkt)
7686
# COA NAK
7787
reply.code = 45
88+
89+
if self.enable_message_authenticator and pkt.message_authenticator:
90+
reply.add_message_authenticator()
7891
protocol.send_response(reply, addr)
7992

8093

8194
if __name__ == '__main__':
8295

8396
# create server and read dictionary
8497
loop = asyncio.get_event_loop()
85-
server = FakeServer(loop=loop, dictionary=Dictionary('dictionary'))
98+
server = FakeServer(loop=loop, dictionary=Dictionary('dictionary'),
99+
enable_message_authenticator=True)
86100

87101
# add clients (address, secret, name)
88102
server.hosts["127.0.0.1"] = RemoteHost("127.0.0.1",

pyrad/client_async.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,16 +132,29 @@ def datagram_received(self, data, addr):
132132

133133
reply = Packet(packet=data, dict=self.client.dict)
134134

135-
if reply and reply.id in self.pending_requests:
135+
if reply is not None and reply.id in self.pending_requests:
136136
req = self.pending_requests[reply.id]
137137
packet = req['packet']
138138

139139
reply.secret = packet.secret
140140

141141
if packet.VerifyReply(reply, data):
142-
req['future'].set_result(reply)
143-
# Remove request for map
144-
del self.pending_requests[reply.id]
142+
143+
if reply.message_authenticator and not \
144+
reply.verify_message_authenticator(
145+
original_authenticator=packet.authenticator):
146+
self.logger.warn(
147+
'[%s:%d] Received invalid reply for id %d. %s' % (
148+
self.server, self.port, reply.id,
149+
'Invalid Message-Authenticator. Ignoring it.'
150+
)
151+
)
152+
self.errors += 1
153+
else:
154+
155+
req['future'].set_result(reply)
156+
# Remove request for map
157+
del self.pending_requests[reply.id]
145158
else:
146159
self.logger.warn(
147160
'[%s:%d] Received invalid reply for id %d. %s' % (
@@ -430,9 +443,14 @@ def SendPacket(self, pkt):
430443
if not self.protocol_acct:
431444
raise Exception('Transport not initialized')
432445

446+
self.protocol_acct.send_packet(pkt, ans)
447+
433448
elif isinstance(pkt, CoAPacket):
434449
if not self.protocol_coa:
435450
raise Exception('Transport not initialized')
451+
452+
self.protocol_coa.send_packet(pkt, ans)
453+
436454
else:
437455
raise Exception('Unsupported packet')
438456

0 commit comments

Comments
 (0)