Spaces:
Paused
Paused
MacBook pro
commited on
Commit
·
d6ddd2d
1
Parent(s):
6472b34
feat: enhance /webrtc/ice_gather_test and add /webrtc/net_info for zero-candidate diagnosis
Browse files- webrtc_server.py +58 -5
webrtc_server.py
CHANGED
|
@@ -1532,23 +1532,37 @@ async def ice_gather_test():
|
|
| 1532 |
try:
|
| 1533 |
cfg = _ice_configuration()
|
| 1534 |
pc = RTCPeerConnection(configuration=cfg)
|
| 1535 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1536 |
try:
|
| 1537 |
pc.createDataChannel("probe")
|
| 1538 |
except Exception:
|
| 1539 |
pass
|
| 1540 |
offer = await pc.createOffer()
|
| 1541 |
await pc.setLocalDescription(offer)
|
| 1542 |
-
# Wait for iceGatheringState complete or timeout
|
| 1543 |
t0 = time.time()
|
| 1544 |
-
|
| 1545 |
-
|
|
|
|
|
|
|
| 1546 |
stats = await pc.getStats()
|
| 1547 |
summary = {
|
| 1548 |
'local_candidates': 0,
|
| 1549 |
'local_types': {},
|
| 1550 |
'elapsed_ms': int((time.time() - t0) * 1000),
|
| 1551 |
'gathering_state': pc.iceGatheringState,
|
|
|
|
|
|
|
|
|
|
| 1552 |
}
|
| 1553 |
candidate_details = []
|
| 1554 |
for sid, rep in stats.items():
|
|
@@ -1557,22 +1571,61 @@ async def ice_gather_test():
|
|
| 1557 |
summary['local_candidates'] += 1
|
| 1558 |
ctype = getattr(rep, 'candidateType', 'unknown')
|
| 1559 |
summary['local_types'][ctype] = summary['local_types'].get(ctype, 0) + 1
|
| 1560 |
-
if len(candidate_details) <
|
| 1561 |
candidate_details.append({
|
| 1562 |
'type': ctype,
|
| 1563 |
'protocol': getattr(rep, 'protocol', None),
|
| 1564 |
'address': getattr(rep, 'address', None),
|
| 1565 |
'port': getattr(rep, 'port', None),
|
|
|
|
| 1566 |
})
|
| 1567 |
try:
|
| 1568 |
await pc.close()
|
| 1569 |
except Exception:
|
| 1570 |
pass
|
| 1571 |
summary['candidates'] = candidate_details
|
|
|
|
|
|
|
| 1572 |
return summary
|
| 1573 |
except Exception as e:
|
| 1574 |
return {"error": str(e)}
|
| 1575 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1576 |
|
| 1577 |
@router.get("/token")
|
| 1578 |
async def mint_token():
|
|
|
|
| 1532 |
try:
|
| 1533 |
cfg = _ice_configuration()
|
| 1534 |
pc = RTCPeerConnection(configuration=cfg)
|
| 1535 |
+
# Force creation of audio+video transceivers; some stacks gather more
|
| 1536 |
+
# aggressively when media components exist vs. data-only.
|
| 1537 |
+
try:
|
| 1538 |
+
pc.addTransceiver('audio', direction='recvonly')
|
| 1539 |
+
except Exception:
|
| 1540 |
+
pass
|
| 1541 |
+
try:
|
| 1542 |
+
pc.addTransceiver('video', direction='recvonly')
|
| 1543 |
+
except Exception:
|
| 1544 |
+
pass
|
| 1545 |
+
# Create data channel to exercise SCTP as well
|
| 1546 |
try:
|
| 1547 |
pc.createDataChannel("probe")
|
| 1548 |
except Exception:
|
| 1549 |
pass
|
| 1550 |
offer = await pc.createOffer()
|
| 1551 |
await pc.setLocalDescription(offer)
|
|
|
|
| 1552 |
t0 = time.time()
|
| 1553 |
+
# Wait at least one loop tick even if state flips to complete instantly
|
| 1554 |
+
await asyncio.sleep(0.3)
|
| 1555 |
+
while pc.iceGatheringState != 'complete' and (time.time() - t0) < 8.0:
|
| 1556 |
+
await asyncio.sleep(0.25)
|
| 1557 |
stats = await pc.getStats()
|
| 1558 |
summary = {
|
| 1559 |
'local_candidates': 0,
|
| 1560 |
'local_types': {},
|
| 1561 |
'elapsed_ms': int((time.time() - t0) * 1000),
|
| 1562 |
'gathering_state': pc.iceGatheringState,
|
| 1563 |
+
'ice_servers_supplied': [
|
| 1564 |
+
s.urls if isinstance(s.urls, list) else s.urls for s in cfg.iceServers
|
| 1565 |
+
],
|
| 1566 |
}
|
| 1567 |
candidate_details = []
|
| 1568 |
for sid, rep in stats.items():
|
|
|
|
| 1571 |
summary['local_candidates'] += 1
|
| 1572 |
ctype = getattr(rep, 'candidateType', 'unknown')
|
| 1573 |
summary['local_types'][ctype] = summary['local_types'].get(ctype, 0) + 1
|
| 1574 |
+
if len(candidate_details) < 40:
|
| 1575 |
candidate_details.append({
|
| 1576 |
'type': ctype,
|
| 1577 |
'protocol': getattr(rep, 'protocol', None),
|
| 1578 |
'address': getattr(rep, 'address', None),
|
| 1579 |
'port': getattr(rep, 'port', None),
|
| 1580 |
+
'foundation': getattr(rep, 'foundation', None),
|
| 1581 |
})
|
| 1582 |
try:
|
| 1583 |
await pc.close()
|
| 1584 |
except Exception:
|
| 1585 |
pass
|
| 1586 |
summary['candidates'] = candidate_details
|
| 1587 |
+
if summary['local_candidates'] == 0:
|
| 1588 |
+
summary['note'] = 'No local candidates gathered. Possible TURN auth or interface enumeration failure.'
|
| 1589 |
return summary
|
| 1590 |
except Exception as e:
|
| 1591 |
return {"error": str(e)}
|
| 1592 |
|
| 1593 |
+
@router.get("/net_info")
|
| 1594 |
+
async def net_info():
|
| 1595 |
+
"""Enumerate local network interfaces & IPv4/IPv6 addresses to explain absence of host candidates."""
|
| 1596 |
+
import socket
|
| 1597 |
+
info = []
|
| 1598 |
+
try:
|
| 1599 |
+
# socket.getaddrinfo on hostname
|
| 1600 |
+
try:
|
| 1601 |
+
hn = socket.gethostname()
|
| 1602 |
+
host_addrs = list({ai[4][0] for ai in socket.getaddrinfo(hn, None) if ai and ai[4]})
|
| 1603 |
+
except Exception as e:
|
| 1604 |
+
host_addrs = [f"error:{e}"]
|
| 1605 |
+
# Iterate common interfaces via /sys/class/net (Linux in HF Spaces)
|
| 1606 |
+
sys_net = '/sys/class/net'
|
| 1607 |
+
if os.path.isdir(sys_net):
|
| 1608 |
+
for iface in os.listdir(sys_net):
|
| 1609 |
+
if iface.startswith('lo'):
|
| 1610 |
+
continue
|
| 1611 |
+
addrs = []
|
| 1612 |
+
try:
|
| 1613 |
+
# Attempt IPv4 probe using dummy UDP socket bind trick
|
| 1614 |
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
| 1615 |
+
try:
|
| 1616 |
+
s.connect(('8.8.8.8', 80))
|
| 1617 |
+
addrs.append(s.getsockname()[0])
|
| 1618 |
+
except Exception:
|
| 1619 |
+
pass
|
| 1620 |
+
finally:
|
| 1621 |
+
s.close()
|
| 1622 |
+
except Exception:
|
| 1623 |
+
pass
|
| 1624 |
+
info.append({"iface": iface, "addresses": list(set(addrs))})
|
| 1625 |
+
return {"hostname_addrs": host_addrs, "ifaces": info}
|
| 1626 |
+
except Exception as e:
|
| 1627 |
+
return {"error": str(e)}
|
| 1628 |
+
|
| 1629 |
|
| 1630 |
@router.get("/token")
|
| 1631 |
async def mint_token():
|