@@ -8,6 +8,10 @@ from scapy.contrib.coap_socket import *
88
99= Redirect logging
1010import logging
11+ import shutil
12+ import socket
13+ import subprocess
14+ import time
1115from scapy.error import log_runtime
1216
1317from io import StringIO
@@ -17,6 +21,73 @@ handler = logging.StreamHandler(log_stream)
1721log_runtime.addHandler(handler)
1822log_coap_sock.addHandler(handler)
1923
24+ coap_client_bin = shutil.which("coap-client")
25+ coap_server_bin = shutil.which("coap-server")
26+ libcoap_available = coap_client_bin is not None and coap_server_bin is not None
27+
28+
29+ def _pick_port():
30+ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
31+ s.bind(("127.0.0.1", 0))
32+ p = s.getsockname()[1]
33+ s.close()
34+ return p
35+
36+
37+ def _run_coap_client(uri, timeout=5):
38+ p = subprocess.Popen(
39+ [coap_client_bin, "-m", "get", "-B", "2", uri],
40+ stdout=subprocess.PIPE,
41+ stderr=subprocess.PIPE,
42+ )
43+ try:
44+ out, err = p.communicate(timeout=timeout)
45+ except subprocess.TimeoutExpired:
46+ p.kill()
47+ out, err = p.communicate()
48+ return p.returncode, out, err
49+
50+
51+ def _start_coap_server(port):
52+ p = subprocess.Popen(
53+ [coap_server_bin, "-A", "127.0.0.1", "-p", str(port), "-v", "0"],
54+ stdout=subprocess.PIPE,
55+ stderr=subprocess.PIPE,
56+ )
57+ time.sleep(0.4)
58+ return p
59+
60+
61+ def _cleanup_process(p):
62+ if p is None or p.poll() is not None:
63+ return
64+ p.terminate()
65+ try:
66+ p.wait(timeout=2)
67+ except subprocess.TimeoutExpired:
68+ p.kill()
69+ p.wait(timeout=2)
70+
71+
72+ class InteropResource(CoAPResource):
73+ def get(self, payload, options, token, sa_ll):
74+ return {
75+ "type": ACK,
76+ "code": CONTENT_205,
77+ "options": [(CONTENT_FORMAT, CF_TEXT_PLAIN)],
78+ "payload": b"scapy-libcoap-interop",
79+ }
80+
81+
82+ class StressResource(CoAPResource):
83+ def get(self, payload, options, token, sa_ll):
84+ return {
85+ "type": ACK,
86+ "code": CONTENT_205,
87+ "options": [(CONTENT_FORMAT, CF_TEXT_PLAIN)],
88+ "payload": b"scapy-stress",
89+ }
90+
2091+ Testing client -> server interactions
2192
2293= Setup dummy resources
@@ -150,3 +221,81 @@ with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_server,
150221 assert rcv.code == CONTENT_205
151222 assert rcv.msg_id == pkt.msg_id
152223 assert rcv.token == pkt.token
224+
225+ + Testing interoperability with libcoap
226+
227+ = libcoap client -> CoAPSocket server
228+
229+ if not libcoap_available:
230+ print("Skipping libcoap interoperability tests: coap-client/coap-server not found")
231+ assert True
232+ else:
233+ interop_port = _pick_port()
234+ with CoAPSocket(
235+ "127.0.0.1",
236+ interop_port,
237+ lst_resources=[InteropResource("/interop")]
238+ ) as coap_server:
239+ rc, out, err = _run_coap_client("coap://127.0.0.1:%d/interop" % interop_port)
240+ assert rc == 0, "coap-client failed rc=%s stderr=%r" % (rc, err)
241+ assert b"scapy-libcoap-interop" in out, "Unexpected payload: %r" % out
242+
243+ = CoAPSocket client -> libcoap server
244+
245+ if not libcoap_available:
246+ print("Skipping libcoap interoperability tests: coap-client/coap-server not found")
247+ assert True
248+ else:
249+ libcoap_port = _pick_port()
250+ libcoap_server = _start_coap_server(libcoap_port)
251+ try:
252+ with CoAPSocket("127.0.0.1", _pick_port()) as coap_client:
253+ req = CoAPSocket.make_coap_req_packet(uri=".well-known/core", payload=b"")
254+ coap_client.send(IP(dst="127.0.0.1")/UDP(dport=libcoap_port)/req)
255+ res = coap_client.recv()
256+ assert res is not None
257+ assert res.code == CONTENT_205
258+ assert b"</" in res.payload.load
259+ assert res.token == req.token
260+ finally:
261+ _cleanup_process(libcoap_server)
262+
263+ + Stress tests
264+
265+ = Stress: libcoap client -> CoAPSocket server
266+
267+ if not libcoap_available:
268+ print("Skipping libcoap interoperability tests: coap-client/coap-server not found")
269+ assert True
270+ else:
271+ stress_port = _pick_port()
272+ with CoAPSocket("127.0.0.1", stress_port, lst_resources=[StressResource("/stress")]):
273+ for i in range(25):
274+ rc, out, err = _run_coap_client("coap://127.0.0.1:%d/stress" % stress_port)
275+ assert rc == 0, "coap-client stress failed #%d rc=%s stderr=%r" % (
276+ i, rc, err
277+ )
278+ assert b"scapy-stress" in out
279+
280+ = Stress: CoAPSocket client -> libcoap server
281+
282+ if not libcoap_available:
283+ print("Skipping libcoap interoperability tests: coap-client/coap-server not found")
284+ assert True
285+ else:
286+ stress_libcoap_port = _pick_port()
287+ stress_libcoap_server = _start_coap_server(stress_libcoap_port)
288+ try:
289+ with CoAPSocket("127.0.0.1", _pick_port()) as coap_client:
290+ for _ in range(25):
291+ req = CoAPSocket.make_coap_req_packet(uri=".well-known/core", payload=b"")
292+ coap_client.send(
293+ IP(dst="127.0.0.1") / UDP(dport=stress_libcoap_port) / req
294+ )
295+ res = coap_client.recv()
296+ assert res is not None
297+ assert res.code == CONTENT_205
298+ assert b"</" in res.payload.load
299+ assert res.token == req.token
300+ finally:
301+ _cleanup_process(stress_libcoap_server)
0 commit comments