368 lines
12 KiB
Rust
368 lines
12 KiB
Rust
//! Provider registration state machine.
|
|
//!
|
|
//! Handles the REGISTER cycle with upstream SIP providers:
|
|
//! - Sends periodic REGISTER messages
|
|
//! - Handles 401/407 Digest authentication challenges
|
|
//! - Detects public IP from Via received= parameter
|
|
//! - Emits registration state events to TypeScript
|
|
//!
|
|
//! Ported from ts/providerstate.ts.
|
|
|
|
use crate::config::ProviderConfig;
|
|
use crate::ipc::{emit_event, OutTx};
|
|
use sip_proto::helpers::{
|
|
compute_digest_auth, generate_branch, generate_call_id, generate_tag, parse_digest_challenge,
|
|
};
|
|
use sip_proto::message::{RequestOptions, SipMessage};
|
|
use std::net::SocketAddr;
|
|
use std::sync::Arc;
|
|
use tokio::net::UdpSocket;
|
|
use tokio::sync::Mutex;
|
|
use tokio::time::{self, Duration};
|
|
|
|
/// Runtime state for a single SIP provider.
|
|
pub struct ProviderState {
|
|
pub config: ProviderConfig,
|
|
pub public_ip: Option<String>,
|
|
pub is_registered: bool,
|
|
pub registered_aor: String,
|
|
|
|
// Registration transaction state.
|
|
reg_call_id: String,
|
|
reg_cseq: u32,
|
|
reg_from_tag: String,
|
|
|
|
// Network.
|
|
lan_ip: String,
|
|
lan_port: u16,
|
|
}
|
|
|
|
impl ProviderState {
|
|
pub fn new(config: ProviderConfig, public_ip_seed: Option<&str>) -> Self {
|
|
let aor = format!("sip:{}@{}", config.username, config.domain);
|
|
Self {
|
|
public_ip: public_ip_seed.map(|s| s.to_string()),
|
|
is_registered: false,
|
|
registered_aor: aor,
|
|
reg_call_id: generate_call_id(None),
|
|
reg_cseq: 0,
|
|
reg_from_tag: generate_tag(),
|
|
lan_ip: String::new(),
|
|
lan_port: 0,
|
|
config,
|
|
}
|
|
}
|
|
|
|
/// Build and send a REGISTER request.
|
|
pub fn build_register(&mut self) -> Vec<u8> {
|
|
self.reg_cseq += 1;
|
|
let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip);
|
|
|
|
let register = SipMessage::create_request(
|
|
"REGISTER",
|
|
&format!("sip:{}", self.config.domain),
|
|
RequestOptions {
|
|
via_host: pub_ip.to_string(),
|
|
via_port: self.lan_port,
|
|
via_transport: None,
|
|
via_branch: Some(generate_branch()),
|
|
from_uri: self.registered_aor.clone(),
|
|
from_display_name: None,
|
|
from_tag: Some(self.reg_from_tag.clone()),
|
|
to_uri: self.registered_aor.clone(),
|
|
to_display_name: None,
|
|
to_tag: None,
|
|
call_id: Some(self.reg_call_id.clone()),
|
|
cseq: Some(self.reg_cseq),
|
|
contact: Some(format!(
|
|
"<sip:{}@{}:{}>",
|
|
self.config.username, pub_ip, self.lan_port
|
|
)),
|
|
max_forwards: Some(70),
|
|
body: None,
|
|
content_type: None,
|
|
extra_headers: Some(vec![
|
|
(
|
|
"Expires".to_string(),
|
|
self.config.register_interval_sec.to_string(),
|
|
),
|
|
("User-Agent".to_string(), "SipRouter/1.0".to_string()),
|
|
(
|
|
"Allow".to_string(),
|
|
"INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE"
|
|
.to_string(),
|
|
),
|
|
]),
|
|
},
|
|
);
|
|
register.serialize()
|
|
}
|
|
|
|
/// Handle a SIP response that might be for this provider's REGISTER.
|
|
/// Returns true if the message was consumed.
|
|
pub fn handle_registration_response(&mut self, msg: &SipMessage) -> Option<Vec<u8>> {
|
|
if !msg.is_response() {
|
|
return None;
|
|
}
|
|
if msg.call_id() != self.reg_call_id {
|
|
return None;
|
|
}
|
|
let cseq_method = msg.cseq_method().unwrap_or("");
|
|
if !cseq_method.eq_ignore_ascii_case("REGISTER") {
|
|
return None;
|
|
}
|
|
|
|
let code = msg.status_code().unwrap_or(0);
|
|
|
|
if code == 200 {
|
|
self.is_registered = true;
|
|
return Some(Vec::new()); // consumed, no reply needed
|
|
}
|
|
|
|
if code == 401 || code == 407 {
|
|
let challenge_header = if code == 401 {
|
|
msg.get_header("WWW-Authenticate")
|
|
} else {
|
|
msg.get_header("Proxy-Authenticate")
|
|
};
|
|
|
|
let challenge_header = match challenge_header {
|
|
Some(h) => h,
|
|
None => return Some(Vec::new()), // consumed but no challenge
|
|
};
|
|
|
|
let challenge = match parse_digest_challenge(challenge_header) {
|
|
Some(c) => c,
|
|
None => return Some(Vec::new()),
|
|
};
|
|
|
|
let auth_value = compute_digest_auth(
|
|
&self.config.username,
|
|
&self.config.password,
|
|
&challenge.realm,
|
|
&challenge.nonce,
|
|
"REGISTER",
|
|
&format!("sip:{}", self.config.domain),
|
|
challenge.algorithm.as_deref(),
|
|
challenge.opaque.as_deref(),
|
|
);
|
|
|
|
// Resend REGISTER with auth credentials.
|
|
self.reg_cseq += 1;
|
|
let pub_ip = self.public_ip.as_deref().unwrap_or(&self.lan_ip);
|
|
|
|
let auth_header_name = if code == 401 {
|
|
"Authorization"
|
|
} else {
|
|
"Proxy-Authorization"
|
|
};
|
|
|
|
let register = SipMessage::create_request(
|
|
"REGISTER",
|
|
&format!("sip:{}", self.config.domain),
|
|
RequestOptions {
|
|
via_host: pub_ip.to_string(),
|
|
via_port: self.lan_port,
|
|
via_transport: None,
|
|
via_branch: Some(generate_branch()),
|
|
from_uri: self.registered_aor.clone(),
|
|
from_display_name: None,
|
|
from_tag: Some(self.reg_from_tag.clone()),
|
|
to_uri: self.registered_aor.clone(),
|
|
to_display_name: None,
|
|
to_tag: None,
|
|
call_id: Some(self.reg_call_id.clone()),
|
|
cseq: Some(self.reg_cseq),
|
|
contact: Some(format!(
|
|
"<sip:{}@{}:{}>",
|
|
self.config.username, pub_ip, self.lan_port
|
|
)),
|
|
max_forwards: Some(70),
|
|
body: None,
|
|
content_type: None,
|
|
extra_headers: Some(vec![
|
|
(auth_header_name.to_string(), auth_value),
|
|
(
|
|
"Expires".to_string(),
|
|
self.config.register_interval_sec.to_string(),
|
|
),
|
|
("User-Agent".to_string(), "SipRouter/1.0".to_string()),
|
|
(
|
|
"Allow".to_string(),
|
|
"INVITE, ACK, OPTIONS, CANCEL, BYE, SUBSCRIBE, NOTIFY, INFO, REFER, UPDATE"
|
|
.to_string(),
|
|
),
|
|
]),
|
|
},
|
|
);
|
|
return Some(register.serialize());
|
|
}
|
|
|
|
if code >= 400 {
|
|
self.is_registered = false;
|
|
}
|
|
|
|
Some(Vec::new()) // consumed
|
|
}
|
|
|
|
/// Detect public IP from Via received= parameter.
|
|
pub fn detect_public_ip(&mut self, via: &str) {
|
|
if let Some(m) = via.find("received=") {
|
|
let rest = &via[m + 9..];
|
|
let end = rest
|
|
.find(|c: char| !c.is_ascii_digit() && c != '.')
|
|
.unwrap_or(rest.len());
|
|
let ip = &rest[..end];
|
|
if !ip.is_empty() && self.public_ip.as_deref() != Some(ip) {
|
|
self.public_ip = Some(ip.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn set_network(&mut self, lan_ip: &str, lan_port: u16) {
|
|
self.lan_ip = lan_ip.to_string();
|
|
self.lan_port = lan_port;
|
|
}
|
|
}
|
|
|
|
/// Manages all provider states and their registration cycles.
|
|
pub struct ProviderManager {
|
|
providers: Vec<Arc<Mutex<ProviderState>>>,
|
|
out_tx: OutTx,
|
|
}
|
|
|
|
impl ProviderManager {
|
|
pub fn new(out_tx: OutTx) -> Self {
|
|
Self {
|
|
providers: Vec::new(),
|
|
out_tx,
|
|
}
|
|
}
|
|
|
|
/// Initialize providers from config and start registration cycles.
|
|
pub async fn configure(
|
|
&mut self,
|
|
configs: &[ProviderConfig],
|
|
public_ip_seed: Option<&str>,
|
|
lan_ip: &str,
|
|
lan_port: u16,
|
|
socket: Arc<UdpSocket>,
|
|
) {
|
|
self.providers.clear();
|
|
|
|
for cfg in configs {
|
|
let mut ps = ProviderState::new(cfg.clone(), public_ip_seed);
|
|
ps.set_network(lan_ip, lan_port);
|
|
let ps = Arc::new(Mutex::new(ps));
|
|
self.providers.push(ps.clone());
|
|
|
|
// Start the registration cycle.
|
|
let socket = socket.clone();
|
|
let out_tx = self.out_tx.clone();
|
|
tokio::spawn(async move {
|
|
provider_register_loop(ps, socket, out_tx).await;
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Try to handle a SIP response as a provider registration response.
|
|
/// Returns true if consumed.
|
|
pub async fn handle_response(
|
|
&self,
|
|
msg: &SipMessage,
|
|
socket: &UdpSocket,
|
|
) -> bool {
|
|
for ps_arc in &self.providers {
|
|
let mut ps = ps_arc.lock().await;
|
|
let was_registered = ps.is_registered;
|
|
if let Some(reply) = ps.handle_registration_response(msg) {
|
|
// If there's a reply to send (e.g. auth retry).
|
|
if !reply.is_empty() {
|
|
if let Some(dest) = ps.config.outbound_proxy.to_socket_addr() {
|
|
let _ = socket.send_to(&reply, dest).await;
|
|
}
|
|
}
|
|
// Emit registration state change.
|
|
if ps.is_registered != was_registered {
|
|
emit_event(
|
|
&self.out_tx,
|
|
"provider_registered",
|
|
serde_json::json!({
|
|
"provider_id": ps.config.id,
|
|
"registered": ps.is_registered,
|
|
"public_ip": ps.public_ip,
|
|
}),
|
|
);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Find which provider sent a packet by matching source address.
|
|
pub async fn find_by_address(&self, addr: &SocketAddr) -> Option<Arc<Mutex<ProviderState>>> {
|
|
for ps_arc in &self.providers {
|
|
let ps = ps_arc.lock().await;
|
|
let proxy_addr = format!(
|
|
"{}:{}",
|
|
ps.config.outbound_proxy.address, ps.config.outbound_proxy.port
|
|
);
|
|
if let Ok(expected) = proxy_addr.parse::<SocketAddr>() {
|
|
if expected == *addr {
|
|
return Some(ps_arc.clone());
|
|
}
|
|
}
|
|
// Also match by IP only (port may differ).
|
|
if ps.config.outbound_proxy.address == addr.ip().to_string() {
|
|
return Some(ps_arc.clone());
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Check if a provider is currently registered.
|
|
pub async fn is_registered(&self, provider_id: &str) -> bool {
|
|
for ps_arc in &self.providers {
|
|
let ps = ps_arc.lock().await;
|
|
if ps.config.id == provider_id {
|
|
return ps.is_registered;
|
|
}
|
|
}
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Registration loop for a single provider.
|
|
async fn provider_register_loop(
|
|
ps: Arc<Mutex<ProviderState>>,
|
|
socket: Arc<UdpSocket>,
|
|
_out_tx: OutTx,
|
|
) {
|
|
// Initial registration.
|
|
{
|
|
let mut state = ps.lock().await;
|
|
let register_buf = state.build_register();
|
|
if let Some(dest) = state.config.outbound_proxy.to_socket_addr() {
|
|
let _ = socket.send_to(®ister_buf, dest).await;
|
|
}
|
|
}
|
|
|
|
// Re-register periodically (85% of the interval).
|
|
let interval_sec = {
|
|
let state = ps.lock().await;
|
|
(state.config.register_interval_sec as f64 * 0.85) as u64
|
|
};
|
|
let mut interval = time::interval(Duration::from_secs(interval_sec.max(30)));
|
|
interval.tick().await; // skip first immediate tick
|
|
|
|
loop {
|
|
interval.tick().await;
|
|
let mut state = ps.lock().await;
|
|
let register_buf = state.build_register();
|
|
if let Some(dest) = state.config.outbound_proxy.to_socket_addr() {
|
|
let _ = socket.send_to(®ister_buf, dest).await;
|
|
}
|
|
}
|
|
}
|