fix multicast flow tx device identification via igmp correlation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -203,6 +203,7 @@ func (t *Tendrils) queryDanteDeviceWithPort(ip net.IP, port int) *DanteDeviceInf
|
|||||||
|
|
||||||
if info.TxChannelCount > 0 {
|
if info.TxChannelCount > 0 {
|
||||||
t.queryDanteTxChannels(conn, ip, info.TxChannelCount)
|
t.queryDanteTxChannels(conn, ip, info.TxChannelCount)
|
||||||
|
t.nodes.UpdateDanteTxChannels(info.Name, ip, fmt.Sprintf("%d", info.TxChannelCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
return info
|
return info
|
||||||
@@ -334,6 +335,7 @@ func (t *Tendrils) queryDanteTxChannels(conn *net.UDPConn, ip net.IP, txCount in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (t *Tendrils) queryDanteSubscriptions(conn *net.UDPConn, ip net.IP, rxCount, txCount int) ([]DanteSubscription, bool) {
|
func (t *Tendrils) queryDanteSubscriptions(conn *net.UDPConn, ip net.IP, rxCount, txCount int) ([]DanteSubscription, bool) {
|
||||||
if rxCount == 0 {
|
if rxCount == 0 {
|
||||||
return nil, false
|
return nil, false
|
||||||
|
|||||||
180
dantemcast.md
180
dantemcast.md
@@ -1,180 +0,0 @@
|
|||||||
# Dante Multicast Flow Discovery Investigation
|
|
||||||
|
|
||||||
## Context for Continuation
|
|
||||||
**Goal**: Find the Dante protocol command that reveals TX channel -> multicast flow -> RX channel mapping.
|
|
||||||
|
|
||||||
**Current state**: Receiver's 0x3000 response shows multicast subscriptions with empty TX device name. We've probed 50+ commands on ARC port 4440 and found nothing. IGMP shows device->group membership but NOT per-channel routing.
|
|
||||||
|
|
||||||
**Key files modified**:
|
|
||||||
- `dante_control.go`: Added multicast record parsing, TX channel parsing
|
|
||||||
- `dante.go`: Added `listenDanteAudio()` stub
|
|
||||||
- `dantemcast.md`: This file
|
|
||||||
|
|
||||||
**Next to try**:
|
|
||||||
1. Probe TX device for "which of my channels are in multicast flows" command
|
|
||||||
2. Check if 0x2010/0x2012 need different arguments (flow ID?)
|
|
||||||
3. Look for SAP/SDP on port 9875
|
|
||||||
4. Capture Dante Controller traffic with Wireshark to see what it queries
|
|
||||||
|
|
||||||
**User instruction**: "stop giving up; we're not stopping until we fix this"
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
When Dante devices use multicast flows, the receiver's subscription response (0x3000) returns empty TX device names. We need to find the protocol command that reveals which TX device is transmitting to which multicast flow, so we can display proper flow routing like:
|
|
||||||
```
|
|
||||||
AJA-4K-T-022b9c[01] -> AJA-4K-R-023232[01]
|
|
||||||
```
|
|
||||||
Instead of:
|
|
||||||
```
|
|
||||||
?? (dante-av:xxxxx) -> AJA-4K-R-023232
|
|
||||||
```
|
|
||||||
|
|
||||||
## What We Know
|
|
||||||
|
|
||||||
### Receiver Side (0x3000 command)
|
|
||||||
- Unicast subscriptions (type 0x0006): Include TX device name and channel name
|
|
||||||
- Multicast subscriptions (type 0x000e): TX device offset points to null (offset 172 = `0000bb80...`)
|
|
||||||
- Multicast record structure (20 bytes):
|
|
||||||
```
|
|
||||||
Offset 0-1: RX channel number
|
|
||||||
Offset 2-3: Record type (0x000e = multicast)
|
|
||||||
Offset 4-5: TX device name offset (always 172, points to null)
|
|
||||||
Offset 6-7: 0x0000 (unknown, always zero)
|
|
||||||
Offset 8-9: 0x0000 (unknown, always zero)
|
|
||||||
Offset 10-11: TX channel name offset (points to "01", "02", etc.)
|
|
||||||
Offset 12-19: 0x00000000 0x00000000 (unknown, always zero)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transmitter Side
|
|
||||||
- 0x2000: Returns TX channel list with names and flags (0x0105 or 0x0107)
|
|
||||||
- 0x2002: Returns 272 bytes (8ch) or 168 bytes (4ch) of channel config (sample rate, format, etc.)
|
|
||||||
- 0x2010: Returns 76 bytes with 8 flow slots, but all zeros
|
|
||||||
- 0x2011: Returns 16 bytes, small status response
|
|
||||||
- 0x2012: Returns 76 bytes with 8 flow slots, but all zeros
|
|
||||||
|
|
||||||
## Commands Probed (No Multicast Flow Info Found)
|
|
||||||
|
|
||||||
### ARC Port (4440)
|
|
||||||
| Command | Response | Content |
|
|
||||||
|---------|----------|---------|
|
|
||||||
| 0x1000 | 44-48 bytes | Channel counts (RX/TX) |
|
|
||||||
| 0x1001 | varies | Set/reset device name |
|
|
||||||
| 0x1002 | 24-26 bytes | Device name string |
|
|
||||||
| 0x1003 | 130-142 bytes | Device info (name, platform) |
|
|
||||||
| 0x1100 | 16 bytes | Status info |
|
|
||||||
| 0x2000 | 72-116 bytes | TX channel names |
|
|
||||||
| 0x2002 | 168-272 bytes | TX channel config (sample rate, format) |
|
|
||||||
| 0x2010 | 76 bytes | 8 flow slots, all zeros |
|
|
||||||
| 0x2011 | 16 bytes | Status |
|
|
||||||
| 0x2012 | 76 bytes | 8 flow slots, all zeros |
|
|
||||||
| 0x2013 | varies | Set TX channel name |
|
|
||||||
| 0x2100-0x2500 | varies | Mostly zeros or no response |
|
|
||||||
| 0x3000 | 60-212 bytes | RX subscriptions (multicast has empty txDev) |
|
|
||||||
| 0x3001 | varies | Set RX channel name |
|
|
||||||
| 0x3002-0x300f | 10-12 bytes | Small status responses |
|
|
||||||
| 0x3010 | varies | Add subscription |
|
|
||||||
| 0x3014 | varies | Remove subscription |
|
|
||||||
| 0x4000-0x5000 | no response | Not implemented on these devices |
|
|
||||||
|
|
||||||
### CMC Port (8800) and DBC Port (4455)
|
|
||||||
- No responses from AJA or ULXD4Q devices on these ports
|
|
||||||
- These ports may only be available on certain device types
|
|
||||||
|
|
||||||
## External Resources Checked
|
|
||||||
|
|
||||||
### netaudio Python Library
|
|
||||||
- Located at `/opt/homebrew/lib/python3.14/site-packages/netaudio/dante/`
|
|
||||||
- Commands implemented: 0x1000, 0x1001, 0x1002, 0x1003, 0x1101, 0x2000, 0x2010, 0x2013, 0x3000, 0x3001, 0x3010, 0x3014
|
|
||||||
- No multicast flow configuration commands found
|
|
||||||
|
|
||||||
### network-audio-controller Wiki
|
|
||||||
- Confirms protocol is undocumented and reverse-engineered
|
|
||||||
- No multicast flow commands documented
|
|
||||||
|
|
||||||
### Inferno Project (Rust)
|
|
||||||
- Claims partial TX multicast support but source not accessible
|
|
||||||
- "Dante protocol is undocumented. Everything was reverse-engineered"
|
|
||||||
|
|
||||||
## Current Hypotheses
|
|
||||||
|
|
||||||
1. **Flow info stored only at creation time**: Dante Controller may store the flow mapping in its own database when flows are created, not query it from devices.
|
|
||||||
|
|
||||||
2. **Info in a different protocol**: Multicast flow config might be managed via:
|
|
||||||
- SAP/SDP announcements (standard AES67 mechanism)
|
|
||||||
- Dante Domain Manager protocol
|
|
||||||
- A different UDP port we haven't found
|
|
||||||
|
|
||||||
3. **Info derived from network traffic**: Dante Controller might correlate:
|
|
||||||
- TX devices advertising channels via mDNS `_netaudio-chan._udp`
|
|
||||||
- IGMP group membership from switches
|
|
||||||
- Actual multicast packet sources (sniffing)
|
|
||||||
|
|
||||||
## Next Steps to Try
|
|
||||||
|
|
||||||
### 1. Sniff Actual Multicast Audio Traffic
|
|
||||||
Added `listenDanteAudio()` function to capture packets on 239.255.0.0:4321. This would reveal:
|
|
||||||
- Source IP = TX device
|
|
||||||
- Destination IP = multicast group
|
|
||||||
|
|
||||||
But `ListenMulticastUDP` only joins one specific group. Need to either:
|
|
||||||
- Use raw sockets / pcap to see all multicast traffic
|
|
||||||
- Join multiple multicast groups dynamically based on IGMP data
|
|
||||||
|
|
||||||
### 2. Try Different Multicast Addresses
|
|
||||||
Dante uses various multicast ranges:
|
|
||||||
- 239.255.x.x - Dante AV flows
|
|
||||||
- 239.253.x.x - Dante audio (older?)
|
|
||||||
- 224.0.0.x - PTP, mDNS
|
|
||||||
|
|
||||||
### 3. Check SAP/SDP
|
|
||||||
AES67-compatible flows use SAP announcements on 239.255.255.255:9875 or 224.2.127.254:9875. Try listening for SDP session descriptions that might include flow routing.
|
|
||||||
|
|
||||||
### 4. Capture Dante Controller Traffic
|
|
||||||
Run Wireshark while Dante Controller is open to see what protocol it uses. Look for:
|
|
||||||
- Different UDP ports
|
|
||||||
- Different command IDs
|
|
||||||
- TCP connections
|
|
||||||
|
|
||||||
### 5. Query via TCP
|
|
||||||
Some Dante services use TCP. Try connecting to:
|
|
||||||
- Port 4440 via TCP
|
|
||||||
- Port 8700-8800 via TCP
|
|
||||||
|
|
||||||
### 6. Probe Command Arguments More Thoroughly
|
|
||||||
Try 0x2010/0x2012 with different argument patterns:
|
|
||||||
- Flow ID as argument
|
|
||||||
- Channel number as argument
|
|
||||||
- Page numbers beyond 1
|
|
||||||
|
|
||||||
## Test Devices
|
|
||||||
- AJA-4K-T-* (10.50.17.6, 10.50.17.24, 10.50.17.28, etc.) - 8ch TX, multicast transmitters
|
|
||||||
- AJA-4K-R-* (10.50.17.2, 10.50.17.29, 10.50.19.7, etc.) - 8ch RX, multicast receivers
|
|
||||||
- ULXD4Q-* (10.50.17.4, 10.50.17.5, 10.50.17.7, etc.) - 4ch TX, Shure wireless
|
|
||||||
- SQ-7 - Mixer, unicast TX (subscriptions show proper device name)
|
|
||||||
|
|
||||||
## Code Changes Made
|
|
||||||
- `dante_control.go`: Added detailed multicast record parsing, TX channel flag parsing
|
|
||||||
- `dante.go`: Added `listenDanteAudio()` stub for packet capture
|
|
||||||
|
|
||||||
## Key Insight
|
|
||||||
The unicast subscription "06@SQ-7" works correctly because the receiver stores the TX device name. Multicast subscriptions appear to be "anonymous" at the receiver level - the receiver knows which channel from which flow, but not which device is transmitting that flow.
|
|
||||||
|
|
||||||
## Why IGMP + mDNS Is Insufficient
|
|
||||||
Even if we correlate:
|
|
||||||
- IGMP: TX device 10.50.17.24 is in multicast group 239.253.127.115
|
|
||||||
- IGMP: RX device 10.50.17.29 is also in multicast group 239.253.127.115
|
|
||||||
- mDNS: TX device has channels "01" through "08"
|
|
||||||
|
|
||||||
We STILL don't know:
|
|
||||||
1. **Which TX channels** are being sent to that multicast group (could be all 8, could be just 2)
|
|
||||||
2. **Which TX channel maps to which RX channel** (TX ch1 -> RX ch1? or TX ch3 -> RX ch1?)
|
|
||||||
3. **Channel-level routing** within the flow
|
|
||||||
|
|
||||||
The RX subscription shows "subscribed to channel 01 from multicast" but not "subscribed to channel 01 from AJA-4K-T-022fde". The per-channel TX->RX mapping through multicast is the missing piece.
|
|
||||||
|
|
||||||
Dante Controller DOES show this info, so there must be a way to query it. Possibilities:
|
|
||||||
1. A command we haven't found that returns "TX channel X is in multicast flow Y"
|
|
||||||
2. The TX device stores which of its channels are in multicast flows
|
|
||||||
3. A central service (DDM) maintains the routing table
|
|
||||||
4. Dante Controller captures this info when flows are created and stores it locally
|
|
||||||
BIN
notes/dante.pcap
Normal file
BIN
notes/dante.pcap
Normal file
Binary file not shown.
116
notes/dantemcast.md
Normal file
116
notes/dantemcast.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Dante Multicast Flow Discovery
|
||||||
|
|
||||||
|
## Solution (2026-01-23)
|
||||||
|
|
||||||
|
**Problem**: Multicast flows showed `?? (dante-av:xxxxx)` instead of the TX device name.
|
||||||
|
|
||||||
|
**Root Cause**: `UpdateDanteTxChannels()` was never being called, so `GetDanteTxDeviceInGroup()` couldn't identify which member of a multicast group was the transmitter.
|
||||||
|
|
||||||
|
**Fix**: Added call to `t.nodes.UpdateDanteTxChannels(info.Name, ip, ...)` when a device with TX channels is discovered via ARC protocol.
|
||||||
|
|
||||||
|
**How it works now**:
|
||||||
|
1. TX device discovered via mDNS, probed via ARC (port 4440)
|
||||||
|
2. ARC query 0x1000 returns TX channel count > 0
|
||||||
|
3. `UpdateDanteTxChannels()` marks the device as a transmitter
|
||||||
|
4. IGMP snooping detects TX device joining multicast group
|
||||||
|
5. IGMP snooping detects RX device joining same multicast group
|
||||||
|
6. `GetDanteTxDeviceInGroup()` finds the TX device (has TxChannels) in the group
|
||||||
|
7. Flow displayed as `TX-DEVICE -> RX-DEVICE`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Protocol Notes
|
||||||
|
|
||||||
|
### ARC Protocol Packet Formats
|
||||||
|
|
||||||
|
There are two packet formats observed:
|
||||||
|
|
||||||
|
**Format 1 (0x27xx)** - Used by our code for basic queries:
|
||||||
|
```
|
||||||
|
27 xx LL LL 13 xx CC CC 00 00 [args...]
|
||||||
|
^seq ^0x1300|seq
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format 2 (0x2809)** - Used by Dante Controller:
|
||||||
|
```
|
||||||
|
28 09 LL LL SS SS CC CC 00 00 [args...]
|
||||||
|
^len ^seq ^cmd
|
||||||
|
```
|
||||||
|
|
||||||
|
Basic commands (0x1000, 0x1003, 0x3000) work with Format 1.
|
||||||
|
Advanced commands (0x2600, 0x3600) require Format 2 to avoid error status 0x0030.
|
||||||
|
|
||||||
|
### Commands Tested
|
||||||
|
|
||||||
|
| Command | Purpose | Status |
|
||||||
|
|---------|---------|--------|
|
||||||
|
| 0x1000 | Channel counts | Works |
|
||||||
|
| 0x1003 | Device name | Works |
|
||||||
|
| 0x2000 | TX channel names | Works |
|
||||||
|
| 0x2010 | TX flow slots | Returns zeros (no flow config) |
|
||||||
|
| 0x2600 | TX unicast flows | Returns zeros (multicast not included) |
|
||||||
|
| 0x3000 | RX subscriptions | Works for unicast; multicast has empty TX device |
|
||||||
|
| 0x3600 | RX unicast source | Returns zeros (multicast not included) |
|
||||||
|
|
||||||
|
### 0x2600/0x3600 Unicast Flow Commands
|
||||||
|
|
||||||
|
These commands work for **unicast** flows but return empty data for **multicast** flows.
|
||||||
|
|
||||||
|
**Query format** (34 bytes):
|
||||||
|
```
|
||||||
|
28 09 00 22 SS SS 26 00 00 00
|
||||||
|
00 00 00 00 00 00 00 00
|
||||||
|
00 01 00 01 00 01
|
||||||
|
00 00 00 00 00 00 00 00 00 00
|
||||||
|
```
|
||||||
|
|
||||||
|
**Successful response** (from pcap with active unicast flows):
|
||||||
|
- 148 bytes with status 0x0001
|
||||||
|
- Contains MAC address pattern `08 02 10 e2 ef fd XX XX`
|
||||||
|
- MAC can be correlated between TX (0x2600) and RX (0x3600) devices
|
||||||
|
|
||||||
|
**Empty response** (current network with multicast flows):
|
||||||
|
- 50 bytes with status 0x0001
|
||||||
|
- Data section shows `10 00` (0 records) instead of `10 01` (1 record)
|
||||||
|
- No MAC data available
|
||||||
|
|
||||||
|
### Multicast vs Unicast
|
||||||
|
|
||||||
|
**Unicast flows**: RX device stores TX device name in subscription record (0x3000 response).
|
||||||
|
|
||||||
|
**Multicast flows**: RX subscription has empty TX device name. Routing is handled at network layer via IGMP. The Dante device doesn't store "which device is transmitting to me" because multicast is anonymous.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Works
|
||||||
|
|
||||||
|
1. **IGMP correlation** - When TX devices are marked via `UpdateDanteTxChannels()`, we can identify the transmitter in a multicast group by checking which group member has TX channels.
|
||||||
|
|
||||||
|
2. **Unicast flow discovery** - 0x3000 returns full TX device name and channel for unicast subscriptions.
|
||||||
|
|
||||||
|
3. **Device discovery** - mDNS + ARC protocol reliably discovers all Dante devices.
|
||||||
|
|
||||||
|
## What Doesn't Work
|
||||||
|
|
||||||
|
1. **MAC-based correlation** - 0x2600/0x3600 return empty data for multicast flows. Only works when unicast flows are active (as seen in pcap from Dante Controller).
|
||||||
|
|
||||||
|
2. **Per-channel multicast routing** - We can identify TX device -> RX device at the device level via IGMP, but not individual channel mappings within a multicast flow.
|
||||||
|
|
||||||
|
3. **0x2010 flow slots** - Always returns zeros on tested devices (AJA, Shure, A&H).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pcap Analysis Notes
|
||||||
|
|
||||||
|
Captured Dante Controller polling the network. Key findings:
|
||||||
|
|
||||||
|
- Dante Controller uses 0x2809 packet format
|
||||||
|
- Queries many commands we hadn't tried: 0x2320, 0x2400, 0x3300, 0x3400
|
||||||
|
- 0x2600/0x3600 responses contained MAC addresses when unicast flows were active
|
||||||
|
- MAC pattern: `08 02` followed by Audinate OUI `10 e2 ef`
|
||||||
|
- Same MAC in TX's 0x2600 response matches RX's 0x3600 response = flow link
|
||||||
|
|
||||||
|
Commands observed (sorted by frequency):
|
||||||
|
- 0x1000, 0x1003, 0x1100, 0x1102 - Device info
|
||||||
|
- 0x2000, 0x2010, 0x2032, 0x2204, 0x2320, 0x2400, 0x2600 - TX queries
|
||||||
|
- 0x3000, 0x3300, 0x3400, 0x3600 - RX queries
|
||||||
Reference in New Issue
Block a user