fix(sfu): use concrete WebRTC listen IP for browser-consumable ICE candidates
This commit is contained in:
@@ -17,7 +17,7 @@ MEDIA_PROVIDER=mock
|
|||||||
TURN_URLS=
|
TURN_URLS=
|
||||||
TURN_USERNAME=
|
TURN_USERNAME=
|
||||||
TURN_CREDENTIAL=
|
TURN_CREDENTIAL=
|
||||||
MEDIA_WEBRTC_LISTEN_IP=0.0.0.0
|
MEDIA_WEBRTC_LISTEN_IP=127.0.0.1
|
||||||
MEDIA_WEBRTC_ANNOUNCED_IP=
|
MEDIA_WEBRTC_ANNOUNCED_IP=
|
||||||
MEDIA_RTC_MIN_PORT=40000
|
MEDIA_RTC_MIN_PORT=40000
|
||||||
MEDIA_RTC_MAX_PORT=49999
|
MEDIA_RTC_MAX_PORT=49999
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Required env vars:
|
|||||||
| `MEDIA_SFU_ENGINE` | SFU engine for `single_server_sfu` mode (`mediasoup` default, `noop` fallback) |
|
| `MEDIA_SFU_ENGINE` | SFU engine for `single_server_sfu` mode (`mediasoup` default, `noop` fallback) |
|
||||||
| `MEDIA_PROVIDER` | Media backend provider (`mock` by default) |
|
| `MEDIA_PROVIDER` | Media backend provider (`mock` by default) |
|
||||||
| `TURN_URLS` / `TURN_USERNAME` / `TURN_CREDENTIAL` | TURN/STUN configuration used by single-server SFU mode |
|
| `TURN_URLS` / `TURN_USERNAME` / `TURN_CREDENTIAL` | TURN/STUN configuration used by single-server SFU mode |
|
||||||
| `MEDIA_WEBRTC_LISTEN_IP` / `MEDIA_WEBRTC_ANNOUNCED_IP` | WebRTC transport bind/announce IPs for mediasoup SFU |
|
| `MEDIA_WEBRTC_LISTEN_IP` / `MEDIA_WEBRTC_ANNOUNCED_IP` | WebRTC transport bind/announce IPs for mediasoup SFU. Do not leave bind IP as `0.0.0.0` without an announced IP in non-local environments. |
|
||||||
| `MEDIA_RTC_MIN_PORT` / `MEDIA_RTC_MAX_PORT` | UDP/TCP RTP port range for mediasoup worker |
|
| `MEDIA_RTC_MIN_PORT` / `MEDIA_RTC_MAX_PORT` | UDP/TCP RTP port range for mediasoup worker |
|
||||||
| `MEDIA_RECORDINGS_DIR` | Local output directory for server-side recording workers (planned in SFU mode) |
|
| `MEDIA_RECORDINGS_DIR` | Local output directory for server-side recording workers (planned in SFU mode) |
|
||||||
| `MEDIA_MAX_PUBLISHERS` / `MEDIA_MAX_SUBSCRIBERS_PER_ROOM` | Soft concurrency limits for single-server media mode (planned) |
|
| `MEDIA_MAX_PUBLISHERS` / `MEDIA_MAX_SUBSCRIBERS_PER_ROOM` | Soft concurrency limits for single-server media mode (planned) |
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { randomUUID } from 'crypto';
|
import { randomUUID } from 'crypto';
|
||||||
|
import { networkInterfaces } from 'os';
|
||||||
|
|
||||||
import { mediaConfig } from '../config';
|
import { mediaConfig } from '../config';
|
||||||
import type {
|
import type {
|
||||||
@@ -39,6 +40,45 @@ const parsePort = (value: string | undefined, fallback: number): number => {
|
|||||||
return parsed;
|
return parsed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pickHostIpv4 = (): string | null => {
|
||||||
|
const interfaces = networkInterfaces();
|
||||||
|
for (const addresses of Object.values(interfaces)) {
|
||||||
|
if (!addresses) continue;
|
||||||
|
for (const address of addresses) {
|
||||||
|
if (address.family === 'IPv4' && !address.internal) {
|
||||||
|
return address.address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveListenAddress = (): { ip: string; announcedAddress?: string } => {
|
||||||
|
const configuredListenIp = (process.env.MEDIA_WEBRTC_LISTEN_IP ?? '').trim();
|
||||||
|
const configuredAnnounced = process.env.MEDIA_WEBRTC_ANNOUNCED_IP?.trim();
|
||||||
|
|
||||||
|
if (configuredListenIp && configuredListenIp !== '0.0.0.0') {
|
||||||
|
return {
|
||||||
|
ip: configuredListenIp,
|
||||||
|
...(configuredAnnounced ? { announcedAddress: configuredAnnounced } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveredIp = pickHostIpv4();
|
||||||
|
if (!configuredAnnounced && configuredListenIp === '0.0.0.0') {
|
||||||
|
console.warn(
|
||||||
|
`[sfu] MEDIA_WEBRTC_LISTEN_IP is 0.0.0.0 without MEDIA_WEBRTC_ANNOUNCED_IP. ` +
|
||||||
|
`Using ${discoveredIp ?? '127.0.0.1'} for ICE candidates. Configure both env vars for production.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ip = discoveredIp ?? '127.0.0.1';
|
||||||
|
return {
|
||||||
|
ip,
|
||||||
|
...(configuredAnnounced ? { announcedAddress: configuredAnnounced } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const toTransportOptions = (transport: any): SfuTransportOptions => ({
|
const toTransportOptions = (transport: any): SfuTransportOptions => ({
|
||||||
id: transport.id,
|
id: transport.id,
|
||||||
iceParameters: transport.iceParameters ?? {},
|
iceParameters: transport.iceParameters ?? {},
|
||||||
@@ -105,19 +145,18 @@ export class MediasoupSfuService implements SfuService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async createWebRtcTransport(router: any): Promise<any> {
|
private async createWebRtcTransport(router: any): Promise<any> {
|
||||||
const listenIp = process.env.MEDIA_WEBRTC_LISTEN_IP ?? '0.0.0.0';
|
const listenAddress = resolveListenAddress();
|
||||||
const announcedAddress = process.env.MEDIA_WEBRTC_ANNOUNCED_IP?.trim();
|
|
||||||
return await router.createWebRtcTransport({
|
return await router.createWebRtcTransport({
|
||||||
listenInfos: [
|
listenInfos: [
|
||||||
{
|
{
|
||||||
protocol: 'udp',
|
protocol: 'udp',
|
||||||
ip: listenIp,
|
ip: listenAddress.ip,
|
||||||
...(announcedAddress ? { announcedAddress } : {}),
|
...(listenAddress.announcedAddress ? { announcedAddress: listenAddress.announcedAddress } : {}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
protocol: 'tcp',
|
protocol: 'tcp',
|
||||||
ip: listenIp,
|
ip: listenAddress.ip,
|
||||||
...(announcedAddress ? { announcedAddress } : {}),
|
...(listenAddress.announcedAddress ? { announcedAddress: listenAddress.announcedAddress } : {}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
enableUdp: true,
|
enableUdp: true,
|
||||||
@@ -364,4 +403,3 @@ export class MediasoupSfuService implements SfuService {
|
|||||||
return consumerDescriptor;
|
return consumerDescriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user