Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added CoAP socket #4334

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Added CoAP socket #4334

wants to merge 2 commits into from

Conversation

eHonnef
Copy link
Contributor

@eHonnef eHonnef commented Mar 23, 2024

Description

This PR implements a CoAP socket, pretty similar on how ISOTPSoftSocket works.
I implemented the basic message exchange, mostly based on the RFC-7252.

  • Congestion control
  • Retransmission mechanism
  • Separate responses
  • Message duplication detection

Known-limitations

  • No POST and DELETE methods
  • No DTLS
  • No discovery via multicast/broadcast, although you can still bind to one of these interfaces
  • No observer

General comments

It has a dependency for from scapy.contrib.isotp.isotp_soft_socket import TimeoutScheduler, I found nice how this is implemented, so I just used it, I didn't want to copy/paste again.

Also I added some unit tests for the basic cases.

Quick usage

Client example:
    >>> with CoAPSocket("127.0.0.1", 1234) as coap_client:
    >>>     req = CoAPSocket.make_coap_req_packet(method=GET, uri="endpoint-uri", payload=b"")
    >>>     coap_client.send("127.0.0.1", 5683, req)
    >>>     res = coap_client.recv() # Careful, this will block until the coap_client receives something

Server without specifying resources:
    >>> with CoAPSocket("127.0.0.1", 5683) as coap_server:
    >>>     while True:
    >>>         pkg = coap_server.recv()
    >>>         handle_package(pkg)

Server with custom resources:
    >>> class DummyResource(CoAPResource):
    >>>     def get(self, payload, options, token, sa_ll):
    >>>         return {"type": ACK, "code": CONTENT_205, "options": [(CONTENT_FORMAT, CF_TEXT_PLAIN)], "payload": b'dummy response'}
    >>>
    >>> class DelayedResource(CoAPResource):
    >>>     def __init__(self, url):
    >>>         CoAPResource.__init__(self, url=url)
    >>>         self.delayed_tokens = []
    >>>
    >>>     def delayed_message(self):
    >>>         token, address = self.delayed_tokens.pop(0)
    >>>         pkt = CoAPSocket.make_delayed_resp_packet(token, [(CONTENT_FORMAT, CF_TEXT_PLAIN)], b"delayed payload")
    >>>         self._send_separate_response(pkt, address)
    >>>
    >>>     def get(self, payload, options, token, sa_ll):
    >>>         # We know that this can take a while, so we return an empty ACK now and wait for whatever resource to be available.
    >>>         TimeoutScheduler.schedule(1, self.delayed_message)
    >>>         self.delayed_tokens.append((token, sa_ll))
    >>>         return CoAPSocket.empty_ack_params()
    >>>
    >>> # Doesn't matter if it starts with "/dummy" or "dummy", but it is an error if it is in the end
    >>> lst_resources = [DummyResource("dummy"), DelayedResource("/delayed")].
    >>> with CoAPSocket("127.0.0.1", 5683, lst_resources=lst_resources) as coap_socket:
    >>>     while True:
    >>>         pkg = coap_socket.recv()
    >>>         # You can handle the packages inside your resources, here will only be the "unhandled" ones.

if sock is None:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((self.ip, self.port))

Check warning

Code scanning / CodeQL

Binding a socket to all network interfaces Medium

'' binds a socket to all interfaces.
Copy link

codecov bot commented Mar 23, 2024

Codecov Report

Merging #4334 (f6b86b5) into master (6294c6e) will increase coverage by 0.24%.
Report is 119 commits behind head on master.
The diff coverage is 89.42%.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #4334      +/-   ##
==========================================
+ Coverage   81.90%   82.15%   +0.24%     
==========================================
  Files         330      351      +21     
  Lines       76380    83547    +7167     
==========================================
+ Hits        62561    68640    +6079     
- Misses      13819    14907    +1088     
Files Coverage Δ
scapy/contrib/coap.py 96.42% <100.00%> (+0.93%) ⬆️
scapy/contrib/coap_socket.py 88.60% <88.60%> (ø)

... and 114 files with indirect coverage changes

@eHonnef eHonnef force-pushed the coap-socket branch 2 times, most recently from 245e388 to 7ee64b2 Compare March 23, 2024 22:10
@eHonnef
Copy link
Contributor Author

eHonnef commented Mar 24, 2024

Any idea on how I can fix these failed tests?
If I try to run on my machine without root, it works, so I don't know really how to debug this.

@gpotter2
Copy link
Member

Tests are failing on versions older than 3.9. You're using 3.9+ specific features. Scapy should work with anything starting from 3.7, so this probably needs to be updated.

Fixing response payload

Some docstring and bug fixes.

Finished CoAP server logic implementation

Added client interaction

Client/Server done.

Added delayed response handling

Fixing small problems

Unit tests

Documentation
@eHonnef
Copy link
Contributor Author

eHonnef commented Mar 24, 2024

@gpotter2 Thanks for the insights, it is ready for reviews :D

scapy/contrib/coap_socket.py Outdated Show resolved Hide resolved
scapy/contrib/coap_socket.py Show resolved Hide resolved
scapy/contrib/coap_socket.py Outdated Show resolved Hide resolved
- Moved the defines/enumerators to coap.py
- Changed the send() function to match the SuperSocket declaration
- Updated unit tests
@polybassa
Copy link
Contributor

Could you please implement a select function for your socket, so that sr, sr1 and sniff will work. Could you please also provide unit tests for these functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants