Skip to content

FreeSBC v0.3.0

Choose a tag to compare

@github-actions github-actions released this 16 Oct 02:51
· 32 commits to main since this release
2391fdb

FreeSBC v0.3.0 hardens the public edge and finishes topology hiding. Client
legs now hide the switch the way carrier legs already did, a distributed REGISTER
brute force, slow scanners and malformed datagrams are now banned rather than
only dropped, a lost 2xx ACK is reported as such, and the edge can mirror its
SIP signaling to HOMER over HEP. It adds edge.advertise for NATs that
translate a port, a WebUI login page, a deployment guide for two FreeSBC nodes,
and fixes for several call-setup races. There are no configuration breaking
changes: a v0.2.0 file loads unchanged. Still one static binary, one YAML file,
no database, no Redis, no kernel modules, no external media process.

Breaking changes

None. Every new configuration key is optional, no key was renamed or removed,
and the Go and sipgo requirements are unchanged (Go 1.27.2 to build from
source, sipgo v1.4.3). The behavior changes below are what an operator may
notice after upgrading; read them before restarting a production node.

Behavior changes

  • Per-AoR REGISTER lockout is on by default (#207, #211). Refused REGISTER
    credentials are now also counted per AoR across all sources. Over
    shield.register_ban_aor (default 10/10m) the AoR is locked for
    shield.ban, and a REGISTER for it from a source with no live binding of
    it is dropped silently. A deliberate lockout of a real AoR is an accepted
    trade-off. Set shield.register_ban_aor: off to disable.
  • Failed REGISTER credentials ban the source (#186). A REGISTER with
    Authorization answered 401/403/404/407 is a strike; over
    shield.register_ban (default 10/10m) the source IP is banned for
    shield.ban. Refreshes of a live binding and carrier sources never count.
    Set shield.register_ban: off to disable.
  • Admission drops now lead to a ban (#195). A public source that is
    neither a carrier nor a registered IP and keeps getting dropped
    (out-of-dialog INVITE, SUBSCRIBE, MESSAGE, OPTIONS, dialog-less BYE/INFO)
    is banned for shield.ban over shield.admission_ban (default 50/5m). A
    UDP-sourced ban blocks only UDP from that IP. Set
    shield.admission_ban: off to disable.
  • Malformed public datagrams count as strikes (#205). A malformed UDP
    datagram or WS/WSS frame feeds the same shield.admission_ban budget; the
    read still goes to sipgo unchanged. There is no separate key, so
    shield.admission_ban: off disables this too.
  • Public OPTIONS from unregistered sources is dropped silently (#218). A
    public OPTIONS is answered only for a live registration source, a carrier
    source, or a To tag naming a live dialog. A phone whose binding has lapsed
    gets no reply to its OPTIONS keep-alive until it registers again, and a
    client that probes with OPTIONS before its first REGISTER gets none. Counted
    as options_not_admitted. No opt-out.
  • Dialog-less public BYE and INFO are refused (#194). A public BYE or INFO
    naming no dialog is forwarded only from a live registration or a carrier
    source; otherwise it gets 481 and nothing goes to the switch. Counted as
    dialogless_not_admitted. No opt-out.
  • Drain refuses only carrier calls (#204). Before, drain refused every new
    INVITE with 503. Now carrier to switch and switch to carrier get 503 +
    Retry-After: 30, while client to switch and switch to client are admitted.
    While draining, an OPTIONS from a carrier source, from the switch to
    FreeSBC itself, or from the switch to a carrier host also gets 503. A
    switch OPTIONS to an fsbc= token with no live binding is 404. To stop all
    new calls, drain the switch as well.
  • Client legs hide the switch (#192, #212). Toward a public client,
    FreeSBC now sends its own public Via and a single public Record-Route (the
    private.ip pair exists on the switch side only); the switch's Via, Server,
    User-Agent, X- and other P- headers, Warning, Path, Service-Route and
    P-Associated-URI are removed; private.ip or a switch IP in From, To and
    identity headers is masked to public.ip. A switch 3xx redirect toward a
    client is no longer usable, and hosts in free text such as Subject are not
    rewritten. A client that relied on a switch header will no longer see it.
    Carrier legs keep the switch's other headers (P-Charge-Info and similar).
    No opt-out.
  • An ACK that reuses the INVITE branch is forwarded (#179). An ACK after a
    2xx that reused the INVITE branch used to be swallowed by sipgo and never
    reached the switch; it is now forwarded. The ACK missed warnings are gone.
  • Lost 2xx ACKs end calls as ack_timeout (#177, #189). A call whose 2xx
    ACK never arrives now ends with reason ack_timeout (a 32 s backstop BYEs
    both ends) instead of looking like a normal bye_callee. Every call ended
    log line gains terminated_by=caller|callee|proxy. A BYE that crosses the
    ACK gets a 500 ms grace first.
  • UDP SIP socket buffers (#215). Every UDP SIP socket requests a 4 MiB
    receive and 1 MiB send buffer. If the kernel caps it, one Warn per socket
    appears at startup (on a default net.core.wmem_max of 212992 the send
    warning shows until the sysctl is raised). Startup never fails over it.
  • Admission summary count (#216). The once-a-minute admission drops WARN
    now also counts struck malformed reads and prints both kinds, so its first
    number is admission plus malformed.
  • WebUI login (#201). The WebUI signs in with a login form and an
    in-memory cookie session (idle 30 min, absolute 12 h, at most 8) instead of
    the browser's Basic Auth dialog. Existing sessions do not survive a
    restart. Basic Auth is unchanged for /metrics, /api/* and pprof, so
    Prometheus and curl scripts keep working.
  • New or extended read-only admin output. /api/shield/bans gains kind
    udp_ip and reasons register, admission and malformed;
    /api/status gains listener_info; /api/switch-nodes shows
    cooling_down while a REGISTER breaker is open. freesbc_edge_draining
    now means "refusing new carrier calls". See New metrics below.

What is new

Edge hardening

  • REGISTER brute-force defence (#186, #207, #211). Strikes per source and
    per AoR, with anti-forgery: a strike counts only if its digest nonce was in
    a challenge FreeSBC relayed to that source. A locked AoR survives a flood of
    distinct struck AoRs. New offender view GET /api/shield/offenders.
  • Slow scanner bans (#195, #205, #216). Admission drops and malformed
    public reads are struck against shield.admission_ban; the bans and the
    summary WARN are described above.
  • REGISTER circuit breaker (#193). A switch node that leaves REGISTER
    unanswered 3 times in a row is not dialed for 30 s, 1 m, then 5 m, with one
    probe when half-open; when every node is open the edge answers 503 +
    Retry-After: 30 itself. Ends the 32 s wait and the WARN storm during a
    registrar outage. INVITE is unchanged.
  • Late 2xx after CANCEL or an abandoned re-INVITE (#222, #224). A 2xx
    that crosses a CANCEL (or a timed-out re-INVITE) is now ACKed, and BYEd for
    an initial INVITE, instead of leaving the switch dialog to its own timers.
  • Provisionals no longer lost (#219). A 180/183 read just before a final
    is relayed in wire order, so the caller still hears ringback or early media.
    Works around emiago/sipgo#306.
  • Fewer parked goroutines (#226). A finished UDP server transaction no
    longer parks a goroutine for 32 s, which lowers memory under a high call
    rate. The unit file gains commented MemoryMax and GOMEMLIMIT lines.

NAT

  • edge.advertise (#164, #225). For a NAT that translates a port:
    edge.advertise.<udp|tcp|tls|ws|wss> is the public port named in Via,
    Contact, Record-Route and the carrier REGISTER Contact, while the socket
    binds edge.listen. Restart-only, signaling only; the rtp range is never
    offset. Validated against edge.listen and duplicate TCP ports.
  • NAT checklist (#161, #178). Behind NAT, freesbc check, init and run
    print the advertised endpoints and the port forwards the router needs
    (udp/16060->5060), and /api/status gains listener_info.

Observability

  • HOMER over HEP (#127, #128, #214). Optional hep: section (address,
    transport udp|tcp|tls, tls.ca|cert|key, capture_id, node_name,
    redact_auth, default true) mirrors every SIP message on every socket to
    HOMER over HEPv3. Off when absent, restart-only. RTCP reporting from the
    switch side is documented in docs/edge.md (#129, #217).
  • New metrics. freesbc_edge_ack_missed_total{plane},
    freesbc_edge_register_auth_failures_total,
    freesbc_edge_register_unanswered_total,
    freesbc_edge_register_breaker_rejects_total,
    freesbc_tls_cert_disk_differs, freesbc_hep_packets_sent_total,
    freesbc_hep_packets_dropped_total, freesbc_hep_errors_total{kind}, and on
    Linux freesbc_udp_receive_buffer_errors_total and
    freesbc_udp_in_errors_total (#203). New
    freesbc_edge_admission_drops_total reasons: dialogless_not_admitted,
    register_aor_locked, options_not_admitted.
  • Health conditions. ack_missed:<plane> (3 misses in 10 minutes),
    tls_cert_renewed and tls_cert_disk_error (the certificate file is
    re-read at start and every 10 minutes; #209).

Admin

  • Login page with a cookie session and a Log out button; logout audit event
    (#201).

Docs and examples

  • docs/two-node.md: two independent FreeSBC nodes with FreeSWITCH and
    Asterisk examples, per-node gateways and failover, and a rolling restart
    with /api/drain (#150, #220). examples/switch/ is updated to match.
  • docs/troubleshooting.md: calls that connect and then drop (#165, #191).
  • Switch-side settings for the Call-ID host and NOTIFY/MESSAGE bodies (#168,
    #199, thanks @javayou13131).

Fixes

Known limitations

Full list: docs/edge.md, "Known limitations". The limitations of v0.2.0
still apply, except that client legs are now hidden (#166, #167) and
ACK missed warnings are gone (#147). Still open:

  • Single node. All state (calls, dialogs, bindings, bans) is in memory; a
    restart drops everything. Drain the node before a restart.
  • A public call must come from a registered transport address or a carrier
    source. A phone whose NAT mapping changed since its last REGISTER is refused
    until it registers again; so is a silent OPTIONS keep-alive (#218).
  • Call-ID passes through unchanged, and NOTIFY/MESSAGE bodies are proxied byte
    for byte; hide the switch's name there with the switch-side settings in
    docs/edge.md.
  • A late forked 2xx on a re-INVITE and a re-INVITE relay are not covered by the
    provisional replay (#219).
  • Memory still grows with the sustained call rate: since #226 no goroutine is
    parked, but sipgo keeps each UDP transaction's state for 32 s after its final
    response, and an ended dialog stays reachable for about as long (#228); cap
    it with the MemoryMax and GOMEMLIMIT lines in freesbc.service.
  • No transcoding; switches are UDP at literal IP:port; no TURN or full ICE;
    IPv6 is untested; REFER is in-dialog only; no PUBLISH.
  • Non-goals: CDR, clustering, being a registrar. See "Not in scope" in the README.

Getting started

Download, verify and unpack (set os to linux or darwin, arch to amd64
or arm64):

v=v0.3.0 os=linux arch=amd64
curl -LO https://github.com/rasonyang/freesbc/releases/download/$v/freesbc_${v}_${os}_${arch}.tar.gz
curl -LO https://github.com/rasonyang/freesbc/releases/download/$v/SHA256SUMS
sha256sum -c --ignore-missing SHA256SUMS      # macOS: shasum -a 256 -c --ignore-missing SHA256SUMS
tar -xzf freesbc_${v}_${os}_${arch}.tar.gz && cd freesbc_${v}_${os}_${arch}
./freesbc version

Upgrading from v0.2.0: replace the binary (and freesbc.service on linux),
run ./freesbc check -c freesbc.yaml, drain the node with POST /api/drain,
then restart. Only shield.* is hot; edge.advertise and hep need a
restart.

New install: init asks for the switch address, detects the local addresses,
and refuses to overwrite an existing file:

./freesbc init  -c freesbc.yaml
./freesbc check -c freesbc.yaml
./freesbc run   -c freesbc.yaml

Non-interactive: ./freesbc init -c freesbc.yaml --switch 10.77.0.10:5060 --private-ip 10.77.0.2 --public-ip 203.0.113.7 --public-bind 172.31.5.10.
The topology needs a second IP or NIC for private.ip. To run under systemd,
follow "Run as a service" in the README (linux archives include
freesbc.service). Before exposing FreeSBC to the internet, read
docs/design.md (security model) and docs/edge.md (switch-side requirements).

Apache License 2.0.