mirror of
https://github.com/netbirdio/docs.git
synced 2026-10-08 22:59:04 +02:00
docs: add "Overlapping IPs for Resources" use case (#828)
Adds the how-to under the reorganized /use-cases/remote-access group (stacked on the Use Cases reorg). Walks through the decision ladder for two sites sharing one internal IP, ending with the per-site TCP proxy pattern on each routing peer.
This commit is contained in:
@@ -847,6 +847,10 @@ export const docsNavigation = [
|
|||||||
title: 'Exit Nodes',
|
title: 'Exit Nodes',
|
||||||
href: '/use-cases/remote-access/exit-nodes',
|
href: '/use-cases/remote-access/exit-nodes',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Overlapping IPs for Resources',
|
||||||
|
href: '/use-cases/remote-access/overlapping-ips-for-resources',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'Reach Services on the Routing Peer',
|
title: 'Reach Services on the Routing Peer',
|
||||||
href: '/use-cases/remote-access/reach-services-on-the-routing-peer',
|
href: '/use-cases/remote-access/reach-services-on-the-routing-peer',
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import {Note} from "@/components/mdx";
|
||||||
|
|
||||||
|
export const description =
|
||||||
|
'Choose the right fix when two sites share one internal IP address, from installing the NetBird client on the device itself to running a small TCP proxy on each site\'s routing peer to reach both sites at once.'
|
||||||
|
|
||||||
|
# Overlapping IPs for Resources
|
||||||
|
|
||||||
|
Two of your sites use the same internal address. Site A and Site B each run an app at
|
||||||
|
`10.0.0.5:8080`, and each site has its own NetBird [routing peer](/manage/networks/how-routing-peers-work).
|
||||||
|
You give a laptop access to both sites, but it can only ever reach one of them at a time. Restart something and it may silently start
|
||||||
|
talking to the other site instead.
|
||||||
|
|
||||||
|
This happens because an address can point to only one place in a routing table. When two routing
|
||||||
|
peers both advertise `10.0.0.0/24`, NetBird picks one of them and sends all traffic for `10.0.0.5`
|
||||||
|
there. Picking a single winner is the right behavior when several routing peers serve the *same*
|
||||||
|
network, which is what a high-availability setup wants. It is the wrong outcome when the peers lead
|
||||||
|
to *different* places that happen to share an address.
|
||||||
|
|
||||||
|
This guide walks through the fixes in order of preference, then shows the last-resort pattern in
|
||||||
|
full: reaching both sites at once when you cannot install NetBird on the devices themselves.
|
||||||
|
|
||||||
|
## The running example
|
||||||
|
|
||||||
|
- **Site A**, internal app at `10.0.0.5:8080`, reached through routing peer `site-a-router`.
|
||||||
|
- **Site B**, a different app, also at `10.0.0.5:8080`, reached through routing peer `site-b-router`.
|
||||||
|
- A **user** whose laptop needs both apps open at once.
|
||||||
|
|
||||||
|
## Choosing a fix
|
||||||
|
|
||||||
|
Not every case needs this page: if the sites don't actually share an address, add a
|
||||||
|
[resource](/manage/networks) for each and grant access directly. A genuine overlap has four fixes,
|
||||||
|
in order of preference:
|
||||||
|
|
||||||
|
1. **Install the NetBird client on the device itself.** If the app servers can run the client,
|
||||||
|
[install it](/get-started/install), make each server a peer, and grant access with a direct
|
||||||
|
policy. Each device gets its own name and address, so there is no overlap to work around.
|
||||||
|
2. **Pick one site at a time.** If the laptop never needs both sites at once,
|
||||||
|
[route selection](/manage/network-routes/overlapping-routes) (`netbird networks select`) is the
|
||||||
|
supported way to choose which site's route is active. It answers "which site am I on," not
|
||||||
|
"both sites at once."
|
||||||
|
3. **Renumber one site.** If you need broad access to many services across both sites,
|
||||||
|
re-addressing one site so the ranges no longer overlap is the durable fix. Everything else here
|
||||||
|
works around the overlap; renumbering removes it.
|
||||||
|
4. **Run a small TCP proxy on each site's routing peer.** When you cannot touch the devices, need
|
||||||
|
both sites at once, and only need a handful of services, give each site its own name instead.
|
||||||
|
The rest of this guide shows how.
|
||||||
|
|
||||||
|
Two other paths look like they should work and don't, so it is worth naming them:
|
||||||
|
|
||||||
|
- **The built-in [Reverse Proxy](/manage/reverse-proxy)** exists to expose services that have their
|
||||||
|
own distinct address or name, with a public URL and managed TLS. The proxy still reaches its
|
||||||
|
target service over the NetBird mesh, so a service pointed at `10.0.0.5` runs into the same
|
||||||
|
single-route choice and reaches only one site. It cannot tell two identical addresses apart.
|
||||||
|
- **Custom NAT remapping on the routing peer** (a `NETMAP` rule that presents one site under an
|
||||||
|
alias range) is the classic fix for overlapping subnets, but it is not supportable on NetBird: the
|
||||||
|
NetBird client picks one of its two internal firewall backends automatically, and on one of them
|
||||||
|
the remapped traffic is silently dropped. It also forces the access policy to open the real range
|
||||||
|
rather than the alias, leaving a broader rule than you intended.
|
||||||
|
|
||||||
|
## The fix: a name per site
|
||||||
|
|
||||||
|
The proxy pattern stops asking the laptop (or a central proxy) to disambiguate one shared address,
|
||||||
|
and instead gives each site its own name. Run a small TCP proxy **on each site's routing peer**.
|
||||||
|
That peer reaches its own local `10.0.0.5` directly, with no overlap to resolve, and the user
|
||||||
|
connects to two different peer names.
|
||||||
|
|
||||||
|
<Note>
|
||||||
|
This pattern pins each site to one named routing peer. If a site uses multiple routing
|
||||||
|
peers for high availability, failing over to the standby becomes a manual step: see
|
||||||
|
[Limits](#limits) before adopting it.
|
||||||
|
</Note>
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- A [Network](/manage/networks) per site, each with its routing peer already routing that site.
|
||||||
|
- Three NetBird groups: `users` containing the user's laptop, `site-a` containing the peer
|
||||||
|
`site-a-router`, and `site-b` containing `site-b-router`. Policies always reference groups, never
|
||||||
|
individual peers, so each routing peer needs a group of its own.
|
||||||
|
- The internal service address and port at each site (`10.0.0.5:8080` here).
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### 1. Run a proxy on each routing peer
|
||||||
|
|
||||||
|
The proxy is nginx's `stream` module, which forwards plain TCP connections without touching what is
|
||||||
|
inside them. If nginx is new to you, one command installs both nginx and the module (Debian/Ubuntu;
|
||||||
|
on many other distributions the module ships in the main nginx package):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt install nginx libnginx-mod-stream
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, on `site-a-router`, add a `stream` block to `/etc/nginx/nginx.conf` that forwards a port on
|
||||||
|
the peer's own NetBird address to the local service:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/nginx.conf on site-a-router
|
||||||
|
stream {
|
||||||
|
server {
|
||||||
|
listen 100.92.10.4:8080; # this peer's NetBird IP, from `netbird status`
|
||||||
|
proxy_pass 10.0.0.5:8080; # the local app at Site A
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Place the block at the top level of the file, next to the `http` block rather than inside it: nginx
|
||||||
|
rejects a nested one with `"stream" directive is not allowed here`. If you see
|
||||||
|
`unknown directive "stream"` instead, the module is not installed. Check the config and apply it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat on `site-b-router`, using **its** NetBird IP in the `listen` line and its own `10.0.0.5`.
|
||||||
|
|
||||||
|
The `listen` port is yours to choose: it only has to match the policy in step 2 and the URL the
|
||||||
|
user types in step 3. This example keeps the app's own port, so for the user only the hostname
|
||||||
|
changes.
|
||||||
|
|
||||||
|
Because the listener sits on the NetBird address, access over the tunnel is gated by the policies you
|
||||||
|
add next, and nothing is exposed to the internet: no firewall port to open, no inbound rule to manage.
|
||||||
|
|
||||||
|
Two things can break that `listen` address later. After a reboot, nginx can start before the NetBird
|
||||||
|
interface exists and fail to bind: order it after the NetBird service. And a peer that is ever
|
||||||
|
re-registered keeps its name but gets a new NetBird IP, so update the `listen` line to match.
|
||||||
|
|
||||||
|
### 2. Grant least-privilege access, one port per site
|
||||||
|
|
||||||
|
In **Access Control**, add two policies:
|
||||||
|
|
||||||
|
- `users` to `site-a` (the group holding `site-a-router`), protocol TCP, port `8080`.
|
||||||
|
- `users` to `site-b` (the group holding `site-b-router`), protocol TCP, port `8080`.
|
||||||
|
|
||||||
|
These grant access *to the routing peer itself* on a single port, not *through* it to the routed
|
||||||
|
network. (That "to the peer" versus "through the peer" distinction is the one covered on the
|
||||||
|
[Networks](/manage/networks) page.) Scope each policy to the exact proxy port and nothing more.
|
||||||
|
|
||||||
|
### 3. Reach each site by its peer name
|
||||||
|
|
||||||
|
From the laptop, use the two distinct peer names instead of the shared IP. Each peer's full name is
|
||||||
|
shown in the dashboard's Peers list and in `netbird status`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://site-a-router.netbird.cloud:8080/ # Site A
|
||||||
|
curl http://site-b-router.netbird.cloud:8080/ # Site B
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verify
|
||||||
|
|
||||||
|
Run both commands from step 3 again, together, repeatedly, in any order. The `site-a-router` name
|
||||||
|
answers with Site A's app and the `site-b-router` name with Site B's, every time, with no flapping.
|
||||||
|
This holds even while the conflicting `10.0.0.0/24` routes still exist underneath: the two names
|
||||||
|
resolve to two different peers, so the shared `10.0.0.5` behind each is never in conflict on the
|
||||||
|
laptop.
|
||||||
|
|
||||||
|
## Optional: friendlier names with a custom DNS zone
|
||||||
|
|
||||||
|
The fix works by giving each site its own name, so it is worth making those names good ones. With a
|
||||||
|
[custom DNS zone](/manage/dns/custom-zones), users can call the apps by service names instead of
|
||||||
|
remembering which router serves which site.
|
||||||
|
|
||||||
|
In **DNS** → **Zones**, create a zone `corp.internal`, distribute it to the `users` group
|
||||||
|
only, and add one CNAME per site pointing at its routing peer's name:
|
||||||
|
|
||||||
|
| Record | Type | Target |
|
||||||
|
|---|---|---|
|
||||||
|
| `app-a.corp.internal` | CNAME | `site-a-router.netbird.cloud` |
|
||||||
|
| `app-b.corp.internal` | CNAME | `site-b-router.netbird.cloud` |
|
||||||
|
|
||||||
|
The laptop resolves the CNAME through to the peer, so the same test now reads:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://app-a.corp.internal:8080/ # Site A
|
||||||
|
curl http://app-b.corp.internal:8080/ # Site B
|
||||||
|
```
|
||||||
|
|
||||||
|
Prefer CNAME records here. An A record maps a name directly to an IP, so it would pin the routing
|
||||||
|
peer's NetBird IP, which changes if the peer ever re-registers. A CNAME can only target another
|
||||||
|
name, in this case the peer's stable name, so NetBird keeps resolving the current IP for you.
|
||||||
|
|
||||||
|
The name only picks the peer. DNS records carry no port, so the port in the URL is what selects the
|
||||||
|
service on that peer, and it must match the proxy's `listen` line and the step 2 policy. If you
|
||||||
|
enable the zone's search-domain option, users can shorten the names to `app-a` and `app-b`.
|
||||||
|
|
||||||
|
## Limits
|
||||||
|
|
||||||
|
- Works for any TCP service. nginx `stream` also proxies UDP: add `udp` to the `listen` directive.
|
||||||
|
- You run and maintain the proxy on each routing peer. This is a pattern, not a NetBird-managed
|
||||||
|
feature, and it costs one listener and one policy port per service, per site: the work grows with
|
||||||
|
every service you add.
|
||||||
|
- A site's high-availability routing does not extend to the proxy path: each listener lives on one
|
||||||
|
named peer, and nothing moves users to another router automatically. If a site has two routing
|
||||||
|
peers, running the same proxy on both keeps a warm standby (the group-based policy already covers
|
||||||
|
it), but moving users to the standby is a manual step: they switch to its name, or you repoint
|
||||||
|
the DNS record.
|
||||||
|
- TLS passes through untouched, but the laptop now dials the peer's name, so the app's certificate
|
||||||
|
must also cover that name (or the connecting app must be told which name to expect).
|
||||||
|
- The proxy listens on the routing peer's NetBird address. NetBird policies gate tunnel-side traffic,
|
||||||
|
but a host on the peer's own local network can still reach the listener. If that matters, add a
|
||||||
|
local firewall rule on the peer that limits the proxy port to the NetBird interface.
|
||||||
Reference in New Issue
Block a user