Skip to content

Commit c11be37

Browse files
author
Nils Weiss
committed
Add interoperability and stress testing for CoAPSocket with libcoap
AI-Assisted: yes (GitHub CoPilot Auto)
1 parent b77c69e commit c11be37

3 files changed

Lines changed: 153 additions & 2 deletions

File tree

.config/ci/install.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ then
3636
sudo apt-get update
3737
sudo apt-get -qy install tshark net-tools || exit 1
3838
sudo apt-get -qy install can-utils || exit 1
39+
sudo apt-get -qy install libcoap3-bin || exit 1
3940
sudo apt-get -qy install linux-modules-extra-$(uname -r) || exit 1
4041
sudo apt-get -qy install samba smbclient
4142
sudo bash $CUR/openldap/install.sh

scapy/contrib/coap_socket.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ class CoAPSocket(SuperSocket):
7979
>>> return CoAPSocket.empty_ack_params()
8080
>>> # Doesn't matter if it starts with "/dummy" or "dummy",
8181
>>> # but it is an error if it is in the end
82-
>>> lst_resources = [DummyResource("dummy"), DelayedResource("/delayed")].
82+
>>> lst_resources = [DummyResource("dummy"), DelayedResource("/delayed")]
8383
>>> with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_socket:
8484
>>> while True:
8585
>>> pkg = coap_socket.recv()
@@ -263,7 +263,8 @@ def __init__(self,
263263
self.url = url
264264
if self.url[0] != "/":
265265
self.url = "/" + self.url
266-
self.description = description # interface description ("if" in CoRE link-format)
266+
# interface description ("if" in CoRE link-format)
267+
self.description = description
267268
self.content_format = content_format # ct
268269
self.resource_type = resource_type # rt
269270
self.title = title # title

test/contrib/coap_socket.uts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ from scapy.contrib.coap_socket import *
88

99
= Redirect logging
1010
import logging
11+
import shutil
12+
import socket
13+
import subprocess
14+
import time
1115
from scapy.error import log_runtime
1216

1317
from io import StringIO
@@ -17,6 +21,73 @@ handler = logging.StreamHandler(log_stream)
1721
log_runtime.addHandler(handler)
1822
log_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

Comments
 (0)