# -*- coding: utf-8 -*-
# Multicast Python Module
# ..................................
# Copyright (c) 2017-2025, Mr. Walls
# ..................................
# Licensed under MIT (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# ..........................................
# http://www.github.com/reactive-firewall/multicast/LICENSE.md
# ..........................................
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
import argparse
import unicodedata
import socket
import struct
import abc
import logging
# skipcq
__all__ = [
"""__package__""",
"""__module__""",
"""__name__""",
"""__version__""",
"""__prologue__""",
"""__doc__""",
"""exceptions""",
"""exceptions.CommandExecutionError""",
"""exceptions.get_exit_code_from_exception""",
"""exceptions.exit_on_exception""",
"""get_exit_code_from_exception""",
"""exit_on_exception""",
"""env""",
"""skt""",
"""skt.__package__""",
"""skt.__module__""",
"""skt.__name__""",
"""skt.__file__""",
"""skt.genSocket""",
"""skt.genSocket.__func__""",
"""genSocket""",
"""skt.endSocket""",
"""skt.endSocket.__func__""",
"""endSocket""",
"""EXIT_CODES""",
"""EXCEPTION_EXIT_CODES""",
"""_BLANK""",
"""_MCAST_DEFAULT_BUFFER_SIZE""", # added in version 2.0.6
"""_MCAST_DEFAULT_PORT""",
"""_MCAST_DEFAULT_GROUP""",
"""_MCAST_DEFAULT_TTL""",
"""mtool""",
"""recv""",
"""send""",
"""hear""",
"""recv.McastRECV""",
"""send.McastSAY""",
"""hear.McastHEAR""",
]
__package__ = "multicast" # skipcq: PYL-W0622
"""The package of this program.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast as _multicast
>>>
>>> _multicast.__package__ is not None
True
>>>
"""
__module__ = "multicast"
"""The module of this program.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast as _multicast
>>>
>>> _multicast.__module__ is not None
True
>>>
"""
__name__ = "multicast" # skipcq: PYL-W0622
"""The name of this program.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast as _multicast
>>>
>>> _multicast.__name__ is not None
True
>>>
"""
global __version__ # skipcq: PYL-W0604
__version__ = "2.0.7"
"""The version of this program.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast as _multicast
>>>
>>> _multicast.__version__ is not None
True
>>>
"""
__prologue__ = f"Python Multicast library version {__version__}."
"""The one-line description or summary of this program."""
__doc__ = __prologue__ + """
The `multicast` package simplifies multicast communication in Python applications.
A comprehensive package for multicast communication in Python applications.
Provides tools for sending, receiving, and listening to multicast messages over UDP.
The package includes command-line utilities and is designed to work with multiple Python
versions. It supports IPv4 multicast addresses and is compliant with dynamic/private port
ranges as per RFC-6335.
Key Features:
- Easy-to-use interfaces for multicast communication.
- Command-line tools for quick multicast operations.
- Support for UDP multicast via IPv4.
- Compliance with RFC-6335 for dynamic/private port ranges
Security Considerations:
- Ensure proper data sanitization and validation to prevent injection attacks.
- Be mindful of TTL settings to limit message propagation to the intended network segment.
Attributes:
__version__ (str): The version of this package.
_MCAST_DEFAULT_BUFFER_SIZE (int): Default buffer size for multicast communication (1316).
_MCAST_DEFAULT_PORT (int): Default port for multicast communication (59259).
_MCAST_DEFAULT_GROUP (str): Default multicast group address ('224.0.0.1').
_MCAST_DEFAULT_TTL (int): Default TTL for multicast packets (1).
Dynamic Imports:
The sub-modules within "multicast" are interdependent, requiring access to each other's
functionalities. These statements import sub-modules of "multicast" and assign them to
aliases that match their sub-module names, facilitating organized access to these
components.
While the multicast alias is the same as the multicast module name, this pattern should
serve to reinforce the Multicast module's namespace, especially when dealing with dynamic
imports and to maintain consistency across different parts of the code.
Minimal Acceptance Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>>
>>> multicast.__doc__ is not None
True
>>>
>>> multicast.__version__ is not None
True
>>>
Testcase 0: multicast.recv should have a doctests.
>>> import multicast.recv
>>>
>>> multicast.recv.__module__ is not None
True
>>>
Testcase 1: multicast.send should have a doctests.
>>> import multicast.send
>>>
>>> multicast.send.__module__ is not None
True
>>>
Testcase 2: multicast.__main__ should have a doctests.
>>> import multicast.__main__ as _main
>>>
>>> _main.__module__ is not None
True
>>> _main.__doc__ is not None
True
>>>
"""
global _MCAST_DEFAULT_BUFFER_SIZE # skipcq: PYL-W0604
_MCAST_DEFAULT_BUFFER_SIZE = 1316
"""
Arbitrary buffer size to use by default, though any value below 65507 should work.
Minimal Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>>
Testcase 0: Multicast should have a default buffer size.
A: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is initialized.
B: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is an int.
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE is not None
True
>>> type(multicast._MCAST_DEFAULT_BUFFER_SIZE) is type(1)
True
>>>
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE > int(1)
True
>>>
Testcase 1: Multicast should validate buffer size constraints.
A: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is initialized.
B: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is an int.
C: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is RFC-791 & RFC-768 compliant.
D: Test that the _MCAST_DEFAULT_BUFFER_SIZE attribute is a smaller than fragment thresholds
for typical ethernet MTUs by default.
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE is not None
True
>>> type(multicast._MCAST_DEFAULT_BUFFER_SIZE) is type(1)
True
>>>
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE >= int(56)
True
>>>
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE <= int(65527)
True
>>>
>>> multicast._MCAST_DEFAULT_BUFFER_SIZE <= int(1500)
True
>>>
"""
global _MCAST_DEFAULT_PORT # skipcq: PYL-W0604
_MCAST_DEFAULT_PORT = 59259
"""
Arbitrary port to use by default, though any dynamic and free port would work.
Minimal Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>>
Testcase 0: Multicast should have a default port.
A: Test that the _MCAST_DEFAULT_PORT attribute is initialized.
B: Test that the _MCAST_DEFAULT_PORT attribute is an int.
>>> multicast._MCAST_DEFAULT_PORT is not None
True
>>> type(multicast._MCAST_DEFAULT_PORT) is type(1)
True
>>>
>>> multicast._MCAST_DEFAULT_PORT > int(1024)
True
>>>
Testcase 1: Multicast should have a default port.
A: Test that the _MCAST_DEFAULT_PORT attribute is initialized.
B: Test that the _MCAST_DEFAULT_PORT attribute is an int.
C: Test that the _MCAST_DEFAULT_PORT attribute is RFC-6335 compliant.
>>> multicast._MCAST_DEFAULT_PORT is not None
True
>>> type(multicast._MCAST_DEFAULT_PORT) is type(1)
True
>>>
>>> multicast._MCAST_DEFAULT_PORT >= int(49152)
True
>>>
>>> multicast._MCAST_DEFAULT_PORT <= int(65535)
True
>>>
"""
global _MCAST_DEFAULT_GROUP # skipcq: PYL-W0604
_MCAST_DEFAULT_GROUP = "224.0.0.1"
"""Arbitrary group to use by default, though any mcst grp would work.
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)
Minimal Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>>
Testcase 0: Multicast should have a default port.
A: Test that the _MCAST_DEFAULT_GROUP attribute is initialized.
B: Test that the _MCAST_DEFAULT_GROUP attribute is an IP string.
>>> multicast._MCAST_DEFAULT_GROUP is not None
True
>>> type(multicast._MCAST_DEFAULT_GROUP) is type(str)
True
>>>
"""
global _MCAST_DEFAULT_TTL # skipcq: PYL-W0604
_MCAST_DEFAULT_TTL = int(1)
"""Arbitrary TTL time to live to use by default, though any small (1-126) TTL would work.
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 value. This is inline with the principle
of none, one or many.
Minimal Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>>
Testcase 0: Multicast should have a default TTL.
A: Test that the _MCAST_DEFAULT_TTL attribute is initialized.
B: Test that the _MCAST_DEFAULT_TTL attribute is an int.
c: Test that the _MCAST_DEFAULT_TTL attribute is default of 1.
>>> multicast._MCAST_DEFAULT_TTL is not None
True
>>> type(multicast._MCAST_DEFAULT_TTL) is type(1)
True
>>> (int(multicast._MCAST_DEFAULT_TTL) >= int(0))
True
>>> (int(multicast._MCAST_DEFAULT_TTL) <= int(2))
True
>>>
"""
global _BLANK # skipcq: PYL-W0604
_BLANK = str("""""")
"""Arbitrary blank string.
Minimal Testing:
First set up test fixtures by importing multicast.
>>> import multicast
>>> _BLANK = multicast._BLANK
Testcase 0: Multicast should have a default port.
A: Test that the _BLANK attribute is initialized.
B: Test that the _BLANK attribute is an empty string.
>>> _BLANK is not None
True
>>> type(_BLANK) is type(str)
True
>>>
>>> len(_BLANK) <= 0
True
>>>
"""
if logging.__name__ is not None: # pragma: no branch
logging.getLogger(__module__).addHandler(logging.NullHandler())
logging.getLogger(__module__).debug(
"Loading %s", # lazy formatting to avoid PYL-W1203
__module__,
)
if sys.__name__ is None:
raise ModuleNotFoundError(
"FAIL: we could not import sys. We're like in the matrix! ABORT."
) from None
if argparse.__name__ is None:
raise ModuleNotFoundError("FAIL: we could not import argparse. ABORT.") from None
if unicodedata.__name__ is None:
raise ModuleNotFoundError("FAIL: we could not import unicodedata. ABORT.") from None
if socket.__name__ is None:
raise ModuleNotFoundError("FAIL: we could not import socket. ABORT.") from None
else: # pragma: no branch
_tmp_mcast_value = int(_MCAST_DEFAULT_TTL)
logging.getLogger(__module__).debug(
"Setting default socket timeout to %d", # lazy formatting to avoid PYL-W1203
_tmp_mcast_value,
)
socket.setdefaulttimeout(_tmp_mcast_value)
del _tmp_mcast_value # skipcq - cleanup any bootstrap/setup leaks early
if struct.__name__ is None:
raise ModuleNotFoundError("FAIL: we could not import struct. ABORT.") from None
if abc.__name__ is None:
raise ModuleNotFoundError("FAIL: we could not import Abstract base class. ABORT.") from None
if 'multicast.exceptions' not in sys.modules:
# pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
from . import exceptions # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global exceptions # skipcq: PYL-W0604
exceptions = sys.modules["multicast.exceptions"]
EXIT_CODES = exceptions.EXIT_CODES
"""See multicast.exceptions.EXIT_CODES."""
EXCEPTION_EXIT_CODES = exceptions.EXCEPTION_EXIT_CODES
"""See multicast.exceptions.EXCEPTION_EXIT_CODES."""
CommandExecutionError = exceptions.CommandExecutionError
"""See multicast.exceptions.CommandExecutionError Class."""
get_exit_code_from_exception = exceptions.get_exit_code_from_exception
"""See multicast.exceptions.get_exit_code_from_exception function."""
exit_on_exception = exceptions.exit_on_exception
"""See multicast.exceptions.exit_on_exception function."""
if "multicast.env" not in sys.modules:
# pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
from . import env # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global env # skipcq: PYL-W0604
env = sys.modules["multicast.env"]
_config = env.load_config()
if _config is None:
raise ImportError("FAIL: we could not import environment. ABORT.") from None
else:
logging.getLogger(__module__).debug("Configuring overrides and defaults.")
_MCAST_DEFAULT_PORT = _config["port"]
_MCAST_DEFAULT_GROUP = _config["group"]
_MCAST_DEFAULT_TTL = _config["ttl"]
_MCAST_DEFAULT_BUFFER_SIZE = _config["buffer_size"]
global _MCAST_DEFAULT_BIND_IP # skipcq: PYL-W0604
_MCAST_DEFAULT_BIND_IP = _config["bind_addr"]
global _MCAST_DEFAULT_GROUPS # skipcq: PYL-W0604
_MCAST_DEFAULT_GROUPS = _config["groups"]
if __debug__: # pragma: no branch
logging.getLogger(__module__).info("Overrides and defaults are configured.")
logging.getLogger(__module__).debug("Defaults:")
for key, value in _config.items():
logging.getLogger(__module__).debug(
"\t%s=%s", # lazy formatting to avoid PYL-W1203
key, value,
)
del _config # skipcq - cleanup any bootstrap/setup leaks early
if "multicast.skt" not in sys.modules:
from . import skt as skt # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global skt # skipcq: PYL-W0604
skt = sys.modules["multicast.skt"]
genSocket = skt.genSocket
"""See multicast.skt.genSocket."""
endSocket = skt.endSocket
"""See multicast.skt.endSocket."""
if "multicast.recv" not in sys.modules:
from . import recv as recv # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global recv # skipcq: PYL-W0604
recv = sys.modules["multicast.recv"]
if "multicast.send" not in sys.modules:
from . import send as send # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global send # skipcq: PYL-W0604
send = sys.modules["multicast.send"]
if "multicast.hear" not in sys.modules:
from . import hear as hear # pylint: disable=cyclic-import - skipcq: PYL-R0401, PYL-C0414
else: # pragma: no branch
global hear # skipcq: PYL-W0604
hear = sys.modules["multicast.hear"]
try:
if "multicast.__main__" in sys.modules: # pragma: no cover
global __main__ # skipcq: PYL-W0604
__main__ = sys.modules["multicast.__main__"]
except Exception:
import multicast.__main__ as __main__ # pylint: disable=cyclic-import - skipcq: PYL-R0401
if __name__ in "__main__":
__EXIT_CODE = 2
if __main__.main is not None:
__EXIT_CODE = __main__.main(sys.argv[1:])
exit(__EXIT_CODE) # skipcq: PYL-R1722 - intentionally allow coverage and testing to catch exit