Skip to content

Add ability to customize stun/turn server #261

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions env-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
PORT=3000
REDIS_URL=redis://redis:6379
COTURN_ENABLED=true
TURN_REALM=file.pizza
TURN_HOST=file.pizza
TURN_PORT=3478
TURNS_PORT=5349
TURN_CRED=
TURN_TRANS=both
STUN_HOST=
STUN_PORT=
62 changes: 54 additions & 8 deletions src/app/api/ice/route.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,75 @@
import { NextResponse } from 'next/server'
import crypto from 'crypto'
import { setTurnCredentials } from '../../../coturn'
import { createHmac } from 'crypto'
import { setTurnCredentials, setTurnCredentials2 } from '../../../coturn'

const turnHost = process.env.TURN_HOST || '127.0.0.1'
const stunHost = process.env.STUN_HOST || 'stun.l.google.com'
const stunPort = process.env.STUN_PORT || 19302
const turnHost = process.env.TURN_HOST || '127.0.0.1'
const turnTrans = process.env.TURN_TRANS || 'both' // both = udp and tcp, udp = udp only, tcp = tcp only
const turnCred = process.env.TURN_CRED || ''
const turnPort = process.env.TURN_PORT || 3478
const turnsPort = process.env.TURNS_PORT || 5349

export async function POST(): Promise<NextResponse> {
if (!process.env.COTURN_ENABLED) {
return NextResponse.json({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
iceServers: [{ urls: `stun:${stunHost}:${stunPort}` }],
})
}

// Use hmac to create a password from username and turnCred
function generateHmacCred(username: string): string {
return createHmac('sha1', turnCred).update(username).digest('hex')
}

// Generate ephemeral credentials
const username = crypto.randomBytes(8).toString('hex')
const password = crypto.randomBytes(8).toString('hex')
const ttl = 86400 // 24 hours
let username: string = ''
let password: string = ''
if (!turnCred) {
username = crypto.randomBytes(8).toString('hex')
password = crypto.randomBytes(8).toString('hex')
} else {
let now: number = Date.now() + ttl
username = String(now)
//password = String(turnCred)
password = generateHmacCred(username)
}

// Store stun and turn hosts in an array
const ar_stun: string[] = []
const ar_turn: string[] = []
let base_stun: string = 'stun:' + stunHost + ':' + stunPort
let base_turn: string = 'turn:' + turnHost + ':' + turnPort
let base_turns: string = 'turns:' + turnHost + ':' + turnsPort

ar_stun.push(base_stun)
if (turnTrans === 'both') {
ar_turn.push(base_turn + '?transport=udp')
ar_turn.push(base_turn + '?transport=tcp')
ar_turn.push(base_turns + '?transport=udp')
ar_turn.push(base_turns + '?transport=tcp')
} else if (turnTrans === 'tcp') {
ar_turn.push(base_turn + '?transport=tcp')
ar_turn.push(base_turns + '?transport=tcp')
} else {
ar_turn.push(base_turn + '?transport=udp')
ar_turn.push(base_turns + '?transport=udp')
}

// Store credentials in Redis
await setTurnCredentials(username, password, ttl)
if (!turnCred) {
await setTurnCredentials(username, password, ttl)
} else {
await setTurnCredentials2(username, password, ttl)
}

return NextResponse.json({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: ar_stun },
{
urls: [`turn:${turnHost}:3478`, `turns:${turnHost}:5349`],
urls: ar_turn,
username,
credential: password,
},
Expand Down
23 changes: 23 additions & 0 deletions src/coturn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ export async function setTurnCredentials(

await redis.setex(key, ttl, hmacKey)
}

export async function setTurnCredentials2(
username: string,
password: string,
ttl: number,
): Promise<void> {
if (!process.env.COTURN_ENABLED) {
return
}

const realm = process.env.TURN_REALM || 'file.pizza'

if (!realm) {
throw new Error('TURN_REALM environment variable not set')
}

const redis = getRedisClient()

const hmacKey = password
const key = `turn/realm/${realm}/user/${username}/key`

await redis.setex(key, ttl, hmacKey)
}