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
Files changed (1) hide show
  1. 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
- # Create a dummy data channel to ensure ICE starts
 
 
 
 
 
 
 
 
 
 
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
- while pc.iceGatheringState != 'complete' and (time.time() - t0) < 6.0:
1545
- await asyncio.sleep(0.2)
 
 
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) < 25: # cap to avoid huge payloads
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():