This repo is basically a wrapper for sending and receiving UDP multicast messages via python. Y.M.M.V. This library is not intended to fully implement the complexities of multicast traffic, rather to allow a user friendly API for python components to send and receive across a multicast transmission. The obvious advantage of this wrapper over unicast solutions is the ability to have multiple nodes communicate concurrently without individual connections for each node pair.
Continuous integration testing is handled by github actions and the generous Circle-CI Service.
The CLI is actually not the best way to use this kind of library so it should not be considered the full implementation. For testing/prototyping though it is quite convenient, thus I begin with it.
CLI should work like so:
multicast (SAY|RECV|HEAR) [-h|--help] [--use-std] [--daemon] [--port PORT] [--iface IFACE] [--pipe|-m MESSAGE|--message MESSAGE] [--group BIND_GROUP] [--groups [JOIN_MCAST_GROUPS ...]]
The commands are SAY
, RECV
, and HEAR
for the CLI and are analogus to send
listen/accept and echo functions of a 1-to-1 connection.
The SAY
command is used to send data messages via multicast diagrams.
- Note: the
--daemon
flag has no effect on theSAY
command.
The RECV
command is used to receive multicast diagrams by listening or "joining" a multicast group.
- if the
--use-std
flag is set the output is printed to the standard-output - this command is purely for testing or interfacing with external components and not intended as a first-class API
- Note: If the
--daemon
flag is used the process will loop after reporting each diagram until canceled, it has no effect on theRECV
command.
The HEAR
command is used to send data acknowledged messages via "HEAR" messages echoing select received multicast diagrams.
- while mostly a testing function it is possible to use
HEAR
as a proxy for other send/recv instances by using the--daemon
flag - note this will use the same port for sends and receives and can lead to data loss if less than two groups are used.
- If more than one group is used via the
--groups
flag then all but the bind group (via--group
) will be echoed to the bind group.
(assuming python3 is setup and installed)
# cd /MY-AWSOME-DEV-PATH
git clone https://github.com/reactive-firewall/multicast.git multicast
cd ./multicast
git checkout stable
# make clean ; make test ; make clean ;
make install ;
python3 -m multicast --help ;
If all went well multicast
is now installed and working 🎉
(assuming project is setup and installed and you want to listen on 0.0.0.0)
# cd /MY-AWSOME-DEV-PATH
python3 -m multicast HEAR --use-std --port 5353 --join-mcast-groups 224.0.0.1 --bind-group 224.0.0.1
Caveat: much more usefull if actually used in a loop like:
# cd /MY-AWSOME-DEV-PATH
while true ; do # unitl user ctl+c inturupts
python3 -m multicast HEAR --use-std --port 5353 --join-mcast-groups 224.0.0.1 --bind-group 224.0.0.1
done
(assuming project is setup and installed)
# cd /MY-AWSOME-DEV-PATH
python3 -m multicast SAY --mcast-group 224.1.1.2 --port 5353 --message "Hello World!"
Here is how it is tested right now
import mulicast as mulicast
from multiprocessing import Process as Process
# setup some stuff
_fixture_PORT_arg = int(59991)
_fixture_mcast_GRP_arg = """224.0.0.1""" # only use dotted notation for multicast group addresses
_fixture_host_BIND_arg
_fixture_HEAR_args = [
"""--port""", _fixture_PORT_arg,
"""--join-mcast-groups""", _fixture_mcast_GRP_arg,
"""--bind-group""", _fixture_mcast_GRP_arg"
]
# spwan a listening proc
def inputHandle()
buffer_string = str("""""")
buffer_string += multicast.recv.hearstep([_fixture_mcast_GRP_arg], _fixture_PORT_arg, _fixture_host_BIND_arg, _fixture_mcast_GRP_arg)
return buffer_string
def printLoopStub(func):
for i in range( 0, 5 ):
print( str( func() ) )
p = Process(
target=multicast.__main__.McastDispatch().doStep,
name="HEAR", args=("HEAR", _fixture_HEAR_args,)
)
p.start()
# ... probably will return with nothing outside a handler function in a loop
and elsewhere (like another function or even module) for the sender:
# assuming already did 'import mulicast as mulicast'
_fixture_SAY_args = [
"""--port""", _fixture_PORT_arg,
"""--mcast-group""", _fixture_mcast_GRP_arg,
"""--message""", """'test message'"""
]
try:
multicast.__main__.McastDispatch().doStep("SAY", _fixture_SAY_args)
# Hint: use a loop to repeat or different arguments to varry message.
except Exception:
p.join()
raise RuntimeException("multicast seems to have failed, blah, blah")
# clean up some stuff
p.join() # if not already handled don't forget to join the process and other overhead
didWork = (int(p.exitcode) <= int(0)) # if you use a loop and need to know the exit code
Caveat: the above examples assume the reader is knowledgeable about general IPC
theory and the standard python multiprocessing
module and its use.
From the documentation:
The Value of "224.0.0.1" is chosen as a default multicast group as per RFC-5771 on the rational that this group address will be treated as a local-net multicast (caveat: one should use link-local for ipv6)
From RFC-1112 §6.1
... If the upper-layer protocol chooses not to specify a time-to-live, it should default to 1 for all multicast IP datagrams, so that an explicit choice is required to multicast beyond a single network.
From the documentation:
A Value of 1 (one TTL) is chosen as per RFC1112 Sec 6.1 on the rational that an explicit value that could traverse byond the local connected network should be chosen by the caller rather than the default vaule. This is inline with the principle of none, one or many.
From the documentation:
Arbitrary port to use by default, though any dynamic and free port would work.
❗ Caution: it is best to specify the port in use at this time as the default has yet to be properly assigned ( see related #62 )
0
is the default and implies success, and means the process has essentially (or actually) returned nothing (or None
)
1
is used when a single result is returned (caveat: functions may return a single tuple
instead of None
to indicate exit code 1
by returning a boolean
success value, and result (which may also be encapsulated as an iteratable if needed) )
2
is used to indicate a value and reason are returned (caveat: functions may return a single tuple
with a single value and reason and the value can be a tuple
)
-1
is used to mean many of unspecified length and otherwise functions as 1
0
success
1
none-sucsess - and is often accompanied by warnings or errors
2 >
failure of specific reason
(extra exit code meanings)
Other codes (such as 126
) may or may not have meanings (such as skip) but are not handled within the scope of the Multicast Project at this time.
:warn: ALL MULTICAST is a surface of attack if the data is not sanitized. Common criteria applies here too, don't assume this library won't forward raw network data that reaches it. Notably the default group is all connected nodes (224.0.0.1).
Other common weakness left to the user to handle (NOT an exhaustive list):
- CWE-417 - in general all risks of a communication channel 🤔
- CWE-346 - multicast is by its very nature NOT one-to-one and can probably always be spoofed to some degree (hint: shared secrets (group keys) are probably a start 🤷)
- CWE-351 - don't assume only strings can be sent/received
#cd /MY-AWSOME-DEV-PATH/multicast
make clean ; # cleans up from any previous tests hopefully
make test ; # runs the tests
make clean ; # cleans up for next test
make clean ; # cleans up from any previous tests hopefully
make test-style ; # runs the tests
make clean ; # cleans up for next test
--(UNSTABLE) clean up Proof-of-concept code into a recognizable python module project.-- (WIP) might expand the documentation to be more user friendly to the non-network guru (WIP) might add tcp multicast ... who knows?