Skip to content
Merged
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
5 changes: 5 additions & 0 deletions config.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
---
bridge_networks:
- name: osbr0
# pin the host-side bridge device name so a host-networked capture
# sidecar (rtp_relay set) can tcpdump -i osbr0 and see inter-container
# traffic; harmless for every other set
device: osbr0
subnet: {{ network_range }}
gateway: {{ network_gateway }}
- name: osbr1
device: osbr1
subnet: {{ network_range_osbr1 }}
gateway: {{ network_gateway_osbr1 }}

Expand Down
11 changes: 11 additions & 0 deletions defines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,14 @@ mysql_root_host: 192.168.52.%
username: testing
password: T3st|n6
nonce: 035cf70b1c4fcf4731150330410151743443

# --- rtp_relay / rtpengine (used only by the rtp_relay test set) ---
rtpengine_ip: 192.168.52.4
rtpengine_ng_port: 22222
# Stock Debian base; the rtp_relay rtpengine task apt-installs the
# rtpengine-daemon package (sipwise upstream, userspace-only) at start.
rtpengine_image: debian:trixie-slim
# packet-capture sidecar (host networking, sniffs the osbr0 bridge device)
capture_image: nicolaka/netshoot
# pure-python ng checker; python-opensips already ships python3
checker_image: ghcr.io/opensips/python-opensips
79 changes: 79 additions & 0 deletions rtp_relay/01.initial-offer-savpf/opensips.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#
# GH-3902 rtp_relay -- SIP -> WebRTC transcoding proxy.
# Caller leg is plain RTP/AVP (request route); the callee WebRTC profile
# (UDP/TLS/RTP/SAVPF + ICE=force) is set in branch_route[webrtc]. Affected
# OpenSIPS never links that branch leg into sess->legs[CALLEE] before the offer,
# so rtp_relay asks rtpengine for plain RTP/AVP -- the #3902 bug. The checker
# inspects the ng OFFER/ANSWER OpenSIPS sends on the rtpengine ng port.
#
log_level=4 # DBG: rtp_relay_offer prints type=[...] peer-flags=[...]
stderror_enabled=yes

udp_workers=4

socket=udp:192.168.52.1:5060

####### Modules #######
mpath="/usr/lib/x86_64-linux-gnu/opensips/modules/"
loadmodule "proto_udp.so"
loadmodule "tm.so"
loadmodule "sl.so"
loadmodule "rr.so"
loadmodule "maxfwd.so"
loadmodule "sipmsgops.so"
loadmodule "dialog.so"
loadmodule "rtpengine.so"
loadmodule "rtp_relay.so"

modparam("rtpengine", "rtpengine_sock", "udp:192.168.52.4:22222")

####### Routing #######
route{
if (!mf_process_maxfwd_header(10)) {
sl_send_reply(483,"Too Many Hops");
exit;
}

if (has_totag()) {
if (loose_route()) {
xlog("L_NOTICE","SEQ $rm cseq=$cs\n");
t_relay();
exit;
}
sl_send_reply(404,"Not here");
exit;
}

if (is_method("CANCEL")) {
t_relay();
exit;
}

if (is_method("INVITE")) {
xlog("L_NOTICE","INITIAL INVITE -- caller(RTP/AVP) leg + engage\n");
create_dialog();

# ---- caller (inbound) leg: plain SIP, RTP/AVP ----
$rtp_relay(flags) = "replace-origin replace-session-connection";
$rtp_relay(type) = "RTP/AVP";
$rtp_relay(peer) = "ICE=remove";

rtp_relay_engage("rtpengine");
record_route();
$du = "sip:192.168.52.3:5080";
t_on_branch("webrtc");
t_relay();
exit;
}

sl_send_reply(405,"Method Not Allowed");
}

# ---- callee (outbound) leg: WebRTC, provisioned at the branch index ----
# This is the leg that affected OpenSIPS fails to link into sess->legs[CALLEE].
branch_route[webrtc] {
xlog("L_NOTICE","BRANCH webrtc -- provisioning callee WebRTC profile\n");
$rtp_relay(flags) = "replace-origin replace-session-connection";
$rtp_relay(type) = "UDP/TLS/RTP/SAVPF";
$rtp_relay(peer) = "ICE=force rtcp-mux-require SDES-off";
}
100 changes: 100 additions & 0 deletions rtp_relay/01.initial-offer-savpf/scenario.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
# GH-3902 rtp_relay -- initial SDP-in-INVITE offer.
#
# A plain SIP caller (RTP/AVP) calls a callee whose WebRTC profile
# (UDP/TLS/RTP/SAVPF + ICE=force) is provisioned in branch_route[webrtc]. The
# checker asserts the ng OFFER OpenSIPS sends to rtpengine carries that profile.
# On affected OpenSIPS the branch-provisioned callee leg is not linked before
# the offer, so the profile is dropped (RED). With the fix it survives (GREEN).
#
# The checker inspects what OpenSIPS ASKS rtpengine (transport-protocol / ICE),
# independent of any real WebRTC endpoint -- so a trivial SIP UAS suffices.

timeout: 300

tasks:
# rtpengine media proxy (userspace only, no kernel module / privileges).
- name: rtpengine
type: generic
image: {{ rtpengine_image }}
ip: {{ rtpengine_ip }}
daemon: true
stop_timeout: 3
entrypoint: sh
# debian:slim base -> install the official Debian rtpengine-daemon package
# (sipwise upstream; userspace-only, no kernel dkms). policy-rc.d keeps the
# package postinst from starting a service; iproute2 provides `ss` for the
# readiness healthcheck below. Then run rtpengine in the foreground.
args: ["-c", "echo 'exit 101' > /usr/sbin/policy-rc.d && chmod +x /usr/sbin/policy-rc.d && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends rtpengine-daemon iproute2 && exec rtpengine --table=-1 --interface={{ rtpengine_ip }} --listen-ng={{ rtpengine_ip }}:{{ rtpengine_ng_port }} --foreground --log-stderr"]
# healthy once the ng socket is bound, so dependents gate on readiness (not a
# fixed sleep) and the apt-install delay cannot race the proxy startup ping.
healthcheck:
test: "ss -lnu | grep -q :{{ rtpengine_ng_port }}"
interval: 2000000000
timeout: 2000000000
retries: 120

# Sidecar that captures the rtpengine ng UDP stream into a shared rw volume.
# It runs with HOST networking and sniffs the osbr0 bridge DEVICE, so it sees
# the OpenSIPS<->rtpengine traffic between two other containers (a normal
# bridge-attached sidecar would only see its own traffic). The built-in
# sipssert tracer also captures capture.pcap on osbr0, but only finalizes it
# after the scenario ends, so a mid-run checker task cannot read it -- hence
# this dedicated capture into a volume the checker then mounts.
- name: ng capture
type: generic
image: {{ capture_image }}
network: host
daemon: true
stop_timeout: 2
volumes:
- caps
entrypoint: sh
args: ["-c", "tcpdump -i osbr0 -n -s0 -U -w /caps/ng.pcap udp port {{ rtpengine_ng_port }}"]
require:
healthy: rtpengine

- name: OpenSIPS
type: opensips
require:
- healthy: rtpengine
- started:
task: ng capture
wait: 0.5

- name: SIPP UAS
type: uas-sipp
config_file: scripts/uas.xml
require: OpenSIPS

- name: SIPP UAC
type: uac-sipp
config_file: scripts/uac.xml
remote: {{ uas_ip }}:{{ uas_port }}
caller: caller
calls: 1
require:
- started:
task: SIPP UAS
wait: 0.5
- after:
task: OpenSIPS
wait: 0.5

# ng checker: PASS iff the OFFER carries UDP/TLS/RTP/SAVPF + ICE=force.
- name: ng checker
type: generic
image: {{ checker_image }}
volumes:
- caps
entrypoint: python3
args: ["/home/scripts/ng_checker.py", "/caps/ng.pcap", "savpf", "--ng-port", "{{ rtpengine_ng_port }}"]
require:
after:
task: SIPP UAC
wait: 2

volumes:
caps:
bind: /caps
mode: rw
Loading
Loading