asterisk-external-media
asterisk-external-media copied to clipboard
No communication between local server and Asterisk in container
@gtjoseph Could the Asterisk version be a plausible cause? I looked through the changelog but couldn't find any ARI related breaking changes. In my case, everything seems to be fine but the audio server running locally (host) just doesn't receive any packages from Asterisk 20 running within Docker.
Originally posted by @jneuendorf-i4h in https://github.com/asterisk/asterisk-external-media/issues/11#issuecomment-1664608700
Can you run Asterisk on the host just as a test and see if it works? What kind of networking are you using with docker? Can you connect to asterisk's built-in web server from a browser on the host?
First of all, thanks for the quick response! 🙏
Can you run Asterisk on the host just as a test and see if it works?
My setup is on macOS and I am not sure if Asterisk can be installed on macOS as easily as on Debian/Ubuntu. Therefore, I have not tried this approach yet.
What kind of networking are you using with docker?
So far, I stuck with the defaults. With network_mode: host
(docker-compose.yml) phone calls no longer worked.
Can you connect to asterisk's built-in web server from a browser on the host?
- Yes, on my 1st attempt, I started the ARI client on the host and the connection to the REST API worked (authentication, channel creation etc.)
- 2nd attempt: Because I suspected networking problems due to Docker, I started the ARI client from within docker (so everything on
localhost
/127.0.0.1 within Docker. But the behavior stayed the same. 🤔
I actually simplified/adjusted this repo's code for the start, as I want to initiate a call to an SIP device (PJSIP/6001) and stream the audio from/to the audio server (without any processing for now).
Any advice or hint would be very helpful, so let me know if anything is unclear or if there is information missing.
Also, debugging tips are very welcome (so far, I've only used ari set debug all on
). 😉
For better insights, here my code:
Client
src/upd.mjs
Start the audio server in a separate script to reduce complexity in the main script (for debugging).
import { RtpUdpServerSocket } from "../lib/rtp-udp-server.js"
const audioServer = new RtpUdpServerSocket(
'0.0.0.0:9999',
false,
"./test.wav",
)
console.log(audioServer)
src/send.mjs
Send a test message to the audio server.
import dgram from 'node:dgram'
import { Buffer } from 'node:buffer'
const message = Buffer.from('A few bytes to send for testing purposes...')
const client = dgram.createSocket('udp4')
client.send(message, 9999, 'localhost', (err) => {
console.log('error', err)
client.close()
})
src/main.mjs
Start the ARI application, setup the channels and bridge, and make the call.
import ari from 'ari-client'
const options = {
dialstring: "PJSIP/6001",
format: "ulaw",
listenServer: '127.0.0.1:9999',
stasisApp: "external-media",
}
ari.connect(
'http://127.0.0.1:8088',
'asterisk',
'asterisk',
async (error, client) => {
if (error) {
console.error(error)
console.warn("CLOSING")
}
await client.start(options.stasisApp)
// Create a simple mixing bridge that is controlled by ARI/Stasis
const mixingBridge = client.Bridge()
try {
await mixingBridge.create({type: "mixing"})
console.log("created mixing bridge")
} catch(error) {
console.error(error)
console.warn("CLOSING")
}
mixingBridge.on('BridgeDestroyed', (event) => {
console.log("destroyed mixing bridge")
console.warn("CLOSING")
})
const outboundChannel = client.Channel()
outboundChannel.on('StasisStart', (event, {id}) => {
mixingBridge.addChannel({channel: id})
console.log("added outbound channel to mixing bridge", id)
})
outboundChannel.on('StasisEnd', (event, chan) => {
console.warn("CLOSING")
})
// Call the phone or confbridge specified in dialstring
try {
await outboundChannel.originate({
endpoint: options.dialstring,
formats: options.format,
app: options.stasisApp,
})
// localChannel.dial()
} catch (error) {
console.error(error)
console.warn("CLOSING")
}
// Now we create the External Media channel.
const externalChannel = client.Channel()
externalChannel.on('StasisStart', (event, {id}) => {
mixingBridge.addChannel({channel: id})
console.log("added external channel to mixing bridge", id)
})
externalChannel.on('StasisEnd', (event, chan) => {
console.warn("CLOSING")
})
/*
* We give the external channel the address of the listener
* we already set up and the format it should stream in.
*/
const externalMediaChannel = await externalChannel.externalMedia({
app: options.stasisApp,
// external_host: options.listenServer,
external_host: "127.0.0.1:9999",
format: options.format,
})
console.log("externalMediaChannel", externalMediaChannel)
setTimeout(async () => {
console.warn("STOPPING")
outboundChannel && await outboundChannel.hangup()
externalChannel && await externalChannel.hangup()
mixingBridge && await mixingBridge.destroy()
client.stop()
}, 30 * 1000);
})
Docker
docker-compose.yml
version: '3'
services:
asterisk:
image: asterisk
build:
context: .
dockerfile: docker/asterisk/Dockerfile
# network_mode: host
ports:
- "8088:8088" # ARI HTTP
- "5060:5060/udp" # SIP UDP
- "5060:5060" # SIP TCP
- "5061:5061" # SIP TLS
- "10000-10099:10000-10099/udp" # RTP
- "9999:9999/udp" # test stream from client on host to audio server in Docker
Server
ari.conf
[general]
enabled = yes
pretty = yes
allowed_origins = *
[asterisk]
type = user
read_only = no
password = asterisk
http.conf
[general]
enabled = yes
bindaddr = 0.0.0.0
bindport = 8088
[asterisk]
type = user
read_only = no
password = asterisk
rtp.conf
[general]
rtpstart = 10000
rtpend = 10099
strictrtp = no
extensions.conf
[from-internal]
exten => 100,1,Answer()
same => n,Stasis(external-media)
same => n,Hangup()
pjsip.conf
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0
[6001]
type=endpoint
context=from-internal
disallow=all
allow=ulaw
auth=6001
aors=6001
[6001]
type=auth
auth_type=userpass
password=unsecurepassword
username=6001
[6001]
type=aor
max_contacts=1
seeing the same thing here; listening on port 9999 with netcat but doesn't seem anything is coming through.
have you solved you problem? @jneuendorf-i4h
Thanks for catching up @vlxdisluv! The currently working solution is running Asterisk not in a Docker container but in a (Proxmox) VM (LXC untested).
A reasonable cause is network_mode: host
on macOS:
The host networking driver only works on Linux hosts, and is not supported on Docker Desktop for Mac https://docs.docker.com/network/drivers/host/
So I guess, the issue can be closed, even though it would be helpful to have documentation for running Asterisk in Docker (on specific machines/operating systems).
@jneuendorf-i4h I have successfully configured Asterisk in Docker container (macOS m1 arm) because it can establish a peer-to-peer connection (between two softphones), but I don't have enough knowledge to write a proper Asterisk configuration for a workflow with external media. I need to initiate an outgoing ARI call to a softphone and receive real-time RTP packets and then send them to some STT provider. In this case, I also established a connection, but I cannot receive RTP packets. Could you share your working Asterisk configs?
@jneuendorf-i4h I have successfully configured Asterisk in Docker container (macOS m1 arm) because it can establish a peer-to-peer connection (between two softphones), but I don't have enough knowledge to write a proper Asterisk configuration for a workflow with external media. I need to initiate an outgoing ARI call to a softphone and receive real-time RTP packets and then send them to some STT provider. In this case, I also established a connection, but I cannot receive RTP packets. Could you share your working Asterisk configs?
Have you checked the audio signal of your p2p/softphone connection? I would not be surprised if the connection can be established while not receiving RTP packets. RTP debugging can be done via several different approaches, e.g.
-
pjsip set logger on
-
rtp set debug on
-
tcpdump -i <INTERFACE> -w dump.pcap
+ Wireshark (open file, telephony > VOIP calls: shows all call legs > flow)
On the JS client side, you could try (using the nomenclature from above)
setInterval(async () => {
const rtpStats = await outboundChannel.rtpstatistics()
console.log("RTP", rtpStats)
}, 5000)