0 to carrier in 3 minutes
This walk-through will take you step-by-step through setting up a small and basic but fully-functional (and compliant) US Class 5 telephone switch. Though not an exhaustive exploration of the platform’s features, this walk-though will show you how easy it is to deploy and manage a telephone network with the Cordboard VSS.
If you’d like to follow along with a test STIR/SHAKEN certificate, you can download the whole lab as a ZIP file here or if you’d just like to download the raw config file, you can grab it here.
As you follow along, keep in mind that configurations are cached in memory for 5 minutes. Between any step which applies a config version, you may need to wait for caches to update.
-
Log into the system with
vsscli auth loginand then enter your API key. -
Download or write out the example configuration file and fill in the blanks for your test trunk, cluster, and the SIP realm you want to use.
# ============================================================================# demo-carrier -- "Smallville Telecom", a minimal but real carrier in one file.## NOTE: emergency (911) routing is deliberately out of scope for this demo# config. A production namespace must carry a special-number-profile and a# dedicated emergency path -- see the namespace-config docs.## Lines marked "<-- EDIT" are the only values you need to change.# ============================================================================configuration:sip-realm: your.sip.realm # <-- EDIT: the SIP domain your devices register asedge-proxy: 0.0.0.0 # <-- EDIT: Your provided edge-proxy IPmax-concurrent-calls: "10"# ---------------------------------------------------------------------------# STIR/SHAKEN policy. Outbound calls are signed with full A-level attestation# (the trunk profile below asserts it); inbound Identity headers are verified# and the result is conveyed to the called device as verstat. Actions are# observe-only here -- flip `failed` to block once you trust your peers' certs.# ---------------------------------------------------------------------------stirshaken:spc: "0000" # <-- EDIT: your OCN / service-provider codejurisdiction: USoutbound:sign: truefail-mode: open # never fail a call because signing brokeinbound:verify: trueallow-invalid-ca: true # LAB ONLY -- accepts self-signed demo certs.convey-verstat: trueactions:valid: allowfailed: allow # observe-only; set to block when readyabsent: allow# ---------------------------------------------------------------------------# The PSTN interconnect: A two-way trunk. Inbound calls walk from-pstn to# find the dialed DID's owner; outbound calls have their caller-ID run# through scrub-cid so a malformed/anonymous CID never leaves the network.# Though this config doesn't transit calls, if we did, they would be signed# with a B attestation if coming from this trunk.# ---------------------------------------------------------------------------trunk-profiles:pstn-profile:inbound-translation-context: from-pstnoutbound-translation-context: to-pstncaller-id-mode: providedoutbound-caller-id-context: scrub-cidattestation-mode: Bresign-policy: resignuri-selection: sequentialring-for: 30codecs: [ulaw, alaw]trunks:pstn:trunk-profile: pstn-profileprimary-caller-id: "+17005550000" # EDIT: fixed-CID fallback + trunk identity for 911ips: # <-- EDIT: your provider's signaling source IPs- 1.1.1.1uris: # <-- EDIT: where we send outbound calls- 1.1.1.1# ---------------------------------------------------------------------------# Outbound route. With one trunk this is a one-liner, but it is still worth# having: the route is a named hop in the forensic route record, and adding# a second carrier later is one destination line# ---------------------------------------------------------------------------routes:pstn-out:strategy: sequentialdestinations:- trunk: pstn# ---------------------------------------------------------------------------# Caller-ID scrubbing (applied by pstn-profile's outbound-caller-id-context# on every call dialed out the trunk). We normalize all CIDs to e164,# rejecting any calls which fail normalization. Change to "national" if your# trunk expects calls in NANP format.# ---------------------------------------------------------------------------caller-id-contexts:scrub-cid:"100":match:- regex: "^\\+1[2-9][0-9]{9}$"modify:failure-treatment: deniedoperations:- must-normalize: e164# ---------------------------------------------------------------------------# Destination screening: lowest priority first, first match wins, no match# means deny (fail-closed). Runs on the post-translation (E.164) number. The# demo policy permits any NANP number. Non-CC1 international lands on the# toll-denied treatment -- dial 011-44... to watch the screen refuse it, then# find the denial in the route record.# ---------------------------------------------------------------------------screening-contexts:residential-screen:"100": # any NANP (+1) destinationmatch:- ast-pattern: "_+1NXXXXXXXXX"permit: true"900": # everything else (international) -> toll-deniedmatch:- always: truepermit: false# ---------------------------------------------------------------------------# Two lines, one device each. A peer is a SIP credential (device@sip-realm);# set its password with `vsscli peer password generate <device>@<sip-realm>`# and point any softphone at the edge-proxy IP.# ---------------------------------------------------------------------------line-profiles:standard:screening-context: residential-screenallow-unrestricted-caller-id: false # This will automatically lock caller# ID, and set A attestation.inbound-translation-context: line-ctxmax-ring-time: 25codecs: [ulaw, alaw]lines:"+17005550101": # EDIT and set DID 1 hereline-profile: standardcaller-id: "+17005550101" # EDIT and set DID 2 herecaller-id-name: "Alice Demo"devices: [phone-alice]"+17005550202": # EDIT and set DID 2 hereline-profile: standardcaller-id: "+17005550202" # EDIT and set DID 2 herecaller-id-name: "Bob Demo"devices: [phone-bob]devices:phone-alice: "+17005550101" # EDIT and set DID 1 herephone-bob: "+17005550202" # EDIT and set DID 2 here# ---------------------------------------------------------------------------# Translations -- the routing brain, two contexts:## line-ctx what a registered device dials. Blocks run lowest-priority# first; rewrite-only blocks (no target) apply their modify and# the walk continues with the rewritten number. Every hop shows# up in the route record.# from-pstn inbound termination from the trunk: normalize, deliver our# DIDs, reject everything else (we are nobody's transit path).# ---------------------------------------------------------------------------translations:line-ctx:# --- rewrite-only: 7-digit local dialing -> prepend the home NPA ---"100":match:- ast-pattern: "_XXXXXXX"modify:operations:- add-prefix: "700" # EDIT and set your DID's NPA here# --- rewrite-only: 011 international -> +CC E.164 (the screening# context above will still deny it: normalization and policy are# separate stages, and both are visible in the route record) ---"200":match:- regex: "^011[0-9]+$"modify:failure-treatment: cannot-complete-as-dialedoperations:- must-regex-replace: "^011!+"# --- rewrite-only: canonicalize every NANP form (10d, 1+10d) to E.164 ---"300":match:- regex: "^1?[2-9][0-9]{9}$"modify:failure-treatment: cannot-complete-as-dialedoperations:- must-normalize: e164# --- everything now canonical E.164 leaves via the PSTN route ---"900":match:- regex: "^\\+[0-9]+$"target:resource-type: routeresource-id: pstn-out"999":match:- always: truetarget:resource-type: treatmentresource-id: cannot-complete-as-dialedfrom-pstn:# --- normalize whatever form the carrier delivers ---"100":match:- always: truemodify:failure-treatment: vacant-codeoperations:- must-normalize: e164# --- direct DIDs in our NPA ---"200":match:- ast-pattern: "_+1845XXXXXXX"target:resource-type: lineresource-id: "!dialedNum" # line find the customer's number# --- not our number: reject (no transit) ---"999":match:- always: truetarget:resource-type: treatmentresource-id: unallocated-number# Any final digit analysis or normalize rules before we send the callto-pstn:"100":match:- always: truemodify:operations:- normalize: e164 -
Create the namespace and deploy the configuration onto the server:
vsscli ns create -f smallville.yaml --apply --use -
Generate passwords for one or both of your SIP peers:
vsscli peer password generate phone-alice@<sip-realm>vsscli peer password generate phone-bob@<sip-realm>If you’d like to set the password rather than generating, swap “generate” for “set”.
-
If you want to follow along with STIR/SHAKEN, deploy the example cert with the following command (or swap in your own):
vsscli stirshaken cert set --cert sp.pem --key sp.key --x5u https://example.com/fake.pem
That’s it. Assuming that you’ve set up your IP-authed provider/testing trunk, and set up your peers, you’re done, and if you ever want to add more lines to the system, all you need to to is add another line and device entry. After you’ve made a few calls, you can inspect CDRs with the CDRs command.
vsscli cdrs callsvsscli cdrs get <call-ulid>If you’d like to inspect the exact routing pathways used and the number rewrite chain, use the route-logs command.
vsscli route-logs list --since 5mvsscli route-logs get <call-ulid>Now, we will simulate a bad config which causes an outage:
-
Retrieve the current running configuration:
vsscli config get running --out ./config-test.yaml -
Introduce a small breaking change in the line->pstn translation context:
translations:line-ctx:"100":match:- ast-pattern: "_XXXXXXX"modify:operations:- add-prefix: "700" # EDIT and set your DID's NPA here"200":match:- regex: "^011[0-9]+$"modify:failure-treatment: cannot-complete-as-dialedoperations:- must-regex-replace: "^011!+""300":match:- regex: "^1?[2-9][0-9]{9}$"modify:failure-treatment: cannot-complete-as-dialedoperations:- must-normalize: e164"900":match:- regex: "^\\+[0-9]+$"target:resource-type: treatment # MODIFY HEREresource-id: cannot-complete-as-dialed # AND HERE"999":match:- always: truetarget:resource-type: treatmentresource-id: cannot-complete-as-dialedThis will cause all outbound calls will fail with a fast busy tone.
-
Apply the config change:
vsscli config push ./config-test.yaml --apply
You can now verify that calls are failing by making a call from your registered test device, and if you check the route logs, you’ll be able to spot the issue. Now, we’ll preform a restore by retrieving and rolling forward the last known-good configuration. In an emergency, we could directly apply the prior configuration, but this will cause the latest and running pointers to diverge. This isn’t a problem for the system, but could confuse future operators.
-
Get a listing of config versions:
vsscli config versions -
Retrieve the last known-good config with:
vsscli config get <old-ulid> --out ./last-known-good.yaml -
Store and activate it:
vsscli config push ./last-known-good.yaml --apply
Now, make a test call to confirm functionality was restored. If you prefer the direct-apply that’s possible with a single command. (Force overrides the descendant activation concurrency guard.)
vsscli config apply <old-ulid> --forceAnd that’s it! A full config driven outage resolved cluster-wide in less than a minute!