A kernel-level multicast forwarding helper for IPTV on FreeBSD/OPNsense routers.
Some ISPs (like Bahnhof/Tele2 in Sweden) send IGMP packets with TOS 0xc0 (DSCP CS6). FreeBSD's raw sockets don't deliver these high-priority IGMP packets to userspace applications, making standard IGMP proxies unable to detect when TV boxes want to join/leave multicast groups.
This helper works around the issue by:
- Using
tcpdumpto capture IGMP traffic from the LAN interface - Sending IGMP membership reports to the ISP on the WAN interface
- Using FreeBSD's kernel Multicast Routing Table (MRT) for efficient packet forwarding
- FreeBSD 14+ or OPNsense 24+
- Python 3.9+
- Root privileges (for MRT and raw sockets)
tcpdumpinstalled
# Install dependencies
pkg install python3 py311-scapy py311-yaml
# Or via pip
pip install -r requirements.txt
# Copy to system location
cp iptv_helper.py /usr/local/bin/
chmod +x /usr/local/bin/iptv_helper.py
# Copy and edit configuration
cp config.yaml.example /usr/local/etc/iptv_helper.yaml
# Edit the configuration file for your setupCreate a configuration file (YAML):
# Network interfaces
interfaces:
wan: "igb0" # WAN interface (connects to ISP)
lan: "igb1" # LAN interface (connects to TV box)
# Client configuration (TV boxes)
clients:
- "192.168.1.100"
- "192.168.1.101" # Multiple clients supported
# Multicast settings
multicast:
prefix: "234." # Only forward groups matching this prefix
# Timing settings (in seconds)
timing:
igmp_refresh: 30 # How often to refresh IGMP with ISP
heartbeat: 300 # Status logging interval
# Logging settings
logging:
level: "INFO" # DEBUG, INFO, WARN, ERROR
file: "/var/log/iptv_helper.log" # Optionaliptv_helper.py -c /usr/local/etc/iptv_helper.yamliptv_helper.py --wan igb0 --lan igb1 --client 192.168.1.100Usage: iptv_helper.py [OPTIONS]
Options:
-c, --config FILE Path to YAML configuration file
--wan IF WAN interface name
--lan IF LAN interface name
--client IP Client IP address (can be repeated)
--prefix PREFIX Multicast prefix filter (default: 234.)
--refresh SEC IGMP refresh interval (default: 30)
--heartbeat SEC Heartbeat interval (default: 300)
--log-level LEVEL Log level: DEBUG, INFO, WARN, ERROR
--log-file FILE Log to file instead of stdout
--version Show version
/usr/sbin/daemon -f /usr/local/bin/python3.11 /usr/local/bin/iptv_helper.py -c /usr/local/etc/iptv_helper.yamlAdd to root's crontab:
*/5 * * * * pgrep -f 'python.*iptv_helper' || /usr/sbin/daemon -f /usr/local/bin/python3.11 /usr/local/bin/iptv_helper.py -c /usr/local/etc/iptv_helper.yamlAlways use SIGTERM, never SIGKILL:
# Correct - allows cleanup
pkill -f 'python.*iptv_helper'
# WRONG - orphans MRT socket, requires reboot
pkill -9 -f 'python.*iptv_helper'If you kill with -9, the kernel MRT socket becomes orphaned and the only fix is a router reboot.
This helper is incompatible with igmpproxy. Only one process can hold the MRT socket. To disable:
- OPNsense Web UI: Services -> IGMP Proxy -> Disable
- Command line:
sysrc igmpproxy_enable="NO"
Both steps are required for a complete disable.
-
IGMP Capture: Uses tcpdump to monitor IGMP traffic from configured clients on the LAN interface
-
Group Tracking: When a client sends an IGMP Report (join), the helper:
- Records the group as active
- Sends an IGMP Report to the ISP on the WAN interface
- Waits for the kernel to request routing via upcall
-
Kernel MRT: When multicast packets arrive for an active group:
- Kernel sends NOCACHE upcall
- Helper adds MFC (Multicast Forwarding Cache) entry
- Kernel forwards packets directly (no userspace copying)
-
IGMP Refresh: Periodically resends IGMP Reports to the ISP to maintain stream
-
Leave Handling: When a client sends IGMP Leave:
- Removes MFC entries
- Sends IGMP Leave to ISP
- Stops tracking the group
pgrep -lf iptv_helpertail -f /var/log/iptv_helper.log
# or if logging to stdout:
tail -f /tmp/iptv_helper.lognetstat -gn # Show multicast groups
netstat -rn # Show routesLook for "Out-Vifs:Ttls" showing "1:1" indicating LAN forwarding is active.
"Address already in use": Another process holds the MRT socket. Check for:
- igmpproxy running
- Previous helper instance not cleaned up (reboot required)
No video: Check:
- Helper is running
- Correct interfaces configured
- ISP is sending multicast (tcpdump on WAN)
- Client is sending IGMP (tcpdump on LAN)
MIT License