Skip to main content
Version: 2.0.0

APIServer

The APIServer is an optional component that can be used to describe a VoIP network in terms of Domains, Agents, Trunks, Numbers, and Peers. The data is stored in a PostgreSQL database and is accessed by other components via gRPC.

The APIServer has two implementations: the pgdata for persistent data storage in a PostgreSQL database and the simpledata for storing data in files. The simpledata implementation is intended for testing and small deployments.

The pgdata has several advantages over the simpledata implementation. The first advantage is that the pgdata allows you to manage the data using the CTL and SDK. Additionally, all the data entities support the extended attribute, enabling you to store additional data in the database in JSON format—more on this in later sections.

Data Migration

The pgdata implementation uses Prisma to manage the database schema. Prisma is important because it allows us to migrate the database schema to newer versions of Routr easily.

Configuration Spec

pgdata

The pgdata has the following environment variables available for configuration:

  • DATABASE_URL - The URL of the PostgreSQL database. Example: postgresql://postgres:postgres@localhost:5432/routr
  • BIND_ADDR - The address where the APIServer will listen for gRPC requests. Default to 0.0.0.0:51907
  • EXTERNAL_SERVER_BIND_ADDR - The address where the APIServer will listen for HTTP requests. Default to 0.0.0.0:51908
  • TLS_ON - Enables TLS for the gRPC server. Default to false
  • VERIFY_CLIENT_CERT - Enables client certificate verification. Default to false
  • CACERT - The path to the CA certificate. Default to /etc/routr/certs/ca.crt
  • SERVER_CERT - The path to the server certificate. Default to /etc/routr/certs/server.crt
  • ENFORCE_E164 - Enables E164 validation for Numbers. Default to false
  • ENFORCE_E164_WITH_MOBILE_PREFIX - Enables E164 validation for Numbers with mobile prefixes. Default to false

For details about connecting to the pgdata to manage the data, see the CTL and SDK documentation.

simpledata

The simpledata has the following environment variables available for configuration:

  • PATH_TO_RESOURCES - The path to the directory where the data will be stored. Example: /etc/routr/resources
  • BIND_ADDR - The address where the APIServer will listen for gRPC requests. Default to 0.0.0.0:51907

Both implementations provide Routr with the same protobuf interface, so you can switch between them without changing the other components.

In your resources directory, you will find the following files:

  • agents.yaml - A list of Agents
  • domains.yaml - A list of Domains to which Agents belong
  • trunks.yaml - Represents the list of Trunks
  • numbers.yaml - A list of Numbers to which Trunks belong
  • peers.yaml - A list of Peers that cooperate with Routr (e.g., Asterisk, FreeSWITCH, etc.)
  • credentials.yaml - A list of Credentials for Peers and Agents
  • acl.yaml - The list of ACLs

You may also provide a .json file instead of a .yaml file.

The Agents configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the AgentYes
kindThe kind of resourceYes
refReference to the AgentYes
metadata.nameA friendly name for the AgentYes
spec.usernameThe username of the AgentYes
spec.domainRefReference to the Domain of the AgentYes
spec.credentialsRefReference to the Credentials of the AgentYes
spec.privacyThe privacy settings of the Agent (Private or None)No
spec.enabledThe enabled status of the Agent (reserved for future use)No

An example of an Agents configuration file:

Filename: agents.yaml

- apiVersion: v2beta1
kind: Agent
ref: agent-01
metadata:
name: John Doe
spec:
username: "1001"
domainRef: domain-01
credentialsRef: credentials-01
privacy: Private

In the example above, the Agent agent-01 has the username 1001, belongs to the Domain domain-01, and uses the Credentials credentials-01. Both the Domain and the Credentials are required for the Agent to work.

The Domains configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the DomainYes
kindThe kind of resourceYes
refReference to the DomainYes
metadata.nameA friendly name for the DomainYes
spec.context.domainUriThe domain URIYes
spec.context.egressPoliciesThe list of egress policiesNo
spec.context.egressPolicies[*].ruleThe regular expression to match the numberYes
spec.context.egressPolicies[*].numberRefReference to the NumberYes

An example of a Domains configuration file:

Filename: domains.yaml

- apiVersion: v2beta1
kind: Domain
ref: domain-01
metadata:
name: Local Domain
spec:
context:
domainUri: sip.local
egressPolicies:
- rule: ".*"
numberRef: number-01

In the example above, the Domain domain-01 has the Domain URI sip.local and the egress policy .* that matches all numbers. The egress policy is associated with the Number number-01. The Number number-01 is required for the Domain to work.

Numbers are used to route calls from and to the PSTN. The Numbers configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the NumberYes
kindThe kind of resourceYes
refReference to the NumberYes
metadata.nameA friendly name for the NumberYes
metadata.geoInfo.cityThe city where the Number is locatedNo
metadata.geoInfo.countryThe country where the Number is locatedNo
metadata.geoInfo.countryISOCodeThe ISO code of the countryNo
spec.trunkRefReference to the TrunkYes
spec.location.telUrlThe tel URL of the NumberYes
spec.location.aorLinkThe Address of Record (AOR) of the NumberYes
spec.location.sessionAffinityHeaderThe session affinity header of the NumberNo
spec.location.extraHeadersThe extra headers of the NumberNo
spec.location.extraHeaders[*].nameThe name of the extra headerYes

Tthe ' tel: ' scheme is required when setting the spec.location.telUrl property. For example, tel:+17066041487. Here, tel: indicates that the URI is a telephone number. Please remember that it must match the number sent by your VoIP provider.

Another important property is the spec.location.aorLink. The important thing is that the AOR must be present in the location table in the Location Service. Otherwise, the call will fail. The AOR can be a SIP URI or a backend URI. Typically, you will use SIP URIs to route calls to Agents and backend URIs to route calls to Peers.

The spec.location.sessionAffinityHeader tells the Location Service to use a specific header to identify the session. This property is helpful in combination with "backend" URIs. For example, if you have a conference server and want to ensure that all calls to the same conference room are routed to the same server, you can use the X-Room-Id header to identify the session. The Location Service will use the header's value to identify the session.

An example of a Numbers configuration file:

Filename: numbers.yaml

- apiVersion: v2beta1
kind: Number
ref: number-01
metadata:
name: "(706)604-1487"
geoInfo:
city: Columbus, GA
country: USA
countryISOCode: US
spec:
trunkRef: trunk-01
location:
telUrl: tel:+17066041487
aorLink: sip:conference@sip.local
sessionAffinityHeader: X-Room-Id
extraHeaders:
- name: X-Room-Id
value: jsa-shqm-iyo

Numbers must be associated with a Trunk to work.

The Trunks configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the TrunkYes
kindThe kind of resourceYes
refReference to the TrunkYes
metadata.nameA friendly name for the TrunkYes
metadata.regionReserved for future useNo
spec.inbound.uriThe inbound URI of the TrunkYes
spec.inbound.accessControlListRefReference to the ACL of the TrunkNo
spec.outbound.sendRegisterEnables outbound registrationNo
spec.outbound.credentialsRefReference to the Credentials of the TrunkNo
spec.outbound.urisThe list of outbound URIsNo
spec.outbound.uris[*].uriThe outbound URIYes
spec.outbound.uris[*].priorityReserved for future useNA
spec.outbound.uris[*].weightReserved for future useNA
spec.outbound.uris[*].enabledReserved for future useNA
spec.outbound.uris[*].transportThe transport of the outbound URINo
spec.outbound.uris[*].userThe user of the outbound URINo
spec.outbound.uris[*].hostThe host of the outbound URINo
spec.outbound.uris[*].portThe port of the outbound URINo

An example of a Trunks configuration file:

Filename: trunks.yaml

- apiVersion: v2beta1
kind: Trunk
ref: trunk-01
metadata:
name: Test Trunk
region: us-east1
spec:
inbound:
uri: trunk01.acme.com
accessControlListRef: acl-01
outbound:
sendRegister: true
credentialsRef: credentials-04
uris:
- uri:
user: pbx-1
host: sip.provider.net
port: 7060
transport: udp
enabled: true

In the spec.inbound section, the uri property helps Routr to identify the Trunk. Calls from the PSTN using the uri will be routed to the Trunk. The accessControlListRef property restricts the access to the Trunk. The accessControlListRef is optional.

In the spec.outbound section, the sendRegister property enables registration outbound registrations while the credentialsRef property for authentication with your trunk provider.

In Routr, a Peer is a SIP entity that belongs to the same network as Routr. For example, Asterisk, FreeSWITCH, etc. The Peers configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the PeerYes
kindThe kind of resourceYes
refReference to the PeerYes
metadata.nameA friendly name for the PeerYes
spec.aorThe Address of Record (AOR) of the PeerYes
spec.usernameThe username of the PeerYes
spec.credentialsRefReference to the Credentials of the PeerYes
spec.loadBalancing.withSessionAffinityEnables session affinityNo
spec.loadBalancing.algorithmThe load balancing algorithmNo

The spec.loadBalancing.withSessionAffinity property enables session affinity, and the spec.loadBalancing.algorithm property sets the load-balancing algorithm. The available algorithms are least-sessions and round-robin.

Remember that for the least-sessions algorithm to work, your SIP backend includes the special header X-Session-Count with the number of active sessions as part of the registration request.

We are evaluating more flexible options to provide the session count, but as of now, you also need to make sure that registration requests happen frequently enough to keep the session count up to date.

An example of a Peers configuration file:

Filename: peers.yaml

- apiVersion: v2beta1
kind: Peer
ref: peer-01
metadata:
name: Asterisk (Media Server)
spec:
aor: sip:conference@sip.local
username: asterisk
credentialsRef: credentials-03
loadBalancing:
withSessionAffinity: true
algorithm: least-sessions

Credentials in Routr help Agents and Peers authenticate with Routr.

The Credentials configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the CredentialsYes
kindThe kind of resourceYes
refReference to the CredentialsYes
metadata.nameA friendly name for the CredentialsYes
spec.credentials.usernameThe username of the CredentialsYes
spec.credentials.passwordThe password of the CredentialsYes

An example of a Credentials configuration file:

Filename: credentials.yaml

- apiVersion: v2beta1
kind: Credentials
ref: credentials-01
metadata:
name: Agent Credentials
spec:
credentials:
username: "1001"
password: "1234"

Routr also supports ACLs. ACLs are used to restrict access to Trunks and Domains.

The ACLs configuration file has the following structure:

PropertyDescriptionRequired
apiVersionThe API version of the ACLYes
kindThe kind of resourceYes
refReference to the ACLYes
metadata.nameA friendly name for the ACLYes
spec.accessControlList.denyThe list of deny rulesNo
spec.accessControlList.deny[*]The deny ruleYes
spec.accessControlList.allowThe list of allow rulesNo
spec.accessControlList.allow[*]The allow ruleYes

An example of an ACLs configuration file:

Filename: acl.yaml

- apiVersion: v2beta1
kind: AccessControlList
ref: acl-01
metadata:
name: Europe ACL
spec:
accessControlList:
deny:
- 0.0.0.0/1
allow:
- 192.168.1.3/31
- 127.0.0.1/8
- 10.111.221.22/31

Communication and Protobuf Spec

Components communicate with the APIServer using gRPC. That includes SDKs, Command-line tools, Processors, Middleware, etc.

The following protobuf defines the Agent service interface:

syntax = "proto3";

package fonoster.routr.connect.agents.v2beta1;

// The Agents service definition
service Agents {
// Creates a new Agent
rpc Create (CreateAgentRequest) returns (Agent) {}
// Updates an existing Agent
rpc Update (UpdateAgentRequest) returns (Agent) {}
// Gets an existing Agent
rpc Get (GetAgentRequest) returns (Agent) {}
// Deletes an existing Agent
rpc Delete (DeleteAgentRequest) returns (.google.protobuf.Empty) {}
// Lists all Agents
rpc List (ListAgentsRequest) returns (ListAgentsResponse) {}
// Find Agents by field name and value
rpc FindBy (FindByRequest) returns (FindByResponse) {}
}

enum Privacy {
PRIVATE = 0;
NONE = 1;
}

// The message for the Agent resource
message Agent {
// The API version of the Agent
string api_version = 1;
// The unique identifier of the Agent
string ref = 2;
// The name of the Agent
string name = 3;
// The username of the Agent
string username = 4;
// The password of the Agent
Privacy privacy = 5;
// The enabled status of the Agent
bool enabled = 6;
// The created_at timestamp of the Agent
int32 created_at = 7;
// The updated_at timestamp of the Agent
int32 updated_at = 8;
// The domain of the Agent
fonoster.routr.connect.domains.v2beta1.Domain domain = 9;
// The credentials of the Agent
fonoster.routr.connect.credentials.v2beta1.Credentials credentials = 10;
// The extended attributes of the Agent
.google.protobuf.Struct extended = 11;
}

// The request message for the Agents.Create method
message CreateAgentRequest {
// The name of the Agent
string name = 1;
// The username of the Agent
string username = 2;
// The password of the Agent
Privacy privacy = 3;
// The enabled status of the Agent
bool enabled = 4;
// Reference to the Domain of the Agent
string domain_ref = 5;
// Reference to the Credentials of the Agent
string credentials_ref = 6;
// The extended attributes of the Agent
.google.protobuf.Struct extended = 7;
}

// The request message for the Agents.Update method
message UpdateAgentRequest {
// The unique identifier of the Agent
string ref = 1;
// The name of the Agent
string name = 2;
// The privacy settings of the Agent
Privacy privacy = 3;
// The enabled status of the Agent
bool enabled = 4;
// Reference to the Domain of the Agent
string domain_ref = 5;
// Reference to the Credentials of the Agent
string credentials_ref = 6;
// The extended attributes of the Agent
.google.protobuf.Struct extended = 7;
}

// The request message for the Agents.Get method
message GetAgentRequest {
// The unique identifier of the Agent
string ref = 1;
}

// The request message for the Agents.Delete method
message DeleteAgentRequest {
// The unique identifier of the Agent
string ref = 1;
}

// The request message for the Agents.FindBy method
message FindByRequest {
// The field name to search
string field_name = 1;
// The value to search
string field_value = 2;
}

// The response message for the Agents.FindBy method
message FindByResponse {
// The list of Agents
repeated Agent items = 1;
}

// The request message for the Agents.List method
message ListAgentsRequest {
// The maximum number of items in the list
int32 page_size = 1;

// The next_page_token value returned from the previous request, if any
string page_token = 2;
}

// The response message for the Agents.List method
message ListAgentsResponse {
// List of Agents
repeated Agent items = 1;

// Token to retrieve the next page of results or empty if there are no more results in the list
string next_page_token = 2;
}

To see the complete protobuf spec, please visit the protobuf directory.

Launching the APIServer

Both implementations are available as Docker images from Docker Hub as fonoster/routr-pgdata and fonoster/routr-simpledata.

pgdata

To launch the pgdata implementation with Docker, you can use the following command:

docker run -it \
-e DATABASE_URL="postgresql://postgres:postgres@localhost:5432/routr" \
-p 51907:51907 \
-p 51908:51908 \
fonoster/routr-pgdata

The port 51907 is used for internal communication, and the port 51908 is used for external communication. The CTL and SDK use the external port to manage the data.

simpledata

To launch the simpledata implementation with Docker, you can use the following command:

docker run -it \
-v /path/to/resources:/etc/routr/resources \
-p 51907:51907 \
fonoster/routr-simpledata

Quick Test with gRPCurl

One easy way to interact with the APIServer for testing and development is to use gRPCurl. The following example shows how to send a request to the Agents service within the APIServer:

grpcurl -plaintext \
-import-path /path/to/protos \
-proto agents.proto -d '{...}' \
localhost:51907 \
fonoster.routr.connect.agents.v2beta1.Agents/Get

Please remember that the simpledata implementation does not accept write operations.