# Telnyx Compute: Tailscale Subnet Router (Beta) — Full Documentation > Complete page content for Tailscale Subnet Router (Beta) (Compute section) of the Telnyx developer docs (https://developers.telnyx.com). > This file: https://developers.telnyx.com/docs/development/llms/compute-tailscale-subnet-router-beta-llms-full-txt · Root index: https://developers.telnyx.com/llms.txt ## Get Started ### Overview > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router.md The Tailscale Subnet Router is in beta. [Beta Limitations](/docs/network/tailscale-subnet-router/beta-limitations) lists what it does not do yet. A Tailscale Subnet Router connects a [*Network*](/docs/network/overview) to a Tailscale network. Telnyx runs the router and joins it to the user's tailnet. Devices already on the tailnet then reach the *Network*, with nothing new to install. In Tailscale's terms they are behind the router: they use it as their gateway to the *Network*. The diagram zooms into the Tailscale Subnet Router lane of the *Network* diagram. Laptops, phones, and servers on the user's tailnet sit in the enterprise network. Their flows go through the router into the *Network*. From there they reach an application through its binding and a SIM fleet through a Private Wireless Gateway. The router has a tailnet address and the *Network* address `172.27.254.1`. Traffic flows one way, from the devices through the router to the *Network*. ## Pricing Each router is billed monthly. | Resource | Monthly Recurring Charge (USD) | | ----------------------- | ------------------------------ | | Tailscale Subnet Router | 20 | --- ### Quick Start > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/quick-start.md ## Prerequisites - Admin access to the tailnet, to edit its policy file, generate an auth key, and approve routes. - A function project and the `telnyx-edge` CLI, as in the [Edge Compute Quick Start](/docs/edge-compute/quickstart). - A region that offers Tailscale Subnet Routers. `GET /v2/network_coverage?filter[available_services][contains]=tailscale_subnet_router` lists them. A location's `location.code`, such as `ashburn-va`, is the `region_code` to use. ## Create a *Network* and a WireGuard Interface Create a [*Network*](/docs/network/overview) and a WireGuard Interface in the router's region, as in the [WireGuard Interfaces Quick Start](/docs/network/wireguard-interfaces/quick-start). The interface's `server_ip_address` is `172.27.0.1/24` here, so its subnet is `172.27.0.0/24`. Wait until the interface is `provisioned`, about three minutes. ## Prepare the Tailnet In the [tailnet policy file](https://tailscale.com/docs/features/access-control/acls), define the [tag](https://tailscale.com/docs/features/tags) the router carries. Let the tag advertise the interface's subnet with [`autoApprovers`](https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers), and let members reach it with an [access rule](https://tailscale.com/docs/features/subnet-routers#add-access-rules-for-the-advertised-subnet-routes). The auto-approver approves the route as soon as the router advertises it. Without one, approve the route in the admin console later. ```json { "tagOwners": { "tag:telnyx": ["autogroup:admin"] }, "autoApprovers": { "routes": { "172.27.0.0/24": ["tag:telnyx"] } }, "grants": [ { "src": ["autogroup:member"], "dst": ["172.27.0.0/24"], "ip": ["*"] } ] } ``` Then generate an [auth key](https://tailscale.com/docs/features/access-control/auth-keys) under **Settings**, **Keys**. Make it pre-approved and tagged `tag:telnyx`, and not ephemeral. A single-use key enrolls one router, and a reusable key enrolls several. ![The Generate auth key dialog in the Tailscale admin console](/assets/images/tailscale-subnet-router-generate-auth-key.png "Generate auth key") Tailscale [disables key expiry](https://tailscale.com/docs/features/tags#key-expiry) for tagged machines. An untagged router's key expires after 180 days by default, and the router then stops working. **Disable key expiry** in the machine's menu prevents that. ## Create the Router Send [`POST /v2/tailscale_subnet_routers`](/api-reference/tailscale-subnet-routers/create-a-tailscale-subnet-router) with the interface's *Network* and subnet: ```json { "name": "tailnet-ashburn", "network_id": "54236b77-9b41-4e5c-be36-7dc7790ba5a0", "region_code": "ashburn-va", "advertised_routes": ["172.27.0.0/24"], "auth_key": "tskey-auth-" } ``` A router cannot move to another *Network*, region, or tailnet. Confirm `network_id`, `region_code`, and the key's tailnet before sending the request. The response returns `status: provisioning` and the router's `customer_address`. Traffic from devices behind the router enters the *Network* from that address. ```json { "data": { "id": "837c5e7f-0ea6-491b-a0e0-192c3df58cf9", "status": "provisioning", "customer_address": "172.27.254.1", ... } } ``` ## Wait for Enrollment Poll [`GET /v2/tailscale_subnet_routers/{id}`](/api-reference/tailscale-subnet-routers/retrieve-a-tailscale-subnet-router) until `status` is `provisioned`. This takes several minutes. `device_id`, `tailnet_name`, and `tailscale_ips` then describe the router's machine: ```json { "data": { "id": "837c5e7f-0ea6-491b-a0e0-192c3df58cf9", "status": "provisioned", "device_id": "n6EiPn67VE11CNTRL", "tailnet_name": "acme.example", "tailscale_ips": ["100.96.104.127", "fd7a:115c:a1e0::6637:6881"], ... } } ``` The machine's name is `ts` plus the first eight characters of the router's `id`, `ts837c5e7f` here. A `failed` status means enrollment did not complete, for example because the tailnet rejected the key. Retry with [`PATCH /v2/tailscale_subnet_routers/{id}`](/api-reference/tailscale-subnet-routers/update-a-tailscale-subnet-router) and a new key. The router keeps its `customer_address`. ```json { "auth_key": "tskey-auth-" } ``` ## Approve the Route With the auto-approver above, the route is active as soon as the router advertises it. Otherwise open the machine's menu in the admin console and choose **Edit route settings**: ![The machine's menu in the Tailscale admin console, with Edit route settings and Disable key expiry](/assets/images/tailscale-subnet-router-machine-menu.png "Machine menu") Approve `172.27.0.0/24` and save, as in Tailscale's [subnet routers](https://tailscale.com/docs/features/subnet-routers) guide: ![Edit route settings of ts837c5e7f, with 172.27.0.0/24 advertised but not approved](/assets/images/tailscale-subnet-router-approve-route.png "Approve the subnet route") `provisioned` reports enrollment, not approval. The tailnet decides which routes are approved and who reaches them. ## Bind the Function Add a [network binding](/docs/edge-compute/runtime/bindings/network) with the interface's `id` to the function's `func.toml`: ```toml [network.PRIVATE_NET] id = "ecc72c55-6faf-4388-a06b-4c2fc2d4cc40" ``` Ship the function: ```bash telnyx-edge ship ``` The deploy registers the function as a peer of the interface, named `func-`. ## Find the Address `GET /v2/wireguard_peers?filter[wireguard_interface_id]=` lists the interface's peers. The function's peer configuration, [`GET /v2/wireguard_peers/{id}/config`](/api-reference/wireguard-interfaces/retrieve-wireguard-config-template-for-peer), shows its `Address`, `172.27.0.4/32` here. Each deploy gives the function a new address. Look it up again after every deploy. ## Call the Function From Behind the Router A tailnet device is behind the router once it [accepts the route](https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices). Devices on macOS, Windows, iOS, and Android accept it automatically. Linux devices need: ```bash tailscale set --accept-routes ``` Call the function on its address and `PORT`, 8080: ```bash curl http://172.27.0.4:8080/ ``` The function answers with its default response: ```json {"message":"Hello from Telnyx Edge Compute!"} ``` The function sees the request from the router's `customer_address`, `172.27.254.1`. ## Change the Routes [`PATCH /v2/tailscale_subnet_routers/{id}`](/api-reference/tailscale-subnet-routers/update-a-tailscale-subnet-router) with a new `advertised_routes` list replaces the advertisement: ```json { "advertised_routes": ["172.27.0.0/24", "172.27.1.0/24"] } ``` The router returns to `provisioning` for about four minutes. It keeps its machine, with the same `device_id` and `tailscale_ips`. A new route needs approval unless an auto-approver covers it. A request while the router is `provisioning` or `deleting` returns `409`. ## Delete the Router [`DELETE /v2/tailscale_subnet_routers/{id}`](/api-reference/tailscale-subnet-routers/delete-a-tailscale-subnet-router) returns `status: deleting`, and the record disappears within a few minutes. The router logs out of the tailnet. Its machine stays in the admin console, expired, until an admin chooses **Remove** in the machine's menu. Delete the tag, the auto-approver, and the ACL entry when nothing else uses them. Delete a *Network*'s routers before the *Network*, as for its other interfaces. --- ### Beta Limitations > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/beta-limitations.md The Tailscale Subnet Router is in beta, with these limits. ## No Site-to-Site Networking Flows start only on the tailnet. [Site-to-site networking](https://tailscale.com/docs/features/site-to-site), where hosts on the [*Network*](/docs/network/overview) also start flows toward the tailnet, is not available. ## Functions Are Reached by Address The *Network* has no DNS. A function is reached by its address, and each deploy moves it to a new one. The old address stops answering when the deploy completes, and the new one answers within about a minute. Look the address up again after each deploy, as in the [Quick Start](/docs/network/tailscale-subnet-router/quick-start#find-the-address). ## Functions Cannot Be Private A function with a network binding keeps its public URL. It answers there and on the *Network*. ## One Site, One Revision A function with a network binding runs at one site, with one revision on the *Network* at a time. ## Always Running A function with a network binding runs one instance at all times. It does not scale to zero, because requests over the *Network* cannot wake a stopped function. ## Every Open Port Is Reachable The *Network* has no firewall. Every port a function opens is reachable from the *Network*, and from the devices behind the router that the tailnet policy allows. --- ## Concepts ### Routes > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/concepts/routes.md The router advertises subnets of the [*Network*](/docs/network/overview) to the tailnet as [subnet routes](https://tailscale.com/docs/features/subnet-routers). After the subnet routes are approved, devices behind the router reach those subnets. These ranges are reserved by Telnyx and cannot be advertised. The API rejects a route that overlaps any of them: - `0.0.0.0/8`, and with it the default route - `127.0.0.0/8`, loopback - `169.254.0.0/16`, link-local - `224.0.0.0/3`, multicast and reserved - `100.64.0.0/10`, Tailscale's own addresses - `172.27.254.0/24` and `198.18.0.0/20`, the router's own addresses A `PATCH` with a new `advertised_routes` list replaces the advertisement. The router keeps its machine on the tailnet. ## For the Tailnet Admin - Approve each route in the admin console, or let [`autoApprovers`](https://tailscale.com/docs/reference/syntax/policy-file#autoapprovers) approve it. - Grant access to the routes with an [access rule](https://tailscale.com/docs/features/subnet-routers#add-access-rules-for-the-advertised-subnet-routes). Approval puts a route on devices. Access rules decide which traffic passes. The [route injection reference](https://tailscale.com/docs/reference/route-injection) covers both. - Android, iOS, macOS, tvOS, and Windows devices [pick up the routes](https://tailscale.com/docs/features/subnet-routers#use-your-subnet-routes-from-other-devices) automatically. Linux devices need `tailscale set --accept-routes`. --- ### Addresses > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/concepts/addresses.md The router has two addresses: - One on the tailnet, `tailscale_ips`, like any machine. - One on the [*Network*](/docs/network/overview), `customer_address`, from `172.27.254.0/24`. Traffic from devices behind the router enters the *Network* from it, so a function or host sees that address as the source, never the device's own. Each router on a *Network* gets its own `customer_address`, such as `172.27.254.1` and `172.27.254.2`. ## For the Tailnet Admin - `tailscale_ips` are [Tailscale IP addresses](https://tailscale.com/docs/concepts/tailscale-ip-addresses), shown on the router's machine in the admin console. - The router uses [SNAT](https://tailscale.com/docs/features/subnet-routers#disable-snat), Tailscale's default for subnet routers. That is why hosts on the *Network* see `customer_address`. --- ### Lifecycle > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/concepts/lifecycle.md | Status | Meaning | | -------------- | -------------------------------------------------------------------------------------------- | | `provisioning` | Telnyx is creating the router or applying a change. | | `provisioned` | The router is on the tailnet and advertises its routes. The tailnet still approves them. | | `failed` | Enrollment did not complete. The router keeps its addresses, and `PATCH` with a new key retries it. | | `deleting` | Telnyx is removing the router. The record disappears once removal completes. | The auth key is used at enrollment. Telnyx stores it encrypted until then and never returns it. A router stays on the tailnet it enrolled in. To join another tailnet, create a new router. `device_id`, `tailnet_name`, and `tailscale_ips` record the enrollment. The router also appears in the [*Network*](/docs/network/overview)'s interface list. Deleting a router logs its machine out of the tailnet. The machine stays listed, expired, until the admin removes it. ## For the Tailnet Admin - Use a pre-approved, tagged [auth key](https://tailscale.com/docs/features/access-control/auth-keys). A key that is expired, revoked, or already used fails enrollment. - A [tagged](https://tailscale.com/docs/features/tags) machine has [key expiry](https://tailscale.com/docs/features/access-control/key-expiry) disabled by default. An untagged router stops working when its key expires, unless the admin [disables key expiry](https://tailscale.com/docs/features/access-control/key-expiry#disabling-key-expiry). - Remove a deleted router's machine with **Remove** in the machine's menu. --- ## Security ### Tailnet Control > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/tailnet-control.md The router joins with an auth key the user issues and carries the tag the key carries, under the key's owner. The tailnet's policy decides the rest: - An admin or an auto-approver approves its routes. - Access rules decide which users and devices reach the advertised subnets. - Device approval, key expiry, and removal apply as for any machine. The router advertises the [*Network*](/docs/network/overview)'s subnets and nothing else. It is not an exit node, accepts no routes from the tailnet, and uses no DNS from the tailnet. Telnyx holds no access to the tailnet's admin API, so a deleted router's machine stays listed until an admin removes it. `ExitNodeOption` tells whether a machine offers itself as an exit node. `PrimaryRoutes` lists the subnets it serves. A device behind the router reads both for the router's machine: ```bash Device tailscale status --json \ | jq '.Peer[] | select(.HostName == "ts837c5e7f") | {ExitNodeOption, PrimaryRoutes}' ``` The router is not an exit node and serves only `172.27.0.0/24`: ```json Output { "ExitNodeOption": false, "PrimaryRoutes": ["172.27.0.0/24"] } ``` --- ### Auth Key > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/auth-key.md The key is sent in the create request, or in a `PATCH` after a failed enrollment. Telnyx stores it encrypted until enrollment succeeds, then discards it. No response returns it. A router that has enrolled stays on that tailnet. Joining another tailnet means creating a new router. Counting `auth_key` in the list of routers checks this. `$API` holds the API base URL and `$AUTH` the bearer header: ```bash curl -s "$API/tailscale_subnet_routers" -H "$AUTH" | grep -c auth_key ``` The key appears nowhere in the response: ```text Output 0 ``` --- ### Route Approval > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/route-approval.md `provisioned` means the router is enrolled and advertising. A device routes a subnet through the router only after an admin approves the route. `PrimaryRoutes` lists the subnets a machine serves to the device. A device behind the router reads it for the router's machine: ```bash Device tailscale status --json \ | jq '.Peer[] | select(.HostName == "ts837c5e7f") | .PrimaryRoutes' ``` Before an admin approved the route, the router served nothing: ```json Before approval null ``` After approval, it serves `172.27.0.0/24`: ```json After approval ["172.27.0.0/24"] ``` --- ### One Way > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/one-way.md Flows start from devices behind the router. Replies follow the same flow back. The router accepts no routes from the tailnet and installs no route toward the tailnet on the [*Network*](/docs/network/overview). Nothing on the *Network* can start a flow toward a device behind the router. A device behind the router calls a function. The function returns the source address it saw, the router's `customer_address`: ```bash Device curl -s http://172.27.0.4:8080/ | jq -r .caller ``` ```text Output ::ffff:172.27.254.1 ``` A host on the *Network* routes the tailnet's range into its tunnel and tries the device's tailnet address, `100.91.239.33`. The connection fails: ```bash Host nc -z -w 3 100.91.239.33 8787; echo "exit $?" ``` ```text Output exit 1 ``` A capture on the device during the attempt holds no packets from the *Network*: ```bash Device tcpdump -nr tailscale0.pcap | grep -c '172\.27\.' ``` ```text Output 0 ``` ## For the Tailnet Admin - This is Tailscale's default subnet router mode, with [SNAT](https://tailscale.com/docs/features/subnet-routers#disable-snat). --- ### Source Address > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/source-address.md Traffic from devices behind the router enters the [*Network*](/docs/network/overview) from the router's `customer_address`, one address per router from `172.27.254.0/24`. A function or host on the *Network* sees that address as the source, never the device's own address. A device behind the router calls a function at `172.27.0.4` that returns the source address it sees. The function runs on Node.js, which prints IPv4 sources in IPv6-mapped form: ```bash Device curl -s http://172.27.0.4:8080/ | jq -r .caller ``` ```text Output ::ffff:172.27.254.1 ``` It then calls a host at `172.27.0.3` that echoes the source address: ```bash Device curl -s http://172.27.0.3:8787/ ``` ```text Output hello from docs-host, caller 172.27.254.1 ``` Both see `172.27.254.1`, the router's `customer_address`. --- ### Isolation > Source: https://developers.telnyx.com/docs/network/tailscale-subnet-router/security/isolation.md - Each router serves one [*Network*](/docs/network/overview) and is isolated from every other *Network*. - The *Network* gains no internet access from the router. - The router's own connection to Tailscale carries no *Network* traffic. - Traffic between a device behind the router and the router is a WireGuard tunnel. Inside Telnyx it stays on the *Network*. --- ## API Reference (Tailscale Subnet Router (Beta)) ### Tailscale Subnet Routers - [List all Tailscale Subnet Routers](https://developers.telnyx.com/api-reference/tailscale-subnet-routers/list-all-tailscale-subnet-routers.md): Returns a paginated list of the Tailscale Subnet Routers on the account, with support for filtering by Network. - [Create a Tailscale Subnet Router](https://developers.telnyx.com/api-reference/tailscale-subnet-routers/create-a-tailscale-subnet-router.md): Creates a Tailscale Subnet Router on a Network and enrolls it in the user's tailnet with the auth key. The response returns the router with `status` `provision… - [Retrieve a Tailscale Subnet Router](https://developers.telnyx.com/api-reference/tailscale-subnet-routers/retrieve-a-tailscale-subnet-router.md): Returns the details of a single Tailscale Subnet Router by its identifier, including its enrollment state. - [Update a Tailscale Subnet Router](https://developers.telnyx.com/api-reference/tailscale-subnet-routers/update-a-tailscale-subnet-router.md): Renames the router, changes its advertised routes, or retries a failed enrollment. Any change queues a new provisioning run and returns the router with `status… - [Delete a Tailscale Subnet Router](https://developers.telnyx.com/api-reference/tailscale-subnet-routers/delete-a-tailscale-subnet-router.md): Removes the router from the Network and logs it out of the tailnet. The response returns the router with `status` `deleting`. The record disappears once remova…