mcast-tools 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcast_tools-0.1.0/LICENSE +21 -0
- mcast_tools-0.1.0/PKG-INFO +192 -0
- mcast_tools-0.1.0/README.md +168 -0
- mcast_tools-0.1.0/pyproject.toml +41 -0
- mcast_tools-0.1.0/setup.cfg +4 -0
- mcast_tools-0.1.0/src/mcast_tools/__init__.py +3 -0
- mcast_tools-0.1.0/src/mcast_tools/common.py +513 -0
- mcast_tools-0.1.0/src/mcast_tools/receiver.py +891 -0
- mcast_tools-0.1.0/src/mcast_tools/sender.py +624 -0
- mcast_tools-0.1.0/src/mcast_tools/status.py +130 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/PKG-INFO +192 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/SOURCES.txt +14 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/dependency_links.txt +1 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/entry_points.txt +4 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/requires.txt +1 -0
- mcast_tools-0.1.0/src/mcast_tools.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mitch Vaughan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcast-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Friendly multicast send/receive tools for lab environments
|
|
5
|
+
Author-email: Mitch Vaughan <mitch.vaughan@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/mitchv85/mcast-tools
|
|
8
|
+
Project-URL: Issues, https://github.com/mitchv85/mcast-tools/issues
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: rich>=13.0
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
|
|
25
|
+
# mcast-tools
|
|
26
|
+
|
|
27
|
+
Friendly multicast send/receive tools for lab environments. Hides the
|
|
28
|
+
complexity of `iperf` / raw socket plumbing behind three simple commands,
|
|
29
|
+
and adds capabilities iperf can't provide — source-specific (SSM) joins,
|
|
30
|
+
IGMP version control, hop-count and DSCP-remark visibility via embedded
|
|
31
|
+
metadata, and live dashboards that update in real time.
|
|
32
|
+
|
|
33
|
+
## Why not just iperf?
|
|
34
|
+
|
|
35
|
+
iperf is great, but for a teaching lab a few limitations matter:
|
|
36
|
+
|
|
37
|
+
- **No SSM joins.** `iperf -s -B <group>` only does any-source `(*,G)` joins.
|
|
38
|
+
No flag fixes this — it's a socket-level capability iperf doesn't expose.
|
|
39
|
+
- **No IGMP version control.** That's a kernel sysctl, not an iperf knob.
|
|
40
|
+
- **No visibility into TTL or DSCP.** iperf doesn't tell you which interface
|
|
41
|
+
the join went out on, what IGMP version was actually sent, what TTL or
|
|
42
|
+
DSCP arrived, or how many hops away the sender is.
|
|
43
|
+
- **Output is built for log scraping, not live UX.**
|
|
44
|
+
|
|
45
|
+
`mcast-tools` keeps iperf's "two commands, easy to remember" feel while
|
|
46
|
+
adding the things you actually want in a lab.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install mcast-tools
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Or from source:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone https://github.com/mitchv85/mcast-tools.git
|
|
58
|
+
cd mcast-tools
|
|
59
|
+
pip install .
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Provides three commands: `mcast-send`, `mcast-receive`, `mcast-status`.
|
|
63
|
+
|
|
64
|
+
> **Root is recommended** for `mcast-receive` so it can force the IGMP version
|
|
65
|
+
> via `/proc/sys/net/ipv4/conf/<iface>/force_igmp_version`. It will still
|
|
66
|
+
> run without root, but the kernel will use whatever IGMP version it's
|
|
67
|
+
> currently configured for.
|
|
68
|
+
|
|
69
|
+
## Quick start
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Sender — defaults to 10 minutes, TTL 100, 25pps, DSCP=BE
|
|
73
|
+
mcast-send 239.0.10.101
|
|
74
|
+
|
|
75
|
+
# Receiver — defaults to ASM (*,G) join, IGMPv3
|
|
76
|
+
mcast-receive 239.0.10.101
|
|
77
|
+
|
|
78
|
+
# What groups is this host joined to?
|
|
79
|
+
mcast-status
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## `mcast-send`
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
mcast-send <group> [duration] [ttl]
|
|
86
|
+
mcast-send <group> --rate 1Mbps --dscp ef --interface eth1
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
| Option | Default | Purpose |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `group` | (required) | IPv4 multicast group, e.g. `239.0.10.101` |
|
|
92
|
+
| `duration` | `600` | Seconds to send. `0` = forever |
|
|
93
|
+
| `ttl` | `100` | IP TTL on outgoing packets |
|
|
94
|
+
| `--rate` | `25pps` | `Npps`, `Nkbps`, `NMbps`, etc. |
|
|
95
|
+
| `--size` | `200` | Datagram size in bytes (header + payload) |
|
|
96
|
+
| `--dscp` | `be` | `0`-`63` or name (`ef`, `af41`, `cs5`, ...) |
|
|
97
|
+
| `--interface` | (routing default) | Egress interface override |
|
|
98
|
+
| `--no-loopback` | off | Disable `IP_MULTICAST_LOOP` |
|
|
99
|
+
| `--quiet` / `--json` | off | Suppress dashboard / emit JSON summary on exit |
|
|
100
|
+
|
|
101
|
+
Each packet carries a 32-byte custom header with magic `MCAS`, a sequence
|
|
102
|
+
number, a monotonic nanosecond timestamp, a random per-invocation sender ID,
|
|
103
|
+
and the original TTL/DSCP the sender configured. `mcast-receive` uses these
|
|
104
|
+
fields to compute loss, duplicates, reordering, hop count, and DSCP remarks.
|
|
105
|
+
|
|
106
|
+
## `mcast-receive`
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
mcast-receive <group> # ASM (*,G)
|
|
110
|
+
mcast-receive <group> <source> # SSM (S,G), IGMPv3
|
|
111
|
+
mcast-receive <group> --igmp-version 2 --interface eth1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Option | Default | Purpose |
|
|
115
|
+
|---|---|---|
|
|
116
|
+
| `group` | (required) | IPv4 multicast group |
|
|
117
|
+
| `source` | (none) | Source IP for SSM `(S,G)` join |
|
|
118
|
+
| `igmp_version` | `3` | `1`, `2`, or `3`. **Must be 3 if a source is specified.** |
|
|
119
|
+
| `--interface` | (routing default) | Interface to join on |
|
|
120
|
+
| `--stall-timeout` | `3.0` | Seconds without a packet before flagging STALLED |
|
|
121
|
+
| `--duration` | `0` | Auto-stop after N seconds (`0` = run forever) |
|
|
122
|
+
| `--quiet` / `--json` | off | Suppress dashboard / emit JSON summary on exit |
|
|
123
|
+
|
|
124
|
+
The receiver continuously displays:
|
|
125
|
+
|
|
126
|
+
- **Status:** `WAITING` → `RECEIVING` → `STALLED` based on packet arrival
|
|
127
|
+
- **Received TTL** with hop-count interpretation (`recv 96, sender set 100 — 4 hops away`)
|
|
128
|
+
- **Received DSCP** with remark detection (`BE, sender set EF — REMARKED`)
|
|
129
|
+
- **Loss / Duplicates / Reordered** counters
|
|
130
|
+
- **Sender restart detection** (new `sender_id` → restart counter increments)
|
|
131
|
+
- **Gap log** — recent stall events with duration
|
|
132
|
+
|
|
133
|
+
### Why IGMPv3 is mandatory for SSM
|
|
134
|
+
|
|
135
|
+
Only IGMPv3 Membership Reports carry the per-source state needed to
|
|
136
|
+
communicate `(S,G)` filtering up to the querier. v1/v2 reports only express
|
|
137
|
+
`(*,G)`. `mcast-receive` enforces this with a clear error message rather
|
|
138
|
+
than silently doing the wrong thing.
|
|
139
|
+
|
|
140
|
+
## `mcast-status`
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
mcast-status # current memberships, excluding kernel defaults
|
|
144
|
+
mcast-status --all # include 224.0.0.1 etc.
|
|
145
|
+
mcast-status -i eth0 # one interface only
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Reads `/proc/net/igmp` and pretty-prints which interfaces are joined to
|
|
149
|
+
which groups, the operational IGMP version, and the current
|
|
150
|
+
`force_igmp_version` value if any.
|
|
151
|
+
|
|
152
|
+
## End-to-end example
|
|
153
|
+
|
|
154
|
+
In one terminal:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
mcast-receive 239.0.10.101
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
In another:
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
mcast-send 239.0.10.101 --rate 50pps --ttl 64 --dscp ef
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The receiver will show 50 pps, EF DSCP preserved, and a hop count derived
|
|
167
|
+
from the difference between the sender-stamped original TTL (64) and the
|
|
168
|
+
TTL observed on arrival. If anything in the path remarks DSCP or
|
|
169
|
+
decrements TTL more than expected, you'll see it immediately.
|
|
170
|
+
|
|
171
|
+
## Behind-the-scenes details (worth knowing for a lab)
|
|
172
|
+
|
|
173
|
+
- **Destination UDP port** is `19779` (`0x4D43` = `"MC"`). Choose this if you
|
|
174
|
+
need to filter in tcpdump: `tcpdump 'udp port 19779 and host 239.0.10.101'`.
|
|
175
|
+
- **Default datagram size** is 200 bytes (32-byte header + 168-byte payload).
|
|
176
|
+
Configure with `--size`. Minimum is 32 bytes (header only).
|
|
177
|
+
- **Packet padding** is `0xA5` repeated — easy to eyeball in a capture.
|
|
178
|
+
- **Sender ID randomization** means restarting a sender looks like a brand-new
|
|
179
|
+
sender on the receiver, which is exactly the right semantic — we don't
|
|
180
|
+
want the receiver to interpret a restart as a giant burst of packet loss.
|
|
181
|
+
- **Per-sender sequence tracking** means a receiver listening to multiple
|
|
182
|
+
senders (or one sender across restarts) doesn't conflate their sequence
|
|
183
|
+
spaces.
|
|
184
|
+
- **Stall detection** is interval-based, not packet-rate-based — we don't
|
|
185
|
+
need a control channel to know the sender's target rate.
|
|
186
|
+
- **Foreign packet counter** counts UDP datagrams on the group that don't
|
|
187
|
+
start with the `MCAS` magic header. Useful for detecting when something
|
|
188
|
+
else (real video, iperf, etc.) is sharing the group.
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# mcast-tools
|
|
2
|
+
|
|
3
|
+
Friendly multicast send/receive tools for lab environments. Hides the
|
|
4
|
+
complexity of `iperf` / raw socket plumbing behind three simple commands,
|
|
5
|
+
and adds capabilities iperf can't provide — source-specific (SSM) joins,
|
|
6
|
+
IGMP version control, hop-count and DSCP-remark visibility via embedded
|
|
7
|
+
metadata, and live dashboards that update in real time.
|
|
8
|
+
|
|
9
|
+
## Why not just iperf?
|
|
10
|
+
|
|
11
|
+
iperf is great, but for a teaching lab a few limitations matter:
|
|
12
|
+
|
|
13
|
+
- **No SSM joins.** `iperf -s -B <group>` only does any-source `(*,G)` joins.
|
|
14
|
+
No flag fixes this — it's a socket-level capability iperf doesn't expose.
|
|
15
|
+
- **No IGMP version control.** That's a kernel sysctl, not an iperf knob.
|
|
16
|
+
- **No visibility into TTL or DSCP.** iperf doesn't tell you which interface
|
|
17
|
+
the join went out on, what IGMP version was actually sent, what TTL or
|
|
18
|
+
DSCP arrived, or how many hops away the sender is.
|
|
19
|
+
- **Output is built for log scraping, not live UX.**
|
|
20
|
+
|
|
21
|
+
`mcast-tools` keeps iperf's "two commands, easy to remember" feel while
|
|
22
|
+
adding the things you actually want in a lab.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install mcast-tools
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or from source:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/mitchv85/mcast-tools.git
|
|
34
|
+
cd mcast-tools
|
|
35
|
+
pip install .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Provides three commands: `mcast-send`, `mcast-receive`, `mcast-status`.
|
|
39
|
+
|
|
40
|
+
> **Root is recommended** for `mcast-receive` so it can force the IGMP version
|
|
41
|
+
> via `/proc/sys/net/ipv4/conf/<iface>/force_igmp_version`. It will still
|
|
42
|
+
> run without root, but the kernel will use whatever IGMP version it's
|
|
43
|
+
> currently configured for.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Sender — defaults to 10 minutes, TTL 100, 25pps, DSCP=BE
|
|
49
|
+
mcast-send 239.0.10.101
|
|
50
|
+
|
|
51
|
+
# Receiver — defaults to ASM (*,G) join, IGMPv3
|
|
52
|
+
mcast-receive 239.0.10.101
|
|
53
|
+
|
|
54
|
+
# What groups is this host joined to?
|
|
55
|
+
mcast-status
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## `mcast-send`
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
mcast-send <group> [duration] [ttl]
|
|
62
|
+
mcast-send <group> --rate 1Mbps --dscp ef --interface eth1
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Option | Default | Purpose |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `group` | (required) | IPv4 multicast group, e.g. `239.0.10.101` |
|
|
68
|
+
| `duration` | `600` | Seconds to send. `0` = forever |
|
|
69
|
+
| `ttl` | `100` | IP TTL on outgoing packets |
|
|
70
|
+
| `--rate` | `25pps` | `Npps`, `Nkbps`, `NMbps`, etc. |
|
|
71
|
+
| `--size` | `200` | Datagram size in bytes (header + payload) |
|
|
72
|
+
| `--dscp` | `be` | `0`-`63` or name (`ef`, `af41`, `cs5`, ...) |
|
|
73
|
+
| `--interface` | (routing default) | Egress interface override |
|
|
74
|
+
| `--no-loopback` | off | Disable `IP_MULTICAST_LOOP` |
|
|
75
|
+
| `--quiet` / `--json` | off | Suppress dashboard / emit JSON summary on exit |
|
|
76
|
+
|
|
77
|
+
Each packet carries a 32-byte custom header with magic `MCAS`, a sequence
|
|
78
|
+
number, a monotonic nanosecond timestamp, a random per-invocation sender ID,
|
|
79
|
+
and the original TTL/DSCP the sender configured. `mcast-receive` uses these
|
|
80
|
+
fields to compute loss, duplicates, reordering, hop count, and DSCP remarks.
|
|
81
|
+
|
|
82
|
+
## `mcast-receive`
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
mcast-receive <group> # ASM (*,G)
|
|
86
|
+
mcast-receive <group> <source> # SSM (S,G), IGMPv3
|
|
87
|
+
mcast-receive <group> --igmp-version 2 --interface eth1
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
| Option | Default | Purpose |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `group` | (required) | IPv4 multicast group |
|
|
93
|
+
| `source` | (none) | Source IP for SSM `(S,G)` join |
|
|
94
|
+
| `igmp_version` | `3` | `1`, `2`, or `3`. **Must be 3 if a source is specified.** |
|
|
95
|
+
| `--interface` | (routing default) | Interface to join on |
|
|
96
|
+
| `--stall-timeout` | `3.0` | Seconds without a packet before flagging STALLED |
|
|
97
|
+
| `--duration` | `0` | Auto-stop after N seconds (`0` = run forever) |
|
|
98
|
+
| `--quiet` / `--json` | off | Suppress dashboard / emit JSON summary on exit |
|
|
99
|
+
|
|
100
|
+
The receiver continuously displays:
|
|
101
|
+
|
|
102
|
+
- **Status:** `WAITING` → `RECEIVING` → `STALLED` based on packet arrival
|
|
103
|
+
- **Received TTL** with hop-count interpretation (`recv 96, sender set 100 — 4 hops away`)
|
|
104
|
+
- **Received DSCP** with remark detection (`BE, sender set EF — REMARKED`)
|
|
105
|
+
- **Loss / Duplicates / Reordered** counters
|
|
106
|
+
- **Sender restart detection** (new `sender_id` → restart counter increments)
|
|
107
|
+
- **Gap log** — recent stall events with duration
|
|
108
|
+
|
|
109
|
+
### Why IGMPv3 is mandatory for SSM
|
|
110
|
+
|
|
111
|
+
Only IGMPv3 Membership Reports carry the per-source state needed to
|
|
112
|
+
communicate `(S,G)` filtering up to the querier. v1/v2 reports only express
|
|
113
|
+
`(*,G)`. `mcast-receive` enforces this with a clear error message rather
|
|
114
|
+
than silently doing the wrong thing.
|
|
115
|
+
|
|
116
|
+
## `mcast-status`
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
mcast-status # current memberships, excluding kernel defaults
|
|
120
|
+
mcast-status --all # include 224.0.0.1 etc.
|
|
121
|
+
mcast-status -i eth0 # one interface only
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Reads `/proc/net/igmp` and pretty-prints which interfaces are joined to
|
|
125
|
+
which groups, the operational IGMP version, and the current
|
|
126
|
+
`force_igmp_version` value if any.
|
|
127
|
+
|
|
128
|
+
## End-to-end example
|
|
129
|
+
|
|
130
|
+
In one terminal:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
mcast-receive 239.0.10.101
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
In another:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mcast-send 239.0.10.101 --rate 50pps --ttl 64 --dscp ef
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The receiver will show 50 pps, EF DSCP preserved, and a hop count derived
|
|
143
|
+
from the difference between the sender-stamped original TTL (64) and the
|
|
144
|
+
TTL observed on arrival. If anything in the path remarks DSCP or
|
|
145
|
+
decrements TTL more than expected, you'll see it immediately.
|
|
146
|
+
|
|
147
|
+
## Behind-the-scenes details (worth knowing for a lab)
|
|
148
|
+
|
|
149
|
+
- **Destination UDP port** is `19779` (`0x4D43` = `"MC"`). Choose this if you
|
|
150
|
+
need to filter in tcpdump: `tcpdump 'udp port 19779 and host 239.0.10.101'`.
|
|
151
|
+
- **Default datagram size** is 200 bytes (32-byte header + 168-byte payload).
|
|
152
|
+
Configure with `--size`. Minimum is 32 bytes (header only).
|
|
153
|
+
- **Packet padding** is `0xA5` repeated — easy to eyeball in a capture.
|
|
154
|
+
- **Sender ID randomization** means restarting a sender looks like a brand-new
|
|
155
|
+
sender on the receiver, which is exactly the right semantic — we don't
|
|
156
|
+
want the receiver to interpret a restart as a giant burst of packet loss.
|
|
157
|
+
- **Per-sender sequence tracking** means a receiver listening to multiple
|
|
158
|
+
senders (or one sender across restarts) doesn't conflate their sequence
|
|
159
|
+
spaces.
|
|
160
|
+
- **Stall detection** is interval-based, not packet-rate-based — we don't
|
|
161
|
+
need a control channel to know the sender's target rate.
|
|
162
|
+
- **Foreign packet counter** counts UDP datagrams on the group that don't
|
|
163
|
+
start with the `MCAS` magic header. Useful for detecting when something
|
|
164
|
+
else (real video, iperf, etc.) is sharing the group.
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT. See `LICENSE`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcast-tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Friendly multicast send/receive tools for lab environments"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Mitch Vaughan", email = "mitch.vaughan@gmail.com" }
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"rich>=13.0",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 4 - Beta",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Intended Audience :: System Administrators",
|
|
22
|
+
"License :: OSI Approved :: MIT License",
|
|
23
|
+
"Operating System :: POSIX :: Linux",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Topic :: System :: Networking",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
mcast-send = "mcast_tools.sender:main"
|
|
33
|
+
mcast-receive = "mcast_tools.receiver:main"
|
|
34
|
+
mcast-status = "mcast_tools.status:main"
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/mitchv85/mcast-tools"
|
|
38
|
+
Issues = "https://github.com/mitchv85/mcast-tools/issues"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|