Compare commits

...

10 Commits

Author SHA1 Message Date
riccardom
63e7a018c1 Merge branch 'main' into task/align_protobuff_toolset
# Conflicts:
#	shared/management/proto/proxy_service.pb.go
#	shared/management/proto/proxy_service_grpc.pb.go
2026-05-21 10:37:26 +02:00
riccardom
274a184135 Detect untracked files in proto drift gate
CodeRabbit: git diff --exit-code ignores untracked .pb.go files, so
adding a new .proto without committing its generated output would slip
through the gate. Use git status --porcelain --untracked-files=all to
catch both modified and untracked drift. Also align [[ ]] usage in
protoc version preflight.
2026-05-21 10:11:53 +02:00
Viktor Liu
37052fd5bc [client] Fix nil channel panic in external chain monitor stop (#6224) 2026-05-20 18:46:51 +02:00
Pascal Fischer
454ff66518 [management] scope network router update call (#6222) 2026-05-20 18:24:00 +02:00
Pascal Fischer
6137a1fcc5 [proxy] concurrent proxy snapshot apply (#6207) 2026-05-20 18:21:22 +02:00
Viktor Liu
4955c345d5 Clean up README header, key features table, and self-hosted quickstart (#6178) 2026-05-20 16:25:56 +02:00
riccardom
0920fa1fa9 Address SonarCloud quality gate
- Use [[ ]] for conditional tests in generate.sh scripts
- Pin arduino/setup-protoc to full commit SHA
2026-05-19 09:04:40 +02:00
riccardom
0151cbd3e3 GH action gate on generated PB output 2026-05-18 18:08:20 +02:00
riccardom
46ec42b9a0 Regenerated pb go files 2026-05-18 18:08:20 +02:00
riccardom
e1b686420a Aligns go protoc toolset 2026-05-18 18:08:20 +02:00
37 changed files with 4610 additions and 4151 deletions

View File

@@ -3,60 +3,74 @@ name: Proto Version Check
on: on:
pull_request: pull_request:
paths: paths:
- "**/*.proto"
- "**/*.pb.go" - "**/*.pb.go"
- "**/generate.sh"
- "proto-tools.env"
- ".github/workflows/proto-version-check.yml"
jobs: jobs:
check-proto-versions: regenerate-and-diff:
name: Regenerate proto and verify no drift
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check for proto tool version changes - name: Checkout
uses: actions/github-script@v7 uses: actions/checkout@v4
- name: Load pinned proto toolchain versions
run: |
# shellcheck source=/dev/null
. ./proto-tools.env
{
echo "PROTOC_VERSION=${PROTOC_VERSION}"
echo "PROTOC_GEN_GO_VERSION=${PROTOC_GEN_GO_VERSION}"
echo "PROTOC_GEN_GO_GRPC_VERSION=${PROTOC_GEN_GO_GRPC_VERSION}"
} >> "$GITHUB_ENV"
- name: Setup Go
uses: actions/setup-go@v5
with: with:
script: | go-version-file: go.mod
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
per_page: 100,
});
const pbFiles = files.filter(f => f.filename.endsWith('.pb.go')); - name: Setup protoc
const missingPatch = pbFiles.filter(f => !f.patch).map(f => f.filename); uses: arduino/setup-protoc@f4d5893b897028ff5739576ea0409746887fa536 # v3.0.0
if (missingPatch.length > 0) { with:
core.setFailed( version: ${{ env.PROTOC_VERSION }}
`Cannot inspect patch data for:\n` + repo-token: ${{ secrets.GITHUB_TOKEN }}
missingPatch.map(f => `- ${f}`).join('\n') +
`\nThis can happen with very large PRs. Verify proto versions manually.`
);
return;
}
const versionPattern = /^[+-]\s*\/\/\s+protoc(?:-gen-go)?\s+v[\d.]+/;
const violations = [];
for (const file of pbFiles) { - name: Install protoc plugins
const changed = file.patch run: |
.split('\n') go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
.filter(line => versionPattern.test(line)); go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
if (changed.length > 0) { echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
violations.push({
file: file.filename,
lines: changed,
});
}
}
if (violations.length > 0) { - name: Verify protoc version matches pin
const details = violations.map(v => run: |
`${v.file}:\n${v.lines.map(l => ' ' + l).join('\n')}` actual=$(protoc --version | awk '{print $2}')
).join('\n\n'); if [[ "$actual" != "$PROTOC_VERSION" ]]; then
echo "::error::protoc $actual does not match pinned $PROTOC_VERSION"
exit 1
fi
core.setFailed( - name: Regenerate all proto bindings
`Proto version strings changed in generated files.\n` + run: |
`This usually means the wrong protoc or protoc-gen-go version was used.\n` + set -euo pipefail
`Regenerate with the matching tool versions.\n\n` + for script in \
details client/proto/generate.sh \
); shared/signal/proto/generate.sh \
return; shared/management/proto/generate.sh \
} flow/proto/generate.sh \
encryption/testprotos/generate.sh; do
echo "::group::$script"
bash "$script"
echo "::endgroup::"
done
console.log('No proto version string changes detected'); - name: Fail if regeneration changed any tracked or untracked file
run: |
if [[ -n "$(git status --porcelain --untracked-files=all)" ]]; then
echo "::error::Generated proto files drift from .proto sources or pinned tool versions."
echo "Run the generate.sh scripts locally with the toolchain in proto-tools.env and commit the result."
git status --short
exit 1
fi

153
README.md
View File

@@ -1,147 +1,134 @@
<div align="center"> <div align="center">
<br/> <p align="center">
<br/> <img width="234" src="docs/media/logo-full.png" alt="NetBird logo"/>
<p align="center"> </p>
<img width="234" src="docs/media/logo-full.png"/> <p align="center">
</p> <a href="https://sonarcloud.io/dashboard?id=netbirdio_netbird">
<p> <img src="https://sonarcloud.io/api/project_badges/measure?project=netbirdio_netbird&metric=alert_status" alt="SonarCloud alert status"/>
<a href="https://img.shields.io/badge/license-BSD--3-blue)"> </a>
<img src="https://sonarcloud.io/api/project_badges/measure?project=netbirdio_netbird&metric=alert_status" /> <a href="https://github.com/netbirdio/netbird/blob/main/LICENSE">
</a> <img src="https://img.shields.io/badge/license-BSD--3-blue" alt="BSD-3 License"/>
<a href="https://github.com/netbirdio/netbird/blob/main/LICENSE"> </a>
<img src="https://img.shields.io/badge/license-BSD--3-blue" />
</a>
<br>
<a href="https://docs.netbird.io/slack-url"> <a href="https://docs.netbird.io/slack-url">
<img src="https://img.shields.io/badge/slack-@netbird-red.svg?logo=slack"/> <img src="https://img.shields.io/badge/slack-@netbird-red.svg?logo=slack" alt="NetBird Slack"/>
</a> </a>
<a href="https://forum.netbird.io"> <a href="https://forum.netbird.io">
<img src="https://img.shields.io/badge/community forum-@netbird-red.svg?logo=discourse"/> <img src="https://img.shields.io/badge/community%20forum-@netbird-red.svg?logo=discourse" alt="Community forum"/>
</a> </a>
<br>
<a href="https://gurubase.io/g/netbird"> <a href="https://gurubase.io/g/netbird">
<img src="https://img.shields.io/badge/Gurubase-Ask%20NetBird%20Guru-006BFF"/> <img src="https://img.shields.io/badge/Gurubase-Ask%20NetBird%20Guru-006BFF" alt="Gurubase: Ask NetBird Guru"/>
</a> </a>
</p> </p>
</div> </div>
<p align="center"> <p align="center">
<strong> <strong>
Start using NetBird at <a href="https://netbird.io/pricing">netbird.io</a> Start using NetBird at <a href="https://netbird.io/pricing">netbird.io</a>
<br/>
See <a href="https://netbird.io/docs/">Documentation</a>
<br/>
Join our <a href="https://docs.netbird.io/slack-url">Slack channel</a> or our <a href="https://forum.netbird.io">Community forum</a>
</strong>
<br/> <br/>
See <a href="https://netbird.io/docs/">Documentation</a>
<br/> <br/>
Join our <a href="https://docs.netbird.io/slack-url">Slack channel</a> or our <a href="https://forum.netbird.io">Community forum</a> <strong>
<br/> 🚀 <a href="https://careers.netbird.io">We are hiring! Join us at careers.netbird.io</a>
</strong>
</strong>
<br>
<strong>
🚀 <a href="https://careers.netbird.io">We are hiring! Join us at careers.netbird.io</a>
</strong>
<br>
<br>
<a href="https://registry.terraform.io/providers/netbirdio/netbird/latest">
New: NetBird terraform provider
</a>
</p> </p>
<br>
**NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.** **NetBird combines a configuration-free peer-to-peer private network and a centralized access control system in a single platform, making it easy to create secure private networks for your organization or home.**
**Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth. **Connect.** NetBird creates a WireGuard-based overlay network that automatically connects your machines over an encrypted tunnel, leaving behind the hassle of opening ports, complex firewall rules, VPN gateways, and so forth.
**Secure.** NetBird enables secure remote access by applying granular access policies while allowing you to manage them intuitively from a single place. Works universally on any infrastructure. **Secure.** NetBird enables secure remote access by applying granular access policies while allowing you to manage them intuitively from a single place. Works universally on any infrastructure.
### Open Source Network Security in a Single Platform
https://github.com/user-attachments/assets/10cec749-bb56-4ab3-97af-4e38850108d2 https://github.com/user-attachments/assets/10cec749-bb56-4ab3-97af-4e38850108d2
### Self-Host NetBird (Video) ### Self-host NetBird (video)
[![Watch the video](https://img.youtube.com/vi/bZAgpT6nzaQ/0.jpg)](https://youtu.be/bZAgpT6nzaQ) [![Watch the video](https://img.youtube.com/vi/bZAgpT6nzaQ/0.jpg)](https://youtu.be/bZAgpT6nzaQ)
### Key features ### Key features
| Connectivity | Management | Security | Automation| Platforms | | Connectivity | Management | Security | Automation | Platforms |
|----|----|----|----|----| |---|---|---|---|---|
| <ul><li>- \[x] Kernel WireGuard</ul></li> | <ul><li>- \[x] [Admin Web UI](https://github.com/netbirdio/dashboard)</ul></li> | <ul><li>- \[x] [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login)</ul></li> | <ul><li>- \[x] [Public API](https://docs.netbird.io/api)</ul></li> | <ul><li>- \[x] Linux</ul></li> | | ✓ [Kernel WireGuard](https://docs.netbird.io/about-netbird/why-wireguard-with-netbird) | ✓ [Admin Web UI](https://github.com/netbirdio/dashboard) | ✓ [SSO & MFA support](https://docs.netbird.io/how-to/installation#running-net-bird-with-sso-login) | ✓ [Public API](https://docs.netbird.io/api) | ✓ [Linux](https://docs.netbird.io/get-started/install/linux) |
| <ul><li>- \[x] Peer-to-peer connections</ul></li> | <ul><li>- \[x] Auto peer discovery and configuration</ui></li> | <ul><li>- \[x] [Access control - groups & rules](https://docs.netbird.io/how-to/manage-network-access)</ui></li> | <ul><li>- \[x] [Setup keys for bulk network provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys)</ui></li> | <ul><li>- \[x] Mac</ui></li> | | ✓ [Peer-to-peer connections](https://docs.netbird.io/about-netbird/how-netbird-works) | ✓ Auto peer discovery and configuration | ✓ [Access control: groups & rules](https://docs.netbird.io/how-to/manage-network-access) | ✓ [Setup keys for bulk provisioning](https://docs.netbird.io/how-to/register-machines-using-setup-keys) | ✓ [macOS](https://docs.netbird.io/get-started/install/macos) |
| <ul><li>- \[x] Connection relay fallback</ui></li> | <ul><li>- \[x] [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers)</ui></li> | <ul><li>- \[x] [Activity logging](https://docs.netbird.io/how-to/audit-events-logging)</ui></li> | <ul><li>- \[x] [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart)</ui></li> | <ul><li>- \[x] Windows</ui></li> | | Connection relay fallback | ✓ [IdP integrations](https://docs.netbird.io/selfhosted/identity-providers) | ✓ [Activity logging](https://docs.netbird.io/how-to/audit-events-logging) | ✓ [Self-hosting quickstart script](https://docs.netbird.io/selfhosted/selfhosted-quickstart) | ✓ [Windows](https://docs.netbird.io/get-started/install/windows) |
| <ul><li>- \[x] [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks)</ui></li> | <ul><li>- \[x] [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network)</ui></li> | <ul><li>- \[x] [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks)</ui></li> | <ul><li>- \[x] IdP groups sync with JWT</ui></li> | <ul><li>- \[x] Android</ui></li> | | [Routes to external networks](https://docs.netbird.io/how-to/routing-traffic-to-private-networks) | ✓ [Private DNS](https://docs.netbird.io/how-to/manage-dns-in-your-network) | ✓ [Traffic events](https://docs.netbird.io/manage/activity/traffic-events-logging) | ✓ [IdP groups sync with JWT](https://docs.netbird.io/manage/team/idp-sync) | ✓ [Android](https://docs.netbird.io/get-started/install/android) |
| <ul><li>- \[x] NAT traversal with BPF</ui></li> | <ul><li>- \[x] [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network)</ui></li> | <ul><li>- \[x] Peer-to-peer encryption</ui></li> || <ul><li>- \[x] iOS</ui></li> | | ✓ [Domain-based DNS routes](https://docs.netbird.io/manage/dns/dns-aliases-for-routed-networks) | ✓ [Custom DNS zones](https://docs.netbird.io/manage/dns/custom-zones) | ✓ [Device posture checks](https://docs.netbird.io/how-to/manage-posture-checks) | ✓ [Terraform provider](https://registry.terraform.io/providers/netbirdio/netbird/latest) | ✓ [Android TV](https://docs.netbird.io/get-started/install/android-tv) |
||| <ul><li>- \[x] [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn)</ui></li> || <ul><li>- \[x] OpenWRT</ui></li> | | ✓ [Exit nodes](https://docs.netbird.io/manage/network-routes/use-cases/exit-nodes) | ✓ [Multiuser support](https://docs.netbird.io/how-to/add-users-to-your-network) | ✓ Peer-to-peer encryption | ✓ [Ansible collection](https://github.com/netbirdio/ansible-netbird) | ✓ [iOS](https://docs.netbird.io/get-started/install/ios) |
||| <ul><li>- \[x] [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication)</ui></li> || <ul><li>- \[x] [Serverless](https://docs.netbird.io/how-to/netbird-on-faas)</ui></li> | | ✓ [IPv6 dual-stack overlay](https://docs.netbird.io/manage/settings/ipv6) | ✓ [Multi-account profile switching](https://docs.netbird.io/client/profiles) | ✓ [SSH with central access policies](https://docs.netbird.io/manage/peers/ssh) | | ✓ [Apple TV](https://docs.netbird.io/get-started/install/tvos) |
||||| <ul><li>- \[x] Docker</ui></li> | | ✓ [Browser SSH & RDP](https://docs.netbird.io/manage/peers/browser-client) | | ✓ [Quantum-resistance with Rosenpass](https://netbird.io/knowledge-hub/the-first-quantum-resistant-mesh-vpn) | | ✓ FreeBSD |
| ✓ [Reverse proxy with auto-TLS](https://docs.netbird.io/manage/reverse-proxy) | | ✓ [Periodic re-authentication](https://docs.netbird.io/how-to/enforce-periodic-user-authentication) | | ✓ [pfSense](https://docs.netbird.io/get-started/install/pfsense) |
| | | | | ✓ [OPNsense](https://docs.netbird.io/get-started/install/opnsense) |
| | | | | ✓ [MikroTik RouterOS](https://docs.netbird.io/use-cases/homelab/client-on-mikrotik-router) |
| | | | | ✓ OpenWRT |
| | | | | ✓ [Synology](https://docs.netbird.io/get-started/install/synology) |
| | | | | ✓ [TrueNAS](https://docs.netbird.io/get-started/install/truenas) |
| | | | | ✓ [Proxmox](https://docs.netbird.io/get-started/install/proxmox-ve) |
| | | | | ✓ [Raspberry Pi](https://docs.netbird.io/get-started/install/raspberrypi) |
| | | | | ✓ [Serverless](https://docs.netbird.io/how-to/netbird-on-faas) |
| | | | | ✓ [Container](https://docs.netbird.io/get-started/install/docker) |
### Quickstart with NetBird Cloud ### Quickstart with NetBird Cloud
- Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install) - Download and install NetBird at [https://app.netbird.io/install](https://app.netbird.io/install).
- Follow the steps to sign-up with Google, Microsoft, GitHub or your email address. - Follow the steps to sign up with Google, Microsoft, GitHub or your email address.
- Check NetBird [admin UI](https://app.netbird.io/). - Check the NetBird [admin UI](https://app.netbird.io/).
- Add more machines.
### Quickstart with self-hosted NetBird ### Quickstart with self-hosted NetBird
> This is the quickest way to try self-hosted NetBird. It should take around 5 minutes to get started if you already have a public domain and a VM. This is the quickest way to try self-hosted NetBird. It should take around 5 minutes to get started if you already have a public domain and a VM. Follow the [Advanced guide with a custom identity provider](https://docs.netbird.io/selfhosted/selfhosted-guide#advanced-guide-with-a-custom-identity-provider) for installations with different IdPs.
Follow the [Advanced guide with a custom identity provider](https://docs.netbird.io/selfhosted/selfhosted-guide#advanced-guide-with-a-custom-identity-provider) for installations with different IDPs.
**Infrastructure requirements:** **Infrastructure requirements:**
- A Linux VM with at least **1CPU** and **2GB** of memory. - A Linux VM with at least **1 CPU** and **2 GB** of memory.
- The VM should be publicly accessible on TCP ports **80** and **443** and UDP port: **3478**. - The VM should be publicly accessible on TCP ports **80** and **443** and UDP port **3478**.
- **Public domain** name pointing to the VM. - A **public domain** name pointing to the VM.
**Software requirements:** **Software requirements:**
- Docker installed on the VM with the docker-compose plugin ([Docker installation guide](https://docs.docker.com/engine/install/)) or docker with docker-compose in version 2 or higher. - Docker with the Compose plugin (Compose v2 or higher). See the [Docker installation guide](https://docs.docker.com/engine/install/).
- [jq](https://jqlang.github.io/jq/) installed. In most distributions
Usually available in the official repositories and can be installed with `sudo apt install jq` or `sudo yum install jq`
- [curl](https://curl.se/) installed.
Usually available in the official repositories and can be installed with `sudo apt install curl` or `sudo yum install curl`
**Steps** **Steps**
- Download and run the installation script: - Download and run the installation script:
```bash ```bash
export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bash export NETBIRD_DOMAIN=netbird.example.com; curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bash
``` ```
- Once finished, you can manage the resources via `docker-compose`
### A bit on NetBird internals ### A bit on NetBird internals
- Every machine in the network runs [NetBird Agent (or Client)](client/) that manages WireGuard. - Every machine in the network runs the [NetBird agent](client/), which manages WireGuard.
- Every agent connects to [Management Service](management/) that holds network state, manages peer IPs, and distributes network updates to agents (peers). - Every agent connects to the [Management Service](management/), which holds network state, manages peer IPs, and distributes updates to agents.
- NetBird agent uses WebRTC ICE implemented in [pion/ice library](https://github.com/pion/ice) to discover connection candidates when establishing a peer-to-peer connection between machines. - Agents use ICE (via [pion/ice](https://github.com/pion/ice)) to discover connection candidates for peer-to-peer connections.
- Connection candidates are discovered with the help of [STUN](https://en.wikipedia.org/wiki/STUN) servers. - Candidates are discovered with the help of [STUN](https://en.wikipedia.org/wiki/STUN) servers.
- Agents negotiate a connection through [Signal Service](signal/) passing p2p encrypted messages with candidates. - Agents negotiate a connection through the [Signal Service](signal/), exchanging end-to-end encrypted messages with candidates.
- Sometimes the NAT traversal is unsuccessful due to strict NATs (e.g. mobile carrier-grade NAT) and a p2p connection isn't possible. When this occurs the system falls back to a relay server called [TURN](https://en.wikipedia.org/wiki/Traversal_Using_Relays_around_NAT), and a secure WireGuard tunnel is established via the TURN server. - When NAT traversal fails (e.g. mobile carrier-grade NAT) and a direct p2p connection isn't possible, the system falls back to a [Relay Service](relay/) and a secure WireGuard tunnel is established through it.
[Coturn](https://github.com/coturn/coturn) is the one that has been successfully used for STUN and TURN in NetBird setups.
<p float="left" align="middle"> <p float="left" align="middle">
<img src="https://docs.netbird.io/docs-static/img/about-netbird/high-level-dia.png" width="700"/> <img src="https://docs.netbird.io/docs-static/img/about-netbird/high-level-dia.png" width="700" alt="NetBird high-level architecture diagram"/>
</p> </p>
See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details. See a complete [architecture overview](https://docs.netbird.io/about-netbird/how-netbird-works#architecture) for details.
### Community projects ### Community projects
- [NetBird installer script](https://github.com/physk/netbird-installer) - [NetBird installer script](https://github.com/physk/netbird-installer)
- [NetBird ansible collection by Dominion Solutions](https://galaxy.ansible.com/ui/repo/published/dominion_solutions/netbird/) - [netbird-tui](https://github.com/n0pashkov/netbird-tui) - terminal UI for managing NetBird peers, routes, and settings
- [netbird-tui](https://github.com/n0pashkov/netbird-tui) — terminal UI for managing NetBird peers, routes, and settings - [caddy-netbird](https://github.com/lixmal/caddy-netbird) - Caddy plugin that embeds a NetBird client for proxying HTTP and TCP/UDP traffic through NetBird networks
**Note**: The `main` branch may be in an *unstable or even broken state* during development. **Note**: The `main` branch may be in an *unstable or even broken state* during development.
For stable versions, see [releases](https://github.com/netbirdio/netbird/releases). For stable versions, see [releases](https://github.com/netbirdio/netbird/releases).
### Support acknowledgement ### Support acknowledgement
In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by The Federal Ministry of Education and Research of The Federal Republic of Germany. Together with [CISPA Helmholtz Center for Information Security](https://cispa.de/en) NetBird brings the security best practices and simplicity to private networking. In November 2022, NetBird joined the [StartUpSecure program](https://www.forschung-it-sicherheit-kommunikationssysteme.de/foerderung/bekanntmachungen/startup-secure) sponsored by the Federal Ministry of Education and Research of the Federal Republic of Germany. Together with the [CISPA Helmholtz Center for Information Security](https://cispa.de/en), NetBird brings security best practices and simplicity to private networking.
![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png) ![CISPA_Logo_BLACK_EN_RZ_RGB (1)](https://user-images.githubusercontent.com/700848/203091324-c6d311a0-22b5-4b05-a288-91cbc6cdcc46.png)
### Testimonials ### Acknowledgements
We use open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE (WebRTC)](https://github.com/pion/ice), [Coturn](https://github.com/coturn/coturn), and [Rosenpass](https://rosenpass.eu). We very much appreciate the work these guys are doing and we'd greatly appreciate if you could support them in any way (e.g., by giving a star or a contribution). We build on open-source technologies like [WireGuard®](https://www.wireguard.com/), [Pion ICE](https://github.com/pion/ice), and [Rosenpass](https://rosenpass.eu). We greatly appreciate the work these projects are doing, and we'd love it if you could support them too (e.g., by starring or contributing).
### Legal ### Legal
This repository is licensed under BSD-3-Clause license that applies to all parts of the repository except for the directories management/, signal/ and relay/. This repository is licensed under the BSD-3-Clause license, which applies to all parts of the repository except for the directories management/, signal/ and relay/.
Those directories are licensed under the GNU Affero General Public License version 3.0 (AGPLv3). See the respective LICENSE files inside each directory. Those directories are licensed under the GNU Affero General Public License version 3.0 (AGPLv3). See the respective LICENSE files inside each directory.
_WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld. _WireGuard_ and the _WireGuard_ logo are [registered trademarks](https://www.wireguard.com/trademark-policy/) of Jason A. Donenfeld.

View File

@@ -52,9 +52,10 @@ func (m *externalChainMonitor) start() {
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel m.cancel = cancel
m.done = make(chan struct{}) done := make(chan struct{})
m.done = done
go m.run(ctx) go m.run(ctx, done)
} }
func (m *externalChainMonitor) stop() { func (m *externalChainMonitor) stop() {
@@ -72,8 +73,8 @@ func (m *externalChainMonitor) stop() {
<-done <-done
} }
func (m *externalChainMonitor) run(ctx context.Context) { func (m *externalChainMonitor) run(ctx context.Context, done chan struct{}) {
defer close(m.done) defer close(done)
bo := &backoff.ExponentialBackOff{ bo := &backoff.ExponentialBackOff{
InitialInterval: externalMonitorInitInterval, InitialInterval: externalMonitorInitInterval,

View File

@@ -9,9 +9,21 @@ then
fi fi
old_pwd=$(pwd) old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0")) script_path=$(dirname "$(realpath "$0")")
cd "$script_path" cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.6
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 repo_root=$(git rev-parse --show-toplevel)
# shellcheck source=/dev/null
. "$repo_root/proto-tools.env"
actual_protoc=$(protoc --version | awk '{print $2}')
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
exit 1
fi
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional protoc -I ./ ./daemon.proto --go_out=../ --go-grpc_out=../ --experimental_allow_proto3_optional
cd "$old_pwd" cd "$old_pwd"

View File

@@ -1,2 +1,28 @@
#!/bin/bash #!/bin/bash
set -e
if ! which realpath > /dev/null 2>&1
then
echo realpath is not installed
echo run: brew install coreutils
exit 1
fi
old_pwd=$(pwd)
script_path=$(dirname "$(realpath "$0")")
cd "$script_path/.."
repo_root=$(git rev-parse --show-toplevel)
# shellcheck source=/dev/null
. "$repo_root/proto-tools.env"
actual_protoc=$(protoc --version | awk '{print $2}')
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
exit 1
fi
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
protoc -I testprotos/ testprotos/testproto.proto --go_out=. protoc -I testprotos/ testprotos/testproto.proto --go_out=.
cd "$old_pwd"

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.36.6
// protoc v3.12.4 // protoc v6.33.1
// source: testproto.proto // source: testproto.proto
package testprotos package testprotos
@@ -11,6 +11,7 @@ import (
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@@ -21,20 +22,17 @@ const (
) )
type TestMessage struct { type TestMessage struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
} }
func (x *TestMessage) Reset() { func (x *TestMessage) Reset() {
*x = TestMessage{} *x = TestMessage{}
if protoimpl.UnsafeEnabled { mi := &file_testproto_proto_msgTypes[0]
mi := &file_testproto_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *TestMessage) String() string { func (x *TestMessage) String() string {
@@ -45,7 +43,7 @@ func (*TestMessage) ProtoMessage() {}
func (x *TestMessage) ProtoReflect() protoreflect.Message { func (x *TestMessage) ProtoReflect() protoreflect.Message {
mi := &file_testproto_proto_msgTypes[0] mi := &file_testproto_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -69,29 +67,27 @@ func (x *TestMessage) GetBody() string {
var File_testproto_proto protoreflect.FileDescriptor var File_testproto_proto protoreflect.FileDescriptor
var file_testproto_proto_rawDesc = []byte{ const file_testproto_proto_rawDesc = "" +
0x0a, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, "\n" +
0x6f, 0x12, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x22, 0x21, 0x0a, "\x0ftestproto.proto\x12\n" +
0x0b, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, "testprotos\"!\n" +
0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, "\vTestMessage\x12\x12\n" +
0x42, 0x0d, 0x5a, 0x0b, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x62, "\x04body\x18\x01 \x01(\tR\x04bodyB\rZ\v/testprotosb\x06proto3"
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_testproto_proto_rawDescOnce sync.Once file_testproto_proto_rawDescOnce sync.Once
file_testproto_proto_rawDescData = file_testproto_proto_rawDesc file_testproto_proto_rawDescData []byte
) )
func file_testproto_proto_rawDescGZIP() []byte { func file_testproto_proto_rawDescGZIP() []byte {
file_testproto_proto_rawDescOnce.Do(func() { file_testproto_proto_rawDescOnce.Do(func() {
file_testproto_proto_rawDescData = protoimpl.X.CompressGZIP(file_testproto_proto_rawDescData) file_testproto_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_testproto_proto_rawDesc), len(file_testproto_proto_rawDesc)))
}) })
return file_testproto_proto_rawDescData return file_testproto_proto_rawDescData
} }
var file_testproto_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_testproto_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
var file_testproto_proto_goTypes = []interface{}{ var file_testproto_proto_goTypes = []any{
(*TestMessage)(nil), // 0: testprotos.TestMessage (*TestMessage)(nil), // 0: testprotos.TestMessage
} }
var file_testproto_proto_depIdxs = []int32{ var file_testproto_proto_depIdxs = []int32{
@@ -107,25 +103,11 @@ func file_testproto_proto_init() {
if File_testproto_proto != nil { if File_testproto_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled {
file_testproto_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*TestMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_testproto_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_testproto_proto_rawDesc), len(file_testproto_proto_rawDesc)),
NumEnums: 0, NumEnums: 0,
NumMessages: 1, NumMessages: 1,
NumExtensions: 0, NumExtensions: 0,
@@ -136,7 +118,6 @@ func file_testproto_proto_init() {
MessageInfos: file_testproto_proto_msgTypes, MessageInfos: file_testproto_proto_msgTypes,
}.Build() }.Build()
File_testproto_proto = out.File File_testproto_proto = out.File
file_testproto_proto_rawDesc = nil
file_testproto_proto_goTypes = nil file_testproto_proto_goTypes = nil
file_testproto_proto_depIdxs = nil file_testproto_proto_depIdxs = nil
} }

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.36.6
// protoc v3.21.9 // protoc v6.33.1
// source: flow.proto // source: flow.proto
package proto package proto
@@ -12,6 +12,7 @@ import (
timestamppb "google.golang.org/protobuf/types/known/timestamppb" timestamppb "google.golang.org/protobuf/types/known/timestamppb"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@@ -125,27 +126,24 @@ func (Direction) EnumDescriptor() ([]byte, []int) {
} }
type FlowEvent struct { type FlowEvent struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Unique client event identifier // Unique client event identifier
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
// When the event occurred // When the event occurred
Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Timestamp *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=timestamp,proto3" json:"timestamp,omitempty"`
// Public key of the sending peer // Public key of the sending peer
PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` PublicKey []byte `protobuf:"bytes,3,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"`
FlowFields *FlowFields `protobuf:"bytes,4,opt,name=flow_fields,json=flowFields,proto3" json:"flow_fields,omitempty"` FlowFields *FlowFields `protobuf:"bytes,4,opt,name=flow_fields,json=flowFields,proto3" json:"flow_fields,omitempty"`
IsInitiator bool `protobuf:"varint,5,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"` IsInitiator bool `protobuf:"varint,5,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *FlowEvent) Reset() { func (x *FlowEvent) Reset() {
*x = FlowEvent{} *x = FlowEvent{}
if protoimpl.UnsafeEnabled { mi := &file_flow_proto_msgTypes[0]
mi := &file_flow_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *FlowEvent) String() string { func (x *FlowEvent) String() string {
@@ -156,7 +154,7 @@ func (*FlowEvent) ProtoMessage() {}
func (x *FlowEvent) ProtoReflect() protoreflect.Message { func (x *FlowEvent) ProtoReflect() protoreflect.Message {
mi := &file_flow_proto_msgTypes[0] mi := &file_flow_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -207,22 +205,19 @@ func (x *FlowEvent) GetIsInitiator() bool {
} }
type FlowEventAck struct { type FlowEventAck struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Unique client event identifier that has been ack'ed // Unique client event identifier that has been ack'ed
EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"` EventId []byte `protobuf:"bytes,1,opt,name=event_id,json=eventId,proto3" json:"event_id,omitempty"`
IsInitiator bool `protobuf:"varint,2,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"` IsInitiator bool `protobuf:"varint,2,opt,name=isInitiator,proto3" json:"isInitiator,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *FlowEventAck) Reset() { func (x *FlowEventAck) Reset() {
*x = FlowEventAck{} *x = FlowEventAck{}
if protoimpl.UnsafeEnabled { mi := &file_flow_proto_msgTypes[1]
mi := &file_flow_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *FlowEventAck) String() string { func (x *FlowEventAck) String() string {
@@ -233,7 +228,7 @@ func (*FlowEventAck) ProtoMessage() {}
func (x *FlowEventAck) ProtoReflect() protoreflect.Message { func (x *FlowEventAck) ProtoReflect() protoreflect.Message {
mi := &file_flow_proto_msgTypes[1] mi := &file_flow_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -263,10 +258,7 @@ func (x *FlowEventAck) GetIsInitiator() bool {
} }
type FlowFields struct { type FlowFields struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Unique client flow session identifier // Unique client flow session identifier
FlowId []byte `protobuf:"bytes,1,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"` FlowId []byte `protobuf:"bytes,1,opt,name=flow_id,json=flowId,proto3" json:"flow_id,omitempty"`
// Flow type // Flow type
@@ -283,7 +275,7 @@ type FlowFields struct {
DestIp []byte `protobuf:"bytes,7,opt,name=dest_ip,json=destIp,proto3" json:"dest_ip,omitempty"` DestIp []byte `protobuf:"bytes,7,opt,name=dest_ip,json=destIp,proto3" json:"dest_ip,omitempty"`
// Layer 4 -specific information // Layer 4 -specific information
// //
// Types that are assignable to ConnectionInfo: // Types that are valid to be assigned to ConnectionInfo:
// //
// *FlowFields_PortInfo // *FlowFields_PortInfo
// *FlowFields_IcmpInfo // *FlowFields_IcmpInfo
@@ -297,15 +289,15 @@ type FlowFields struct {
// Resource ID // Resource ID
SourceResourceId []byte `protobuf:"bytes,14,opt,name=source_resource_id,json=sourceResourceId,proto3" json:"source_resource_id,omitempty"` SourceResourceId []byte `protobuf:"bytes,14,opt,name=source_resource_id,json=sourceResourceId,proto3" json:"source_resource_id,omitempty"`
DestResourceId []byte `protobuf:"bytes,15,opt,name=dest_resource_id,json=destResourceId,proto3" json:"dest_resource_id,omitempty"` DestResourceId []byte `protobuf:"bytes,15,opt,name=dest_resource_id,json=destResourceId,proto3" json:"dest_resource_id,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *FlowFields) Reset() { func (x *FlowFields) Reset() {
*x = FlowFields{} *x = FlowFields{}
if protoimpl.UnsafeEnabled { mi := &file_flow_proto_msgTypes[2]
mi := &file_flow_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *FlowFields) String() string { func (x *FlowFields) String() string {
@@ -316,7 +308,7 @@ func (*FlowFields) ProtoMessage() {}
func (x *FlowFields) ProtoReflect() protoreflect.Message { func (x *FlowFields) ProtoReflect() protoreflect.Message {
mi := &file_flow_proto_msgTypes[2] mi := &file_flow_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -380,23 +372,27 @@ func (x *FlowFields) GetDestIp() []byte {
return nil return nil
} }
func (m *FlowFields) GetConnectionInfo() isFlowFields_ConnectionInfo { func (x *FlowFields) GetConnectionInfo() isFlowFields_ConnectionInfo {
if m != nil { if x != nil {
return m.ConnectionInfo return x.ConnectionInfo
} }
return nil return nil
} }
func (x *FlowFields) GetPortInfo() *PortInfo { func (x *FlowFields) GetPortInfo() *PortInfo {
if x, ok := x.GetConnectionInfo().(*FlowFields_PortInfo); ok { if x != nil {
return x.PortInfo if x, ok := x.ConnectionInfo.(*FlowFields_PortInfo); ok {
return x.PortInfo
}
} }
return nil return nil
} }
func (x *FlowFields) GetIcmpInfo() *ICMPInfo { func (x *FlowFields) GetIcmpInfo() *ICMPInfo {
if x, ok := x.GetConnectionInfo().(*FlowFields_IcmpInfo); ok { if x != nil {
return x.IcmpInfo if x, ok := x.ConnectionInfo.(*FlowFields_IcmpInfo); ok {
return x.IcmpInfo
}
} }
return nil return nil
} }
@@ -463,21 +459,18 @@ func (*FlowFields_IcmpInfo) isFlowFields_ConnectionInfo() {}
// TCP/UDP port information // TCP/UDP port information
type PortInfo struct { type PortInfo struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache SourcePort uint32 `protobuf:"varint,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
DestPort uint32 `protobuf:"varint,2,opt,name=dest_port,json=destPort,proto3" json:"dest_port,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
SourcePort uint32 `protobuf:"varint,1,opt,name=source_port,json=sourcePort,proto3" json:"source_port,omitempty"`
DestPort uint32 `protobuf:"varint,2,opt,name=dest_port,json=destPort,proto3" json:"dest_port,omitempty"`
} }
func (x *PortInfo) Reset() { func (x *PortInfo) Reset() {
*x = PortInfo{} *x = PortInfo{}
if protoimpl.UnsafeEnabled { mi := &file_flow_proto_msgTypes[3]
mi := &file_flow_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *PortInfo) String() string { func (x *PortInfo) String() string {
@@ -488,7 +481,7 @@ func (*PortInfo) ProtoMessage() {}
func (x *PortInfo) ProtoReflect() protoreflect.Message { func (x *PortInfo) ProtoReflect() protoreflect.Message {
mi := &file_flow_proto_msgTypes[3] mi := &file_flow_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -519,21 +512,18 @@ func (x *PortInfo) GetDestPort() uint32 {
// ICMP message information // ICMP message information
type ICMPInfo struct { type ICMPInfo struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache IcmpType uint32 `protobuf:"varint,1,opt,name=icmp_type,json=icmpType,proto3" json:"icmp_type,omitempty"`
IcmpCode uint32 `protobuf:"varint,2,opt,name=icmp_code,json=icmpCode,proto3" json:"icmp_code,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
IcmpType uint32 `protobuf:"varint,1,opt,name=icmp_type,json=icmpType,proto3" json:"icmp_type,omitempty"`
IcmpCode uint32 `protobuf:"varint,2,opt,name=icmp_code,json=icmpCode,proto3" json:"icmp_code,omitempty"`
} }
func (x *ICMPInfo) Reset() { func (x *ICMPInfo) Reset() {
*x = ICMPInfo{} *x = ICMPInfo{}
if protoimpl.UnsafeEnabled { mi := &file_flow_proto_msgTypes[4]
mi := &file_flow_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *ICMPInfo) String() string { func (x *ICMPInfo) String() string {
@@ -544,7 +534,7 @@ func (*ICMPInfo) ProtoMessage() {}
func (x *ICMPInfo) ProtoReflect() protoreflect.Message { func (x *ICMPInfo) ProtoReflect() protoreflect.Message {
mi := &file_flow_proto_msgTypes[4] mi := &file_flow_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -575,102 +565,79 @@ func (x *ICMPInfo) GetIcmpCode() uint32 {
var File_flow_proto protoreflect.FileDescriptor var File_flow_proto protoreflect.FileDescriptor
var file_flow_proto_rawDesc = []byte{ const file_flow_proto_rawDesc = "" +
0x0a, 0x0a, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x66, 0x6c, "\n" +
0x6f, 0x77, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, "\n" +
0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, "flow.proto\x12\x04flow\x1a\x1fgoogle/protobuf/timestamp.proto\"\xd4\x01\n" +
0x6f, 0x74, 0x6f, 0x22, 0xd4, 0x01, 0x0a, 0x09, 0x46, 0x6c, 0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, "\tFlowEvent\x12\x19\n" +
0x74, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, "\bevent_id\x18\x01 \x01(\fR\aeventId\x128\n" +
0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, "\ttimestamp\x18\x02 \x01(\v2\x1a.google.protobuf.TimestampR\ttimestamp\x12\x1d\n" +
0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, "\n" +
0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, "public_key\x18\x03 \x01(\fR\tpublicKey\x121\n" +
0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, "\vflow_fields\x18\x04 \x01(\v2\x10.flow.FlowFieldsR\n" +
0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, "flowFields\x12 \n" +
0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, "\visInitiator\x18\x05 \x01(\bR\visInitiator\"K\n" +
0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x31, 0x0a, 0x0b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x66, 0x69, "\fFlowEventAck\x12\x19\n" +
0x65, 0x6c, 0x64, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x66, 0x6c, 0x6f, "\bevent_id\x18\x01 \x01(\fR\aeventId\x12 \n" +
0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x52, 0x0a, 0x66, 0x6c, "\visInitiator\x18\x02 \x01(\bR\visInitiator\"\x9c\x04\n" +
0x6f, 0x77, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73, 0x49, 0x6e, "\n" +
0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, "FlowFields\x12\x17\n" +
0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x4b, 0x0a, 0x0c, 0x46, 0x6c, "\aflow_id\x18\x01 \x01(\fR\x06flowId\x12\x1e\n" +
0x6f, 0x77, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x12, 0x19, 0x0a, 0x08, 0x65, 0x76, "\x04type\x18\x02 \x01(\x0e2\n" +
0x65, 0x6e, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x65, 0x76, ".flow.TypeR\x04type\x12\x17\n" +
0x65, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x69, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, "\arule_id\x18\x03 \x01(\fR\x06ruleId\x12-\n" +
0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x49, 0x6e, "\tdirection\x18\x04 \x01(\x0e2\x0f.flow.DirectionR\tdirection\x12\x1a\n" +
0x69, 0x74, 0x69, 0x61, 0x74, 0x6f, 0x72, 0x22, 0x9c, 0x04, 0x0a, 0x0a, 0x46, 0x6c, 0x6f, 0x77, "\bprotocol\x18\x05 \x01(\rR\bprotocol\x12\x1b\n" +
0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x17, 0x0a, 0x07, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, "\tsource_ip\x18\x06 \x01(\fR\bsourceIp\x12\x17\n" +
0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x12, "\adest_ip\x18\a \x01(\fR\x06destIp\x12-\n" +
0x1e, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0a, 0x2e, "\tport_info\x18\b \x01(\v2\x0e.flow.PortInfoH\x00R\bportInfo\x12-\n" +
0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, "\ticmp_info\x18\t \x01(\v2\x0e.flow.ICMPInfoH\x00R\bicmpInfo\x12\x1d\n" +
0x17, 0x0a, 0x07, 0x72, 0x75, 0x6c, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, "\n" +
0x52, 0x06, 0x72, 0x75, 0x6c, 0x65, 0x49, 0x64, 0x12, 0x2d, 0x0a, 0x09, 0x64, 0x69, 0x72, 0x65, "rx_packets\x18\n" +
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x66, 0x6c, " \x01(\x04R\trxPackets\x12\x1d\n" +
0x6f, 0x77, 0x2e, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x64, 0x69, "\n" +
0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, "tx_packets\x18\v \x01(\x04R\ttxPackets\x12\x19\n" +
0x63, 0x6f, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x74, 0x6f, "\brx_bytes\x18\f \x01(\x04R\arxBytes\x12\x19\n" +
0x63, 0x6f, 0x6c, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x70, "\btx_bytes\x18\r \x01(\x04R\atxBytes\x12,\n" +
0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x70, "\x12source_resource_id\x18\x0e \x01(\fR\x10sourceResourceId\x12(\n" +
0x12, 0x17, 0x0a, 0x07, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, "\x10dest_resource_id\x18\x0f \x01(\fR\x0edestResourceIdB\x11\n" +
0x0c, 0x52, 0x06, 0x64, 0x65, 0x73, 0x74, 0x49, 0x70, 0x12, 0x2d, 0x0a, 0x09, 0x70, 0x6f, 0x72, "\x0fconnection_info\"H\n" +
0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, "\bPortInfo\x12\x1f\n" +
0x6c, 0x6f, 0x77, 0x2e, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x08, "\vsource_port\x18\x01 \x01(\rR\n" +
0x70, 0x6f, 0x72, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2d, 0x0a, 0x09, 0x69, 0x63, 0x6d, 0x70, "sourcePort\x12\x1b\n" +
0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x66, 0x6c, "\tdest_port\x18\x02 \x01(\rR\bdestPort\"D\n" +
0x6f, 0x77, 0x2e, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x08, 0x69, "\bICMPInfo\x12\x1b\n" +
0x63, 0x6d, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x78, 0x5f, 0x70, 0x61, "\ticmp_type\x18\x01 \x01(\rR\bicmpType\x12\x1b\n" +
0x63, 0x6b, 0x65, 0x74, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x72, 0x78, 0x50, "\ticmp_code\x18\x02 \x01(\rR\bicmpCode*E\n" +
0x61, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x1d, 0x0a, 0x0a, 0x74, 0x78, 0x5f, 0x70, 0x61, 0x63, "\x04Type\x12\x10\n" +
0x6b, 0x65, 0x74, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x78, 0x50, 0x61, "\fTYPE_UNKNOWN\x10\x00\x12\x0e\n" +
0x63, 0x6b, 0x65, 0x74, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, "\n" +
0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x72, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, "TYPE_START\x10\x01\x12\f\n" +
0x12, 0x19, 0x0a, 0x08, 0x74, 0x78, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x0d, 0x20, 0x01, "\bTYPE_END\x10\x02\x12\r\n" +
0x28, 0x04, 0x52, 0x07, 0x74, 0x78, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, "\tTYPE_DROP\x10\x03*;\n" +
0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, "\tDirection\x12\x15\n" +
0x64, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, "\x11DIRECTION_UNKNOWN\x10\x00\x12\v\n" +
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x64, 0x65, 0x73, "\aINGRESS\x10\x01\x12\n" +
0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x0f, 0x20, "\n" +
0x01, 0x28, 0x0c, 0x52, 0x0e, 0x64, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, "\x06EGRESS\x10\x022B\n" +
0x65, 0x49, 0x64, 0x42, 0x11, 0x0a, 0x0f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, "\vFlowService\x123\n" +
0x6e, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x6f, 0x72, 0x74, 0x49, 0x6e, "\x06Events\x12\x0f.flow.FlowEvent\x1a\x12.flow.FlowEventAck\"\x00(\x010\x01B\bZ\x06/protob\x06proto3"
0x66, 0x6f, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x70, 0x6f, 0x72,
0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x50,
0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x64, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74,
0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x64, 0x65, 0x73, 0x74, 0x50, 0x6f, 0x72, 0x74,
0x22, 0x44, 0x0a, 0x08, 0x49, 0x43, 0x4d, 0x50, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a, 0x09,
0x69, 0x63, 0x6d, 0x70, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52,
0x08, 0x69, 0x63, 0x6d, 0x70, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x63, 0x6d,
0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x69, 0x63,
0x6d, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x2a, 0x45, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x10,
0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x01,
0x12, 0x0c, 0x0a, 0x08, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x45, 0x4e, 0x44, 0x10, 0x02, 0x12, 0x0d,
0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x52, 0x4f, 0x50, 0x10, 0x03, 0x2a, 0x3b, 0x0a,
0x09, 0x44, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x0a, 0x11, 0x44, 0x49,
0x52, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
0x00, 0x12, 0x0b, 0x0a, 0x07, 0x49, 0x4e, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x01, 0x12, 0x0a,
0x0a, 0x06, 0x45, 0x47, 0x52, 0x45, 0x53, 0x53, 0x10, 0x02, 0x32, 0x42, 0x0a, 0x0b, 0x46, 0x6c,
0x6f, 0x77, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x45, 0x76, 0x65,
0x6e, 0x74, 0x73, 0x12, 0x0f, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77, 0x45,
0x76, 0x65, 0x6e, 0x74, 0x1a, 0x12, 0x2e, 0x66, 0x6c, 0x6f, 0x77, 0x2e, 0x46, 0x6c, 0x6f, 0x77,
0x45, 0x76, 0x65, 0x6e, 0x74, 0x41, 0x63, 0x6b, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08,
0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_flow_proto_rawDescOnce sync.Once file_flow_proto_rawDescOnce sync.Once
file_flow_proto_rawDescData = file_flow_proto_rawDesc file_flow_proto_rawDescData []byte
) )
func file_flow_proto_rawDescGZIP() []byte { func file_flow_proto_rawDescGZIP() []byte {
file_flow_proto_rawDescOnce.Do(func() { file_flow_proto_rawDescOnce.Do(func() {
file_flow_proto_rawDescData = protoimpl.X.CompressGZIP(file_flow_proto_rawDescData) file_flow_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_flow_proto_rawDesc), len(file_flow_proto_rawDesc)))
}) })
return file_flow_proto_rawDescData return file_flow_proto_rawDescData
} }
var file_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_flow_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
var file_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_flow_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_flow_proto_goTypes = []interface{}{ var file_flow_proto_goTypes = []any{
(Type)(0), // 0: flow.Type (Type)(0), // 0: flow.Type
(Direction)(0), // 1: flow.Direction (Direction)(0), // 1: flow.Direction
(*FlowEvent)(nil), // 2: flow.FlowEvent (*FlowEvent)(nil), // 2: flow.FlowEvent
@@ -701,69 +668,7 @@ func file_flow_proto_init() {
if File_flow_proto != nil { if File_flow_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled { file_flow_proto_msgTypes[2].OneofWrappers = []any{
file_flow_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FlowEvent); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_flow_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FlowEventAck); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_flow_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*FlowFields); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_flow_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PortInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_flow_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ICMPInfo); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_flow_proto_msgTypes[2].OneofWrappers = []interface{}{
(*FlowFields_PortInfo)(nil), (*FlowFields_PortInfo)(nil),
(*FlowFields_IcmpInfo)(nil), (*FlowFields_IcmpInfo)(nil),
} }
@@ -771,7 +676,7 @@ func file_flow_proto_init() {
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_flow_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_flow_proto_rawDesc), len(file_flow_proto_rawDesc)),
NumEnums: 2, NumEnums: 2,
NumMessages: 5, NumMessages: 5,
NumExtensions: 0, NumExtensions: 0,
@@ -783,7 +688,6 @@ func file_flow_proto_init() {
MessageInfos: file_flow_proto_msgTypes, MessageInfos: file_flow_proto_msgTypes,
}.Build() }.Build()
File_flow_proto = out.File File_flow_proto = out.File
file_flow_proto_rawDesc = nil
file_flow_proto_goTypes = nil file_flow_proto_goTypes = nil
file_flow_proto_depIdxs = nil file_flow_proto_depIdxs = nil
} }

View File

@@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v6.33.1
// source: flow.proto
package proto package proto
@@ -11,15 +15,19 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const (
FlowService_Events_FullMethodName = "/flow.FlowService/Events"
)
// FlowServiceClient is the client API for FlowService service. // FlowServiceClient is the client API for FlowService service.
// //
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type FlowServiceClient interface { type FlowServiceClient interface {
// Client to receiver streams of events and acknowledgements // Client to receiver streams of events and acknowledgements
Events(ctx context.Context, opts ...grpc.CallOption) (FlowService_EventsClient, error) Events(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[FlowEvent, FlowEventAck], error)
} }
type flowServiceClient struct { type flowServiceClient struct {
@@ -30,54 +38,40 @@ func NewFlowServiceClient(cc grpc.ClientConnInterface) FlowServiceClient {
return &flowServiceClient{cc} return &flowServiceClient{cc}
} }
func (c *flowServiceClient) Events(ctx context.Context, opts ...grpc.CallOption) (FlowService_EventsClient, error) { func (c *flowServiceClient) Events(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[FlowEvent, FlowEventAck], error) {
stream, err := c.cc.NewStream(ctx, &FlowService_ServiceDesc.Streams[0], "/flow.FlowService/Events", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &FlowService_ServiceDesc.Streams[0], FlowService_Events_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &flowServiceEventsClient{stream} x := &grpc.GenericClientStream[FlowEvent, FlowEventAck]{ClientStream: stream}
return x, nil return x, nil
} }
type FlowService_EventsClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*FlowEvent) error type FlowService_EventsClient = grpc.BidiStreamingClient[FlowEvent, FlowEventAck]
Recv() (*FlowEventAck, error)
grpc.ClientStream
}
type flowServiceEventsClient struct {
grpc.ClientStream
}
func (x *flowServiceEventsClient) Send(m *FlowEvent) error {
return x.ClientStream.SendMsg(m)
}
func (x *flowServiceEventsClient) Recv() (*FlowEventAck, error) {
m := new(FlowEventAck)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// FlowServiceServer is the server API for FlowService service. // FlowServiceServer is the server API for FlowService service.
// All implementations must embed UnimplementedFlowServiceServer // All implementations must embed UnimplementedFlowServiceServer
// for forward compatibility // for forward compatibility.
type FlowServiceServer interface { type FlowServiceServer interface {
// Client to receiver streams of events and acknowledgements // Client to receiver streams of events and acknowledgements
Events(FlowService_EventsServer) error Events(grpc.BidiStreamingServer[FlowEvent, FlowEventAck]) error
mustEmbedUnimplementedFlowServiceServer() mustEmbedUnimplementedFlowServiceServer()
} }
// UnimplementedFlowServiceServer must be embedded to have forward compatible implementations. // UnimplementedFlowServiceServer must be embedded to have
type UnimplementedFlowServiceServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedFlowServiceServer struct{}
func (UnimplementedFlowServiceServer) Events(FlowService_EventsServer) error { func (UnimplementedFlowServiceServer) Events(grpc.BidiStreamingServer[FlowEvent, FlowEventAck]) error {
return status.Errorf(codes.Unimplemented, "method Events not implemented") return status.Error(codes.Unimplemented, "method Events not implemented")
} }
func (UnimplementedFlowServiceServer) mustEmbedUnimplementedFlowServiceServer() {} func (UnimplementedFlowServiceServer) mustEmbedUnimplementedFlowServiceServer() {}
func (UnimplementedFlowServiceServer) testEmbeddedByValue() {}
// UnsafeFlowServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeFlowServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to FlowServiceServer will // Use of this interface is not recommended, as added methods to FlowServiceServer will
@@ -87,34 +81,22 @@ type UnsafeFlowServiceServer interface {
} }
func RegisterFlowServiceServer(s grpc.ServiceRegistrar, srv FlowServiceServer) { func RegisterFlowServiceServer(s grpc.ServiceRegistrar, srv FlowServiceServer) {
// If the following call panics, it indicates UnimplementedFlowServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&FlowService_ServiceDesc, srv) s.RegisterService(&FlowService_ServiceDesc, srv)
} }
func _FlowService_Events_Handler(srv interface{}, stream grpc.ServerStream) error { func _FlowService_Events_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(FlowServiceServer).Events(&flowServiceEventsServer{stream}) return srv.(FlowServiceServer).Events(&grpc.GenericServerStream[FlowEvent, FlowEventAck]{ServerStream: stream})
} }
type FlowService_EventsServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*FlowEventAck) error type FlowService_EventsServer = grpc.BidiStreamingServer[FlowEvent, FlowEventAck]
Recv() (*FlowEvent, error)
grpc.ServerStream
}
type flowServiceEventsServer struct {
grpc.ServerStream
}
func (x *flowServiceEventsServer) Send(m *FlowEventAck) error {
return x.ServerStream.SendMsg(m)
}
func (x *flowServiceEventsServer) Recv() (*FlowEvent, error) {
m := new(FlowEvent)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// FlowService_ServiceDesc is the grpc.ServiceDesc for FlowService service. // FlowService_ServiceDesc is the grpc.ServiceDesc for FlowService service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,

View File

@@ -9,9 +9,21 @@ then
fi fi
old_pwd=$(pwd) old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0")) script_path=$(dirname "$(realpath "$0")")
cd "$script_path" cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 repo_root=$(git rev-parse --show-toplevel)
# shellcheck source=/dev/null
. "$repo_root/proto-tools.env"
actual_protoc=$(protoc --version | awk '{print $2}')
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
exit 1
fi
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
protoc -I ./ ./flow.proto --go_out=../ --go-grpc_out=../ protoc -I ./ ./flow.proto --go_out=../ --go-grpc_out=../
cd "$old_pwd" cd "$old_pwd"

View File

@@ -9,6 +9,7 @@ import (
"encoding/hex" "encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io"
"net" "net"
"net/http" "net/http"
"net/url" "net/url"
@@ -136,9 +137,12 @@ type proxyConnection struct {
tokenID string tokenID string
capabilities *proto.ProxyCapabilities capabilities *proto.ProxyCapabilities
stream proto.ProxyService_GetMappingUpdateServer stream proto.ProxyService_GetMappingUpdateServer
sendChan chan *proto.GetMappingUpdateResponse // syncStream is set when the proxy connected via SyncMappings.
ctx context.Context // When non-nil, the sender goroutine uses this instead of stream.
cancel context.CancelFunc syncStream proto.ProxyService_SyncMappingsServer
sendChan chan *proto.GetMappingUpdateResponse
ctx context.Context
cancel context.CancelFunc
} }
func enforceAccountScope(ctx context.Context, requestAccountID string) error { func enforceAccountScope(ctx context.Context, requestAccountID string) error {
@@ -206,145 +210,322 @@ func (s *ProxyServiceServer) SetProxyController(proxyController proxy.Controller
s.proxyController = proxyController s.proxyController = proxyController
} }
// proxyConnectParams holds the validated parameters extracted from either
// a GetMappingUpdateRequest or a SyncMappingsInit message.
type proxyConnectParams struct {
proxyID string
address string
capabilities *proto.ProxyCapabilities
}
// GetMappingUpdate handles the control stream with proxy clients // GetMappingUpdate handles the control stream with proxy clients
func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest, stream proto.ProxyService_GetMappingUpdateServer) error { func (s *ProxyServiceServer) GetMappingUpdate(req *proto.GetMappingUpdateRequest, stream proto.ProxyService_GetMappingUpdateServer) error {
ctx := stream.Context() params, err := s.validateProxyConnect(req.GetProxyId(), req.GetAddress(), stream.Context())
if err != nil {
return err
}
params.capabilities = req.GetCapabilities()
peerInfo := PeerIPFromContext(ctx) conn, proxyRecord, err := s.registerProxyConnection(stream.Context(), params, &proxyConnection{
log.Infof("New proxy connection from %s", peerInfo) stream: stream,
})
if err != nil {
return err
}
proxyID := req.GetProxyId() if err := s.sendSnapshot(stream.Context(), conn); err != nil {
s.cleanupFailedSnapshot(stream.Context(), conn)
return fmt.Errorf("send snapshot to proxy %s: %w", params.proxyID, err)
}
errChan := make(chan error, 2)
go s.sender(conn, errChan)
return s.serveProxyConnection(conn, proxyRecord, errChan, false)
}
// SyncMappings implements the bidirectional SyncMappings RPC.
// It mirrors GetMappingUpdate but provides application-level back-pressure:
// management waits for an ack from the proxy before sending the next batch.
func (s *ProxyServiceServer) SyncMappings(stream proto.ProxyService_SyncMappingsServer) error {
init, err := recvSyncInit(stream)
if err != nil {
return err
}
params, err := s.validateProxyConnect(init.GetProxyId(), init.GetAddress(), stream.Context())
if err != nil {
return err
}
params.capabilities = init.GetCapabilities()
conn, proxyRecord, err := s.registerProxyConnection(stream.Context(), params, &proxyConnection{
syncStream: stream,
})
if err != nil {
return err
}
if err := s.sendSnapshotSync(stream.Context(), conn, stream); err != nil {
s.cleanupFailedSnapshot(stream.Context(), conn)
return fmt.Errorf("send snapshot to proxy %s: %w", params.proxyID, err)
}
errChan := make(chan error, 2)
go s.sender(conn, errChan)
go s.drainRecv(stream, errChan)
return s.serveProxyConnection(conn, proxyRecord, errChan, true)
}
// recvSyncInit receives and validates the first message on a SyncMappings stream.
func recvSyncInit(stream proto.ProxyService_SyncMappingsServer) (*proto.SyncMappingsInit, error) {
firstMsg, err := stream.Recv()
if err != nil {
return nil, status.Errorf(codes.Internal, "receive init: %v", err)
}
init := firstMsg.GetInit()
if init == nil {
return nil, status.Errorf(codes.InvalidArgument, "first message must be init")
}
return init, nil
}
// validateProxyConnect validates the proxy ID and address, and checks cluster
// address availability for account-scoped tokens.
func (s *ProxyServiceServer) validateProxyConnect(proxyID, address string, ctx context.Context) (proxyConnectParams, error) {
if proxyID == "" { if proxyID == "" {
return status.Errorf(codes.InvalidArgument, "proxy_id is required") return proxyConnectParams{}, status.Errorf(codes.InvalidArgument, "proxy_id is required")
}
if !isProxyAddressValid(address) {
return proxyConnectParams{}, status.Errorf(codes.InvalidArgument, "proxy address is invalid")
} }
proxyAddress := req.GetAddress()
if !isProxyAddressValid(proxyAddress) {
return status.Errorf(codes.InvalidArgument, "proxy address is invalid")
}
var accountID *string
token := GetProxyTokenFromContext(ctx) token := GetProxyTokenFromContext(ctx)
if token != nil && token.AccountID != nil { if token != nil && token.AccountID != nil {
accountID = token.AccountID available, err := s.proxyManager.IsClusterAddressAvailable(ctx, address, *token.AccountID)
available, err := s.proxyManager.IsClusterAddressAvailable(ctx, proxyAddress, *accountID)
if err != nil { if err != nil {
return status.Errorf(codes.Internal, "check cluster address: %v", err) return proxyConnectParams{}, status.Errorf(codes.Internal, "check cluster address: %v", err)
} }
if !available { if !available {
return status.Errorf(codes.AlreadyExists, "cluster address %s is already in use", proxyAddress) return proxyConnectParams{}, status.Errorf(codes.AlreadyExists, "cluster address %s is already in use", address)
} }
} }
return proxyConnectParams{proxyID: proxyID, address: address}, nil
}
// registerProxyConnection creates a proxyConnection, registers it with the
// proxy manager and cluster, and stores it in connectedProxies. The caller
// provides a partially initialised connSeed with stream-specific fields set;
// the remaining fields are filled in here.
func (s *ProxyServiceServer) registerProxyConnection(ctx context.Context, params proxyConnectParams, connSeed *proxyConnection) (*proxyConnection, *proxy.Proxy, error) {
peerInfo := PeerIPFromContext(ctx)
var accountID *string
var tokenID string var tokenID string
if token != nil { if token := GetProxyTokenFromContext(ctx); token != nil {
if token.AccountID != nil {
accountID = token.AccountID
}
tokenID = token.ID tokenID = token.ID
} }
sessionID := uuid.NewString() sessionID := uuid.NewString()
s.supersedePriorConnection(params.proxyID, sessionID)
if old, loaded := s.connectedProxies.Load(proxyID); loaded {
oldConn := old.(*proxyConnection)
log.WithFields(log.Fields{
"proxy_id": proxyID,
"old_session_id": oldConn.sessionID,
"new_session_id": sessionID,
}).Info("Superseding existing proxy connection")
oldConn.cancel()
}
connCtx, cancel := context.WithCancel(ctx) connCtx, cancel := context.WithCancel(ctx)
conn := &proxyConnection{ connSeed.proxyID = params.proxyID
proxyID: proxyID, connSeed.sessionID = sessionID
sessionID: sessionID, connSeed.address = params.address
address: proxyAddress, connSeed.accountID = accountID
accountID: accountID, connSeed.tokenID = tokenID
tokenID: tokenID, connSeed.capabilities = params.capabilities
capabilities: req.GetCapabilities(), connSeed.sendChan = make(chan *proto.GetMappingUpdateResponse, 100)
stream: stream, connSeed.ctx = connCtx
sendChan: make(chan *proto.GetMappingUpdateResponse, 100), connSeed.cancel = cancel
ctx: connCtx,
cancel: cancel,
}
var caps *proxy.Capabilities var caps *proxy.Capabilities
if c := req.GetCapabilities(); c != nil { if c := params.capabilities; c != nil {
caps = &proxy.Capabilities{ caps = &proxy.Capabilities{
SupportsCustomPorts: c.SupportsCustomPorts, SupportsCustomPorts: c.SupportsCustomPorts,
RequireSubdomain: c.RequireSubdomain, RequireSubdomain: c.RequireSubdomain,
SupportsCrowdsec: c.SupportsCrowdsec, SupportsCrowdsec: c.SupportsCrowdsec,
} }
} }
proxyRecord, err := s.proxyManager.Connect(ctx, proxyID, sessionID, proxyAddress, peerInfo, accountID, caps)
proxyRecord, err := s.proxyManager.Connect(ctx, params.proxyID, sessionID, params.address, peerInfo, accountID, caps)
if err != nil { if err != nil {
cancel() cancel()
if accountID != nil { if accountID != nil {
return status.Errorf(codes.Internal, "failed to register BYOP proxy: %v", err) return nil, nil, status.Errorf(codes.Internal, "failed to register BYOP proxy: %v", err)
} }
log.WithContext(ctx).Warnf("failed to register proxy %s in database: %v", proxyID, err) log.WithContext(ctx).Warnf("failed to register proxy %s in database: %v", params.proxyID, err)
return status.Errorf(codes.Internal, "register proxy in database: %v", err) return nil, nil, status.Errorf(codes.Internal, "register proxy in database: %v", err)
} }
s.connectedProxies.Store(proxyID, conn) s.connectedProxies.Store(params.proxyID, connSeed)
if err := s.proxyController.RegisterProxyToCluster(ctx, conn.address, proxyID); err != nil { if err := s.proxyController.RegisterProxyToCluster(ctx, params.address, params.proxyID); err != nil {
log.WithContext(ctx).Warnf("Failed to register proxy %s in cluster: %v", proxyID, err) log.WithContext(ctx).Warnf("Failed to register proxy %s in cluster: %v", params.proxyID, err)
} }
if err := s.sendSnapshot(ctx, conn); err != nil { return connSeed, proxyRecord, nil
if s.connectedProxies.CompareAndDelete(proxyID, conn) { }
if unregErr := s.proxyController.UnregisterProxyFromCluster(context.Background(), conn.address, proxyID); unregErr != nil {
log.WithContext(ctx).Debugf("cleanup after snapshot failure for proxy %s: %v", proxyID, unregErr) // supersedePriorConnection cancels any existing connection for the given proxy.
} func (s *ProxyServiceServer) supersedePriorConnection(proxyID, newSessionID string) {
} if old, loaded := s.connectedProxies.Load(proxyID); loaded {
cancel() oldConn := old.(*proxyConnection)
if disconnErr := s.proxyManager.Disconnect(context.Background(), proxyID, sessionID); disconnErr != nil { log.WithFields(log.Fields{
log.WithContext(ctx).Debugf("cleanup after snapshot failure for proxy %s: %v", proxyID, disconnErr) "proxy_id": proxyID,
} "old_session_id": oldConn.sessionID,
return fmt.Errorf("send snapshot to proxy %s: %w", proxyID, err) "new_session_id": newSessionID,
}).Info("Superseding existing proxy connection")
oldConn.cancel()
} }
}
errChan := make(chan error, 2) // cleanupFailedSnapshot removes the connection from the cluster and store
go s.sender(conn, errChan) // after a snapshot send failure.
func (s *ProxyServiceServer) cleanupFailedSnapshot(ctx context.Context, conn *proxyConnection) {
if s.connectedProxies.CompareAndDelete(conn.proxyID, conn) {
if err := s.proxyController.UnregisterProxyFromCluster(context.Background(), conn.address, conn.proxyID); err != nil {
log.WithContext(ctx).Debugf("cleanup after snapshot failure for proxy %s: %v", conn.proxyID, err)
}
}
conn.cancel()
if err := s.proxyManager.Disconnect(context.Background(), conn.proxyID, conn.sessionID); err != nil {
log.WithContext(ctx).Debugf("cleanup after snapshot failure for proxy %s: %v", conn.proxyID, err)
}
}
log.WithFields(log.Fields{ // drainRecv consumes and discards messages from a bidirectional stream.
"proxy_id": proxyID, // The proxy sends an ack for every incremental update; we don't need them
"session_id": sessionID, // after the snapshot phase. Recv errors are forwarded to errChan.
"address": proxyAddress, func (s *ProxyServiceServer) drainRecv(stream proto.ProxyService_SyncMappingsServer, errChan chan<- error) {
"cluster_addr": proxyAddress, for {
"account_id": accountID, if _, err := stream.Recv(); err != nil {
"total_proxies": len(s.GetConnectedProxies()), errChan <- err
}).Info("Proxy registered in cluster")
defer func() {
if !s.connectedProxies.CompareAndDelete(proxyID, conn) {
log.Infof("Proxy %s session %s: skipping cleanup, superseded by new connection", proxyID, sessionID)
cancel()
return return
} }
}
}
if err := s.proxyController.UnregisterProxyFromCluster(context.Background(), conn.address, proxyID); err != nil { // serveProxyConnection runs the post-snapshot lifecycle: heartbeat, sender,
log.Warnf("Failed to unregister proxy %s from cluster: %v", proxyID, err) // and wait for termination. When bidi is true, normal stream closure (EOF,
} // canceled) is treated as a clean disconnect rather than an error.
if err := s.proxyManager.Disconnect(context.Background(), proxyID, sessionID); err != nil { func (s *ProxyServiceServer) serveProxyConnection(conn *proxyConnection, proxyRecord *proxy.Proxy, errChan <-chan error, bidi bool) error {
log.Warnf("Failed to mark proxy %s as disconnected: %v", proxyID, err) log.WithFields(log.Fields{
} "proxy_id": conn.proxyID,
"session_id": conn.sessionID,
"address": conn.address,
"cluster_addr": conn.address,
"account_id": conn.accountID,
"total_proxies": len(s.GetConnectedProxies()),
}).Info("Proxy registered in cluster")
cancel() defer s.disconnectProxy(conn)
log.Infof("Proxy %s session %s disconnected", proxyID, sessionID) go s.heartbeat(conn.ctx, conn, proxyRecord)
}()
go s.heartbeat(connCtx, conn, proxyRecord)
select { select {
case err := <-errChan: case err := <-errChan:
log.WithContext(ctx).Warnf("Failed to send update: %v", err) if bidi && isStreamClosed(err) {
return fmt.Errorf("send update to proxy %s: %w", proxyID, err) log.Infof("Proxy %s stream closed", conn.proxyID)
case <-connCtx.Done(): return nil
log.WithContext(ctx).Infof("Proxy %s context canceled", proxyID) }
return connCtx.Err() log.Warnf("Failed to send update: %v", err)
return fmt.Errorf("send update to proxy %s: %w", conn.proxyID, err)
case <-conn.ctx.Done():
log.Infof("Proxy %s context canceled", conn.proxyID)
return conn.ctx.Err()
} }
} }
// disconnectProxy removes the connection from cluster and store, unless it
// has already been superseded by a newer connection.
func (s *ProxyServiceServer) disconnectProxy(conn *proxyConnection) {
if !s.connectedProxies.CompareAndDelete(conn.proxyID, conn) {
log.Infof("Proxy %s session %s: skipping cleanup, superseded by new connection", conn.proxyID, conn.sessionID)
conn.cancel()
return
}
if err := s.proxyController.UnregisterProxyFromCluster(context.Background(), conn.address, conn.proxyID); err != nil {
log.Warnf("Failed to unregister proxy %s from cluster: %v", conn.proxyID, err)
}
if err := s.proxyManager.Disconnect(context.Background(), conn.proxyID, conn.sessionID); err != nil {
log.Warnf("Failed to mark proxy %s as disconnected: %v", conn.proxyID, err)
}
conn.cancel()
log.Infof("Proxy %s session %s disconnected", conn.proxyID, conn.sessionID)
}
// sendSnapshotSync sends the initial snapshot with back-pressure: it sends
// one batch, then waits for the proxy to ack before sending the next.
func (s *ProxyServiceServer) sendSnapshotSync(ctx context.Context, conn *proxyConnection, stream proto.ProxyService_SyncMappingsServer) error {
if !isProxyAddressValid(conn.address) {
return fmt.Errorf("proxy address is invalid")
}
if s.snapshotBatchSize <= 0 {
return fmt.Errorf("invalid snapshot batch size: %d", s.snapshotBatchSize)
}
mappings, err := s.snapshotServiceMappings(ctx, conn)
if err != nil {
return err
}
for i := 0; i < len(mappings); i += s.snapshotBatchSize {
end := i + s.snapshotBatchSize
if end > len(mappings) {
end = len(mappings)
}
for _, m := range mappings[i:end] {
token, err := s.tokenStore.GenerateToken(m.AccountId, m.Id, s.proxyTokenTTL())
if err != nil {
return fmt.Errorf("generate auth token for service %s: %w", m.Id, err)
}
m.AuthToken = token
}
if err := stream.Send(&proto.SyncMappingsResponse{
Mapping: mappings[i:end],
InitialSyncComplete: end == len(mappings),
}); err != nil {
return fmt.Errorf("send snapshot batch: %w", err)
}
if err := waitForAck(stream); err != nil {
return err
}
}
if len(mappings) == 0 {
if err := stream.Send(&proto.SyncMappingsResponse{
InitialSyncComplete: true,
}); err != nil {
return fmt.Errorf("send snapshot completion: %w", err)
}
if err := waitForAck(stream); err != nil {
return err
}
}
return nil
}
func waitForAck(stream proto.ProxyService_SyncMappingsServer) error {
msg, err := stream.Recv()
if err != nil {
return fmt.Errorf("receive ack: %w", err)
}
if msg.GetAck() == nil {
return fmt.Errorf("expected ack, got %T", msg.GetMsg())
}
return nil
}
// heartbeat updates the proxy's last_seen timestamp every minute and // heartbeat updates the proxy's last_seen timestamp every minute and
// disconnects the proxy if its access token has been revoked. // disconnects the proxy if its access token has been revoked.
func (s *ProxyServiceServer) heartbeat(ctx context.Context, conn *proxyConnection, p *proxy.Proxy) { func (s *ProxyServiceServer) heartbeat(ctx context.Context, conn *proxyConnection, p *proxy.Proxy) {
@@ -381,6 +562,9 @@ func (s *ProxyServiceServer) sendSnapshot(ctx context.Context, conn *proxyConnec
if !isProxyAddressValid(conn.address) { if !isProxyAddressValid(conn.address) {
return fmt.Errorf("proxy address is invalid") return fmt.Errorf("proxy address is invalid")
} }
if s.snapshotBatchSize <= 0 {
return fmt.Errorf("invalid snapshot batch size: %d", s.snapshotBatchSize)
}
mappings, err := s.snapshotServiceMappings(ctx, conn) mappings, err := s.snapshotServiceMappings(ctx, conn)
if err != nil { if err != nil {
@@ -460,12 +644,26 @@ func isProxyAddressValid(addr string) bool {
return err == nil return err == nil
} }
// sender handles sending messages to proxy // isStreamClosed returns true for errors that indicate normal stream
// termination: io.EOF, context cancellation, or gRPC Canceled.
func isStreamClosed(err error) bool {
if err == nil {
return false
}
if errors.Is(err, io.EOF) || errors.Is(err, context.Canceled) {
return true
}
return status.Code(err) == codes.Canceled
}
// sender handles sending messages to proxy.
// When conn.syncStream is set the message is sent as SyncMappingsResponse;
// otherwise the legacy GetMappingUpdateResponse stream is used.
func (s *ProxyServiceServer) sender(conn *proxyConnection, errChan chan<- error) { func (s *ProxyServiceServer) sender(conn *proxyConnection, errChan chan<- error) {
for { for {
select { select {
case resp := <-conn.sendChan: case resp := <-conn.sendChan:
if err := conn.stream.Send(resp); err != nil { if err := conn.sendResponse(resp); err != nil {
errChan <- err errChan <- err
return return
} }
@@ -475,6 +673,17 @@ func (s *ProxyServiceServer) sender(conn *proxyConnection, errChan chan<- error)
} }
} }
// sendResponse sends a mapping update on whichever stream the proxy connected with.
func (conn *proxyConnection) sendResponse(resp *proto.GetMappingUpdateResponse) error {
if conn.syncStream != nil {
return conn.syncStream.Send(&proto.SyncMappingsResponse{
Mapping: resp.Mapping,
InitialSyncComplete: resp.InitialSyncComplete,
})
}
return conn.stream.Send(resp)
}
// SendAccessLog processes access log from proxy // SendAccessLog processes access log from proxy
func (s *ProxyServiceServer) SendAccessLog(ctx context.Context, req *proto.SendAccessLogRequest) (*proto.SendAccessLogResponse, error) { func (s *ProxyServiceServer) SendAccessLog(ctx context.Context, req *proto.SendAccessLogRequest) (*proto.SendAccessLogResponse, error) {
accessLog := req.GetLog() accessLog := req.GetLog()
@@ -541,8 +750,8 @@ func (s *ProxyServiceServer) SendServiceUpdate(update *proto.GetMappingUpdateRes
return true return true
} }
connUpdate = &proto.GetMappingUpdateResponse{ connUpdate = &proto.GetMappingUpdateResponse{
Mapping: filtered, Mapping: filtered,
InitialSyncComplete: update.InitialSyncComplete, InitialSyncComplete: update.InitialSyncComplete,
} }
} }
resp := s.perProxyMessage(connUpdate, conn.proxyID) resp := s.perProxyMessage(connUpdate, conn.proxyID)

View File

@@ -0,0 +1,411 @@
package grpc
import (
"context"
"fmt"
"sync"
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
rpservice "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
"github.com/netbirdio/netbird/shared/management/proto"
)
// syncRecordingStream is a mock ProxyService_SyncMappingsServer that records
// sent messages and returns pre-loaded ack responses from Recv.
type syncRecordingStream struct {
grpc.ServerStream
mu sync.Mutex
sent []*proto.SyncMappingsResponse
recvMsgs []*proto.SyncMappingsRequest
recvIdx int
}
func (s *syncRecordingStream) Send(m *proto.SyncMappingsResponse) error {
s.mu.Lock()
defer s.mu.Unlock()
s.sent = append(s.sent, m)
return nil
}
func (s *syncRecordingStream) Recv() (*proto.SyncMappingsRequest, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.recvIdx >= len(s.recvMsgs) {
return nil, fmt.Errorf("no more recv messages")
}
msg := s.recvMsgs[s.recvIdx]
s.recvIdx++
return msg, nil
}
func (s *syncRecordingStream) Context() context.Context { return context.Background() }
func (s *syncRecordingStream) SetHeader(metadata.MD) error { return nil }
func (s *syncRecordingStream) SendHeader(metadata.MD) error { return nil }
func (s *syncRecordingStream) SetTrailer(metadata.MD) {}
func (s *syncRecordingStream) SendMsg(any) error { return nil }
func (s *syncRecordingStream) RecvMsg(any) error { return nil }
func ackMsg() *proto.SyncMappingsRequest {
return &proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
}
}
func TestSendSnapshotSync_BatchesWithAcks(t *testing.T) {
const cluster = "cluster.example.com"
const batchSize = 3
const totalServices = 7 // 3 + 3 + 1 → 3 batches, 3 acks (one per batch, including final)
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
stream := &syncRecordingStream{
recvMsgs: []*proto.SyncMappingsRequest{ackMsg(), ackMsg(), ackMsg()},
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.NoError(t, err)
require.Len(t, stream.sent, 3, "should send ceil(7/3) = 3 batches")
assert.Len(t, stream.sent[0].Mapping, 3)
assert.False(t, stream.sent[0].InitialSyncComplete)
assert.Len(t, stream.sent[1].Mapping, 3)
assert.False(t, stream.sent[1].InitialSyncComplete)
assert.Len(t, stream.sent[2].Mapping, 1)
assert.True(t, stream.sent[2].InitialSyncComplete)
// All 3 acks consumed — including the final batch.
assert.Equal(t, 3, stream.recvIdx)
}
func TestSendSnapshotSync_SingleBatchWaitsForAck(t *testing.T) {
const cluster = "cluster.example.com"
const batchSize = 100
const totalServices = 5
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
stream := &syncRecordingStream{
recvMsgs: []*proto.SyncMappingsRequest{ackMsg()},
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.NoError(t, err)
require.Len(t, stream.sent, 1)
assert.Len(t, stream.sent[0].Mapping, totalServices)
assert.True(t, stream.sent[0].InitialSyncComplete)
assert.Equal(t, 1, stream.recvIdx, "final batch ack must be consumed")
}
func TestSendSnapshotSync_EmptySnapshot(t *testing.T) {
const cluster = "cluster.example.com"
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(nil, nil)
s := newSnapshotTestServer(t, 500)
s.serviceManager = mgr
stream := &syncRecordingStream{
recvMsgs: []*proto.SyncMappingsRequest{ackMsg()},
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.NoError(t, err)
require.Len(t, stream.sent, 1, "empty snapshot must still send sync-complete")
assert.Empty(t, stream.sent[0].Mapping)
assert.True(t, stream.sent[0].InitialSyncComplete)
assert.Equal(t, 1, stream.recvIdx, "empty snapshot ack must be consumed")
}
func TestSendSnapshotSync_MissingAckReturnsError(t *testing.T) {
const cluster = "cluster.example.com"
const batchSize = 2
const totalServices = 4 // 2 batches → 1 ack needed, but we provide none
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
// No acks available — Recv will return error.
stream := &syncRecordingStream{}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.Error(t, err)
assert.Contains(t, err.Error(), "receive ack")
// First batch should have been sent before the error.
require.Len(t, stream.sent, 1)
}
func TestSendSnapshotSync_WrongMessageInsteadOfAck(t *testing.T) {
const cluster = "cluster.example.com"
const batchSize = 2
const totalServices = 4
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
// Send an init message instead of an ack.
stream := &syncRecordingStream{
recvMsgs: []*proto.SyncMappingsRequest{
{Msg: &proto.SyncMappingsRequest_Init{Init: &proto.SyncMappingsInit{ProxyId: "bad"}}},
},
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.Error(t, err)
assert.Contains(t, err.Error(), "expected ack")
}
func TestSendSnapshotSync_BackPressureOrdering(t *testing.T) {
// Verify batches are sent strictly sequentially — batch N+1 is not sent
// until the ack for batch N is received, including the final batch.
const cluster = "cluster.example.com"
const batchSize = 2
const totalServices = 6 // 3 batches, 3 acks
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
var mu sync.Mutex
var events []string
// Build a stream that logs send/recv events so we can verify ordering.
ackCh := make(chan struct{}, 3)
stream := &orderTrackingStream{
mu: &mu,
events: &events,
ackCh: ackCh,
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
// Feed acks asynchronously after a short delay to simulate real proxy.
go func() {
for range 3 {
time.Sleep(10 * time.Millisecond)
ackCh <- struct{}{}
}
}()
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.NoError(t, err)
mu.Lock()
defer mu.Unlock()
// Expected: send, recv-ack, send, recv-ack, send, recv-ack.
require.Len(t, events, 6)
assert.Equal(t, "send", events[0])
assert.Equal(t, "recv", events[1])
assert.Equal(t, "send", events[2])
assert.Equal(t, "recv", events[3])
assert.Equal(t, "send", events[4])
assert.Equal(t, "recv", events[5])
}
// orderTrackingStream logs "send" and "recv" events and blocks Recv until
// an ack is signaled via ackCh.
type orderTrackingStream struct {
grpc.ServerStream
mu *sync.Mutex
events *[]string
ackCh chan struct{}
}
func (s *orderTrackingStream) Send(_ *proto.SyncMappingsResponse) error {
s.mu.Lock()
*s.events = append(*s.events, "send")
s.mu.Unlock()
return nil
}
func (s *orderTrackingStream) Recv() (*proto.SyncMappingsRequest, error) {
<-s.ackCh
s.mu.Lock()
*s.events = append(*s.events, "recv")
s.mu.Unlock()
return ackMsg(), nil
}
func (s *orderTrackingStream) Context() context.Context { return context.Background() }
func (s *orderTrackingStream) SetHeader(metadata.MD) error { return nil }
func (s *orderTrackingStream) SendHeader(metadata.MD) error { return nil }
func (s *orderTrackingStream) SetTrailer(metadata.MD) {}
func (s *orderTrackingStream) SendMsg(any) error { return nil }
func (s *orderTrackingStream) RecvMsg(any) error { return nil }
func TestSendSnapshotSync_TokensGeneratedPerBatch(t *testing.T) {
const cluster = "cluster.example.com"
const batchSize = 2
const totalServices = 4
const ttl = 100 * time.Millisecond
const ackDelay = 200 * time.Millisecond
ctrl := gomock.NewController(t)
mgr := rpservice.NewMockManager(ctrl)
mgr.EXPECT().GetGlobalServices(gomock.Any()).Return(makeServices(totalServices, cluster), nil)
s := newSnapshotTestServer(t, batchSize)
s.serviceManager = mgr
s.tokenTTL = ttl
// Build a stream that validates tokens immediately on Send, then
// delays the ack to ensure the next batch's tokens are generated fresh.
var validateErrs []error
ackCh := make(chan struct{}, 2)
stream := &tokenValidatingSyncStream{
tokenStore: s.tokenStore,
validateErrs: &validateErrs,
ackCh: ackCh,
}
conn := &proxyConnection{
proxyID: "proxy-a",
address: cluster,
syncStream: stream,
}
go func() {
// Delay first ack so that if tokens were all generated upfront they'd expire.
time.Sleep(ackDelay)
ackCh <- struct{}{}
// Final batch ack — immediate.
ackCh <- struct{}{}
}()
err := s.sendSnapshotSync(context.Background(), conn, stream)
require.NoError(t, err)
require.Empty(t, validateErrs,
"tokens must remain valid: per-batch generation guarantees freshness")
}
type tokenValidatingSyncStream struct {
grpc.ServerStream
tokenStore *OneTimeTokenStore
validateErrs *[]error
ackCh chan struct{}
}
func (s *tokenValidatingSyncStream) Send(m *proto.SyncMappingsResponse) error {
for _, mapping := range m.Mapping {
if err := s.tokenStore.ValidateAndConsume(mapping.AuthToken, mapping.AccountId, mapping.Id); err != nil {
*s.validateErrs = append(*s.validateErrs, fmt.Errorf("svc %s: %w", mapping.Id, err))
}
}
return nil
}
func (s *tokenValidatingSyncStream) Recv() (*proto.SyncMappingsRequest, error) {
<-s.ackCh
return ackMsg(), nil
}
func (s *tokenValidatingSyncStream) Context() context.Context { return context.Background() }
func (s *tokenValidatingSyncStream) SetHeader(metadata.MD) error { return nil }
func (s *tokenValidatingSyncStream) SendHeader(metadata.MD) error { return nil }
func (s *tokenValidatingSyncStream) SetTrailer(metadata.MD) {}
func (s *tokenValidatingSyncStream) SendMsg(any) error { return nil }
func (s *tokenValidatingSyncStream) RecvMsg(any) error { return nil }
func TestConnectionSendResponse_RoutesToSyncStream(t *testing.T) {
stream := &syncRecordingStream{}
conn := &proxyConnection{
syncStream: stream,
}
resp := &proto.GetMappingUpdateResponse{
Mapping: []*proto.ProxyMapping{
{Id: "svc-1", AccountId: "acct-1", Domain: "example.com"},
},
InitialSyncComplete: true,
}
err := conn.sendResponse(resp)
require.NoError(t, err)
require.Len(t, stream.sent, 1)
assert.Len(t, stream.sent[0].Mapping, 1)
assert.Equal(t, "svc-1", stream.sent[0].Mapping[0].Id)
assert.True(t, stream.sent[0].InitialSyncComplete)
}
func TestConnectionSendResponse_RoutesToLegacyStream(t *testing.T) {
stream := &recordingStream{}
conn := &proxyConnection{
stream: stream,
}
resp := &proto.GetMappingUpdateResponse{
Mapping: []*proto.ProxyMapping{
{Id: "svc-2", AccountId: "acct-2"},
},
}
err := conn.sendResponse(resp)
require.NoError(t, err)
require.Len(t, stream.messages, 1)
assert.Equal(t, "svc-2", stream.messages[0].Mapping[0].Id)
}

View File

@@ -1319,7 +1319,7 @@ func Test_NetworkRouters_Update(t *testing.T) {
}, },
}, },
{ {
name: "Update non-existing router creates it", name: "Update non-existing router returns not found",
networkId: "testNetworkId", networkId: "testNetworkId",
routerId: "nonExistingRouterId", routerId: "nonExistingRouterId",
requestBody: &api.NetworkRouterRequest{ requestBody: &api.NetworkRouterRequest{
@@ -1328,11 +1328,7 @@ func Test_NetworkRouters_Update(t *testing.T) {
Metric: 100, Metric: 100,
Enabled: true, Enabled: true,
}, },
expectedStatus: http.StatusOK, expectedStatus: http.StatusNotFound,
verifyResponse: func(t *testing.T, router *api.NetworkRouter) {
t.Helper()
assert.Equal(t, "nonExistingRouterId", router.Id)
},
}, },
{ {
name: "Update router with both peer and peer_groups", name: "Update router with both peer and peer_groups",

View File

@@ -34,8 +34,11 @@ func Test_GetAllNetworksReturnsNetworks(t *testing.T) {
networks, err := manager.GetAllNetworks(ctx, accountID, userID) networks, err := manager.GetAllNetworks(ctx, accountID, userID)
require.NoError(t, err) require.NoError(t, err)
require.Len(t, networks, 1) ids := make([]string, 0, len(networks))
require.Equal(t, "testNetworkId", networks[0].ID) for _, n := range networks {
ids = append(ids, n.ID)
}
require.ElementsMatch(t, []string{"testNetworkId", "secondNetworkId"}, ids)
} }
func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) { func Test_GetAllNetworksReturnsPermissionDenied(t *testing.T) {

View File

@@ -102,7 +102,7 @@ func (m *managerImpl) CreateRouter(ctx context.Context, userID string, router *t
router.ID = xid.New().String() router.ID = xid.New().String()
err = transaction.SaveNetworkRouter(ctx, router) err = transaction.CreateNetworkRouter(ctx, router)
if err != nil { if err != nil {
return fmt.Errorf("failed to create network router: %w", err) return fmt.Errorf("failed to create network router: %w", err)
} }
@@ -162,11 +162,20 @@ func (m *managerImpl) UpdateRouter(ctx context.Context, userID string, router *t
return fmt.Errorf("failed to get network: %w", err) return fmt.Errorf("failed to get network: %w", err)
} }
if network.ID != router.NetworkID { existing, err := transaction.GetNetworkRouterByID(ctx, store.LockingStrengthUpdate, router.AccountID, router.ID)
if err != nil {
return fmt.Errorf("failed to get network router: %w", err)
}
if existing.AccountID != router.AccountID {
return status.NewNetworkRouterNotFoundError(router.ID)
}
if existing.NetworkID != router.NetworkID {
return status.NewRouterNotPartOfNetworkError(router.ID, router.NetworkID) return status.NewRouterNotPartOfNetworkError(router.ID, router.NetworkID)
} }
err = transaction.SaveNetworkRouter(ctx, router) err = transaction.UpdateNetworkRouter(ctx, router)
if err != nil { if err != nil {
return fmt.Errorf("failed to update network router: %w", err) return fmt.Errorf("failed to update network router: %w", err)
} }

View File

@@ -195,6 +195,7 @@ func Test_UpdateRouterSuccessfully(t *testing.T) {
if err != nil { if err != nil {
require.NoError(t, err) require.NoError(t, err)
} }
router.ID = "testRouterId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir()) s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil { if err != nil {
@@ -210,6 +211,102 @@ func Test_UpdateRouterSuccessfully(t *testing.T) {
require.Equal(t, router.Metric, updatedRouter.Metric) require.Equal(t, router.Metric, updatedRouter.Metric)
} }
func Test_UpdateRouterRejectsCrossAccountID(t *testing.T) {
ctx := context.Background()
userID := "testAdminId"
// Admin of testAccountId tries to update a router that belongs to otherAccountId
// by passing the other account's router ID through the URL.
router, err := types.NewNetworkRouter("testAccountId", "testNetworkId", "testPeerId", []string{}, false, 1, true)
if err != nil {
require.NoError(t, err)
}
router.ID = "otherRouterId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(cleanUp)
permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am)
updatedRouter, err := manager.UpdateRouter(ctx, userID, router)
require.Error(t, err)
require.Nil(t, updatedRouter)
// The other account's router must be untouched.
stored, err := s.GetNetworkRouterByID(ctx, store.LockingStrengthNone, "otherAccountId", "otherRouterId")
require.NoError(t, err)
require.Equal(t, "otherAccountId", stored.AccountID)
require.Equal(t, "otherNetworkId", stored.NetworkID)
require.Equal(t, "otherPeer", stored.Peer)
require.Equal(t, 1, stored.Metric)
}
func Test_CreateRouterRejectsCrossAccountID(t *testing.T) {
ctx := context.Background()
userID := "testAdminId"
// Admin of testAccountId tries to create a router in otherAccountId's network.
// The permission check is on router.AccountID (their own), but the network
// lookup must fail because (testAccountId, otherNetworkId) does not exist.
router, err := types.NewNetworkRouter("testAccountId", "otherNetworkId", "testPeerId", []string{}, false, 1, true)
if err != nil {
require.NoError(t, err)
}
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(cleanUp)
permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am)
createdRouter, err := manager.CreateRouter(ctx, userID, router)
require.Error(t, err)
require.Nil(t, createdRouter)
// No router should have been created in either account's scope under otherNetworkId.
routersInOther, err := s.GetNetworkRoutersByNetID(ctx, store.LockingStrengthNone, "otherAccountId", "otherNetworkId")
require.NoError(t, err)
require.Len(t, routersInOther, 1)
require.Equal(t, "otherRouterId", routersInOther[0].ID)
}
func Test_UpdateRouterRejectsNetworkMismatch(t *testing.T) {
ctx := context.Background()
userID := "testAdminId"
// The router exists in testNetworkId, but the caller submits secondNetworkId
// (a different network in the same account). The update must be refused.
router, err := types.NewNetworkRouter("testAccountId", "secondNetworkId", "testPeerId", []string{}, false, 1, true)
if err != nil {
require.NoError(t, err)
}
router.ID = "testRouterId"
s, cleanUp, err := store.NewTestStoreFromSQL(context.Background(), "../../testdata/networks.sql", t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(cleanUp)
permissionsManager := permissions.NewManager(s)
am := mock_server.MockAccountManager{}
manager := NewManager(s, permissionsManager, &am)
updatedRouter, err := manager.UpdateRouter(ctx, userID, router)
require.Error(t, err)
require.Nil(t, updatedRouter)
stored, err := s.GetNetworkRouterByID(ctx, store.LockingStrengthNone, "testAccountId", "testRouterId")
require.NoError(t, err)
require.Equal(t, "testNetworkId", stored.NetworkID)
}
func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) { func Test_UpdateRouterFailsWithPermissionDenied(t *testing.T) {
ctx := context.Background() ctx := context.Background()
userID := "testUserId" userID := "testUserId"

View File

@@ -4315,11 +4315,27 @@ func (s *SqlStore) GetNetworkRouterByID(ctx context.Context, lockStrength Lockin
return netRouter, nil return netRouter, nil
} }
func (s *SqlStore) SaveNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error { func (s *SqlStore) CreateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error {
result := s.db.Save(router) if err := s.db.Create(router).Error; err != nil {
log.WithContext(ctx).Errorf("failed to create network router in store: %v", err)
return status.Errorf(status.Internal, "failed to create network router in store")
}
return nil
}
func (s *SqlStore) UpdateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error {
result := s.db.
Select("*").
Where(accountAndIDQueryCondition, router.AccountID, router.ID).
Updates(router)
if result.Error != nil { if result.Error != nil {
log.WithContext(ctx).Errorf("failed to save network router to store: %v", result.Error) log.WithContext(ctx).Errorf("failed to update network router in store: %v", result.Error)
return status.Errorf(status.Internal, "failed to save network router to store") return status.Errorf(status.Internal, "failed to update network router in store")
}
if result.RowsAffected == 0 {
return status.NewNetworkRouterNotFoundError(router.ID)
} }
return nil return nil

View File

@@ -2399,7 +2399,7 @@ func TestSqlStore_GetNetworkRouterByID(t *testing.T) {
} }
} }
func TestSqlStore_SaveNetworkRouter(t *testing.T) { func TestSqlStore_CreateNetworkRouter(t *testing.T) {
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir()) store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir())
t.Cleanup(cleanup) t.Cleanup(cleanup)
require.NoError(t, err) require.NoError(t, err)
@@ -2410,7 +2410,7 @@ func TestSqlStore_SaveNetworkRouter(t *testing.T) {
netRouter, err := routerTypes.NewNetworkRouter(accountID, networkID, "", []string{"net-router-grp"}, true, 0, true) netRouter, err := routerTypes.NewNetworkRouter(accountID, networkID, "", []string{"net-router-grp"}, true, 0, true)
require.NoError(t, err) require.NoError(t, err)
err = store.SaveNetworkRouter(context.Background(), netRouter) err = store.CreateNetworkRouter(context.Background(), netRouter)
require.NoError(t, err) require.NoError(t, err)
savedNetRouter, err := store.GetNetworkRouterByID(context.Background(), LockingStrengthNone, accountID, netRouter.ID) savedNetRouter, err := store.GetNetworkRouterByID(context.Background(), LockingStrengthNone, accountID, netRouter.ID)
@@ -2418,6 +2418,39 @@ func TestSqlStore_SaveNetworkRouter(t *testing.T) {
require.Equal(t, netRouter, savedNetRouter) require.Equal(t, netRouter, savedNetRouter)
} }
func TestSqlStore_UpdateNetworkRouter(t *testing.T) {
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir())
t.Cleanup(cleanup)
require.NoError(t, err)
accountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b"
networkID := "ct286bi7qv930dsrrug0"
routerID := "ctc20ji7qv9ck2sebc80"
netRouter := &routerTypes.NetworkRouter{
ID: routerID,
AccountID: accountID,
NetworkID: networkID,
Peer: "",
PeerGroups: []string{"net-router-grp"},
Masquerade: true,
Metric: 42,
Enabled: true,
}
err = store.UpdateNetworkRouter(context.Background(), netRouter)
require.NoError(t, err)
savedNetRouter, err := store.GetNetworkRouterByID(context.Background(), LockingStrengthNone, accountID, routerID)
require.NoError(t, err)
require.Equal(t, netRouter, savedNetRouter)
// Updating a router under a different account must not match any row.
netRouter.AccountID = "non-existent-account"
err = store.UpdateNetworkRouter(context.Background(), netRouter)
require.Error(t, err)
}
func TestSqlStore_DeleteNetworkRouter(t *testing.T) { func TestSqlStore_DeleteNetworkRouter(t *testing.T) {
store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir()) store, cleanup, err := NewTestStoreFromSQL(context.Background(), "../testdata/store.sql", t.TempDir())
t.Cleanup(cleanup) t.Cleanup(cleanup)

View File

@@ -228,7 +228,8 @@ type Store interface {
GetNetworkRoutersByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*routerTypes.NetworkRouter, error) GetNetworkRoutersByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*routerTypes.NetworkRouter, error)
GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error) GetNetworkRoutersByAccountID(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*routerTypes.NetworkRouter, error)
GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error) GetNetworkRouterByID(ctx context.Context, lockStrength LockingStrength, accountID, routerID string) (*routerTypes.NetworkRouter, error)
SaveNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error CreateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error
UpdateNetworkRouter(ctx context.Context, router *routerTypes.NetworkRouter) error
DeleteNetworkRouter(ctx context.Context, accountID, routerID string) error DeleteNetworkRouter(ctx context.Context, accountID, routerID string) error
GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*resourceTypes.NetworkResource, error) GetNetworkResourcesByNetID(ctx context.Context, lockStrength LockingStrength, accountID, netID string) ([]*resourceTypes.NetworkResource, error)

View File

@@ -310,6 +310,20 @@ func (mr *MockStoreMockRecorder) CreateGroups(ctx, accountID, groups interface{}
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroups", reflect.TypeOf((*MockStore)(nil).CreateGroups), ctx, accountID, groups) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGroups", reflect.TypeOf((*MockStore)(nil).CreateGroups), ctx, accountID, groups)
} }
// CreateNetworkRouter mocks base method.
func (m *MockStore) CreateNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CreateNetworkRouter", ctx, router)
ret0, _ := ret[0].(error)
return ret0
}
// CreateNetworkRouter indicates an expected call of CreateNetworkRouter.
func (mr *MockStoreMockRecorder) CreateNetworkRouter(ctx, router interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNetworkRouter", reflect.TypeOf((*MockStore)(nil).CreateNetworkRouter), ctx, router)
}
// CreatePeerJob mocks base method. // CreatePeerJob mocks base method.
func (m *MockStore) CreatePeerJob(ctx context.Context, job *types2.Job) error { func (m *MockStore) CreatePeerJob(ctx context.Context, job *types2.Job) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -2612,6 +2626,36 @@ func (mr *MockStoreMockRecorder) MarkPATUsed(ctx, patID interface{}) *gomock.Cal
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPATUsed", reflect.TypeOf((*MockStore)(nil).MarkPATUsed), ctx, patID) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPATUsed", reflect.TypeOf((*MockStore)(nil).MarkPATUsed), ctx, patID)
} }
// MarkPeerConnectedIfNewerSession mocks base method.
func (m *MockStore) MarkPeerConnectedIfNewerSession(ctx context.Context, accountID, peerID string, newSessionStartedAt int64) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkPeerConnectedIfNewerSession", ctx, accountID, peerID, newSessionStartedAt)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MarkPeerConnectedIfNewerSession indicates an expected call of MarkPeerConnectedIfNewerSession.
func (mr *MockStoreMockRecorder) MarkPeerConnectedIfNewerSession(ctx, accountID, peerID, newSessionStartedAt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerConnectedIfNewerSession", reflect.TypeOf((*MockStore)(nil).MarkPeerConnectedIfNewerSession), ctx, accountID, peerID, newSessionStartedAt)
}
// MarkPeerDisconnectedIfSameSession mocks base method.
func (m *MockStore) MarkPeerDisconnectedIfSameSession(ctx context.Context, accountID, peerID string, sessionStartedAt int64) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkPeerDisconnectedIfSameSession", ctx, accountID, peerID, sessionStartedAt)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MarkPeerDisconnectedIfSameSession indicates an expected call of MarkPeerDisconnectedIfSameSession.
func (mr *MockStoreMockRecorder) MarkPeerDisconnectedIfSameSession(ctx, accountID, peerID, sessionStartedAt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerDisconnectedIfSameSession", reflect.TypeOf((*MockStore)(nil).MarkPeerDisconnectedIfSameSession), ctx, accountID, peerID, sessionStartedAt)
}
// MarkPendingJobsAsFailed mocks base method. // MarkPendingJobsAsFailed mocks base method.
func (m *MockStore) MarkPendingJobsAsFailed(ctx context.Context, accountID, peerID, jobID, reason string) error { func (m *MockStore) MarkPendingJobsAsFailed(ctx context.Context, accountID, peerID, jobID, reason string) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -2822,20 +2866,6 @@ func (mr *MockStoreMockRecorder) SaveNetworkResource(ctx, resource interface{})
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNetworkResource", reflect.TypeOf((*MockStore)(nil).SaveNetworkResource), ctx, resource) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNetworkResource", reflect.TypeOf((*MockStore)(nil).SaveNetworkResource), ctx, resource)
} }
// SaveNetworkRouter mocks base method.
func (m *MockStore) SaveNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SaveNetworkRouter", ctx, router)
ret0, _ := ret[0].(error)
return ret0
}
// SaveNetworkRouter indicates an expected call of SaveNetworkRouter.
func (mr *MockStoreMockRecorder) SaveNetworkRouter(ctx, router interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveNetworkRouter", reflect.TypeOf((*MockStore)(nil).SaveNetworkRouter), ctx, router)
}
// SavePAT mocks base method. // SavePAT mocks base method.
func (m *MockStore) SavePAT(ctx context.Context, pat *types2.PersonalAccessToken) error { func (m *MockStore) SavePAT(ctx context.Context, pat *types2.PersonalAccessToken) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -2892,36 +2922,6 @@ func (mr *MockStoreMockRecorder) SavePeerStatus(ctx, accountID, peerID, status i
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePeerStatus", reflect.TypeOf((*MockStore)(nil).SavePeerStatus), ctx, accountID, peerID, status) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SavePeerStatus", reflect.TypeOf((*MockStore)(nil).SavePeerStatus), ctx, accountID, peerID, status)
} }
// MarkPeerConnectedIfNewerSession mocks base method.
func (m *MockStore) MarkPeerConnectedIfNewerSession(ctx context.Context, accountID, peerID string, newSessionStartedAt int64) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkPeerConnectedIfNewerSession", ctx, accountID, peerID, newSessionStartedAt)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MarkPeerConnectedIfNewerSession indicates an expected call of MarkPeerConnectedIfNewerSession.
func (mr *MockStoreMockRecorder) MarkPeerConnectedIfNewerSession(ctx, accountID, peerID, newSessionStartedAt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerConnectedIfNewerSession", reflect.TypeOf((*MockStore)(nil).MarkPeerConnectedIfNewerSession), ctx, accountID, peerID, newSessionStartedAt)
}
// MarkPeerDisconnectedIfSameSession mocks base method.
func (m *MockStore) MarkPeerDisconnectedIfSameSession(ctx context.Context, accountID, peerID string, sessionStartedAt int64) (bool, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "MarkPeerDisconnectedIfSameSession", ctx, accountID, peerID, sessionStartedAt)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// MarkPeerDisconnectedIfSameSession indicates an expected call of MarkPeerDisconnectedIfSameSession.
func (mr *MockStoreMockRecorder) MarkPeerDisconnectedIfSameSession(ctx, accountID, peerID, sessionStartedAt interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkPeerDisconnectedIfSameSession", reflect.TypeOf((*MockStore)(nil).MarkPeerDisconnectedIfSameSession), ctx, accountID, peerID, sessionStartedAt)
}
// SavePolicy mocks base method. // SavePolicy mocks base method.
func (m *MockStore) SavePolicy(ctx context.Context, policy *types2.Policy) error { func (m *MockStore) SavePolicy(ctx context.Context, policy *types2.Policy) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()
@@ -3173,6 +3173,20 @@ func (mr *MockStoreMockRecorder) UpdateGroups(ctx, accountID, groups interface{}
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGroups", reflect.TypeOf((*MockStore)(nil).UpdateGroups), ctx, accountID, groups) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateGroups", reflect.TypeOf((*MockStore)(nil).UpdateGroups), ctx, accountID, groups)
} }
// UpdateNetworkRouter mocks base method.
func (m *MockStore) UpdateNetworkRouter(ctx context.Context, router *types0.NetworkRouter) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateNetworkRouter", ctx, router)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateNetworkRouter indicates an expected call of UpdateNetworkRouter.
func (mr *MockStoreMockRecorder) UpdateNetworkRouter(ctx, router interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNetworkRouter", reflect.TypeOf((*MockStore)(nil).UpdateNetworkRouter), ctx, router)
}
// UpdateProxyHeartbeat mocks base method. // UpdateProxyHeartbeat mocks base method.
func (m *MockStore) UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error { func (m *MockStore) UpdateProxyHeartbeat(ctx context.Context, p *proxy.Proxy) error {
m.ctrl.T.Helper() m.ctrl.T.Helper()

View File

@@ -9,9 +9,13 @@ INSERT INTO peers VALUES('testPeerId','testAccountId','5rvhvriKJZ3S9oxYToVj5TzDM
CREATE TABLE `networks` (`id` text,`account_id` text,`name` text,`description` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_networks` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); CREATE TABLE `networks` (`id` text,`account_id` text,`name` text,`description` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_networks` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
INSERT INTO networks VALUES('testNetworkId','testAccountId','some-name','some-description'); INSERT INTO networks VALUES('testNetworkId','testAccountId','some-name','some-description');
INSERT INTO networks VALUES('secondNetworkId','testAccountId','second-name','second-description');
CREATE TABLE `network_routers` (`id` text,`network_id` text,`account_id` text,`peer` text,`peer_groups` text,`masquerade` numeric,`metric` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_routers` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); CREATE TABLE `network_routers` (`id` text,`network_id` text,`account_id` text,`peer` text,`peer_groups` text,`masquerade` numeric,`metric` integer,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_routers` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
INSERT INTO network_routers VALUES('testRouterId','testNetworkId','testAccountId','','["csquuo4jcko732k1ag00"]',0,9999); INSERT INTO network_routers VALUES('testRouterId','testNetworkId','testAccountId','','["csquuo4jcko732k1ag00"]',0,9999);
INSERT INTO accounts VALUES('otherAccountId','','2024-10-02 16:01:38.000000000+00:00','other.com','private',1,'otherNetworkIdentifier','{"IP":"100.65.0.0","Mask":"//8AAA=="}','',0,'[]',0,86400000000000,0,0,0,'',NULL,NULL,NULL);
INSERT INTO networks VALUES('otherNetworkId','otherAccountId','other-net','other-description');
INSERT INTO network_routers VALUES('otherRouterId','otherNetworkId','otherAccountId','otherPeer',NULL,0,1);
CREATE TABLE `network_resources` (`id` text,`network_id` text,`account_id` text,`name` text,`description` text,`type` text,`address` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_resources` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`)); CREATE TABLE `network_resources` (`id` text,`network_id` text,`account_id` text,`name` text,`description` text,`type` text,`address` text,PRIMARY KEY (`id`),CONSTRAINT `fk_accounts_network_resources` FOREIGN KEY (`account_id`) REFERENCES `accounts`(`id`));
INSERT INTO network_resources VALUES('testResourceId','testNetworkId','testAccountId','some-name','some-description','host','3.3.3.3/32'); INSERT INTO network_resources VALUES('testResourceId','testNetworkId','testAccountId','some-name','some-description','host','3.3.3.3/32');

14
proto-tools.env Normal file
View File

@@ -0,0 +1,14 @@
# Pinned protobuf code-generation toolchain.
# Sourced by every proto generate.sh and the proto-generation-check CI workflow.
# When bumping a version, regenerate all *.pb.go files in the same PR.
# protoc release tag from https://github.com/protocolbuffers/protobuf/releases
# `protoc --version` reports `libprotoc ${PROTOC_VERSION}`.
# Generated pb.go headers embed `protoc v6.${PROTOC_VERSION}`.
PROTOC_VERSION="33.1"
# google.golang.org/protobuf/cmd/protoc-gen-go
PROTOC_GEN_GO_VERSION="v1.36.6"
# google.golang.org/grpc/cmd/protoc-gen-go-grpc
PROTOC_GEN_GO_GRPC_VERSION="v1.6.1"

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"testing" "testing"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -59,7 +60,7 @@ func TestHandleMappingStream_SyncCompleteFlag(t *testing.T) {
} }
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, syncDone, "initial sync should be marked done when flag is set") assert.True(t, syncDone, "initial sync should be marked done when flag is set")
} }
@@ -79,7 +80,7 @@ func TestHandleMappingStream_NoSyncFlagDoesNotMarkDone(t *testing.T) {
} }
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, syncDone, "initial sync should not be marked done without flag") assert.False(t, syncDone, "initial sync should not be marked done without flag")
} }
@@ -97,7 +98,7 @@ func TestHandleMappingStream_NilHealthChecker(t *testing.T) {
} }
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, syncDone, "sync done flag should be set even without health checker") assert.True(t, syncDone, "sync done flag should be set even without health checker")
} }

View File

@@ -25,6 +25,11 @@ type Metrics struct {
backendDuration metric.Int64Histogram backendDuration metric.Int64Histogram
certificateIssueDuration metric.Int64Histogram certificateIssueDuration metric.Int64Histogram
// Management sync metrics.
snapshotSyncDuration metric.Int64Histogram
snapshotBatchDuration metric.Int64Histogram
addPeerDuration metric.Int64Histogram
// L4 service-level metrics. // L4 service-level metrics.
l4Services metric.Int64UpDownCounter l4Services metric.Int64UpDownCounter
@@ -54,6 +59,9 @@ func New(ctx context.Context, meter metric.Meter) (*Metrics, error) {
if err := m.initHTTPMetrics(meter); err != nil { if err := m.initHTTPMetrics(meter); err != nil {
return nil, err return nil, err
} }
if err := m.initSyncMetrics(meter); err != nil {
return nil, err
}
if err := m.initL4Metrics(meter); err != nil { if err := m.initL4Metrics(meter); err != nil {
return nil, err return nil, err
} }
@@ -126,6 +134,59 @@ func (m *Metrics) initHTTPMetrics(meter metric.Meter) error {
return err return err
} }
func (m *Metrics) initSyncMetrics(meter metric.Meter) error {
var err error
m.snapshotSyncDuration, err = meter.Int64Histogram(
"proxy.sync.snapshot.duration.ms",
metric.WithUnit("milliseconds"),
metric.WithDescription("Duration from management connect until the initial snapshot sync is complete"),
metric.WithExplicitBucketBoundaries(100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 120000, 300000),
)
if err != nil {
return err
}
m.snapshotBatchDuration, err = meter.Int64Histogram(
"proxy.sync.batch.duration.ms",
metric.WithUnit("milliseconds"),
metric.WithDescription("Duration to process a single mapping batch during initial snapshot sync"),
metric.WithExplicitBucketBoundaries(100, 250, 500, 1000, 2500, 5000, 10000, 30000, 60000, 120000, 300000),
)
if err != nil {
return err
}
m.addPeerDuration, err = meter.Int64Histogram(
"proxy.peer.add.duration.ms",
metric.WithUnit("milliseconds"),
metric.WithDescription("Duration to add a peer for an account (keygen + gRPC CreateProxyPeer + embed.New)"),
metric.WithExplicitBucketBoundaries(10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000),
)
return err
}
// RecordSnapshotSyncDuration records the total time from connect to sync-complete.
func (m *Metrics) RecordSnapshotSyncDuration(d time.Duration) {
m.snapshotSyncDuration.Record(m.ctx, d.Milliseconds())
}
// RecordSnapshotBatchDuration records the time to process one mapping batch during initial sync.
func (m *Metrics) RecordSnapshotBatchDuration(d time.Duration) {
m.snapshotBatchDuration.Record(m.ctx, d.Milliseconds())
}
// RecordAddPeerDuration records the time to create a new peer for an account.
func (m *Metrics) RecordAddPeerDuration(d time.Duration, err error) {
result := "success"
if err != nil {
result = "error"
}
m.addPeerDuration.Record(m.ctx, d.Milliseconds(), metric.WithAttributes(
attribute.String("result", result),
))
}
func (m *Metrics) initL4Metrics(meter metric.Meter) error { func (m *Metrics) initL4Metrics(meter metric.Meter) error {
var err error var err error

View File

@@ -76,6 +76,11 @@ type clientEntry struct {
services map[ServiceKey]serviceInfo services map[ServiceKey]serviceInfo
createdAt time.Time createdAt time.Time
started bool started bool
// ready is closed once the client has been fully initialized.
// Callers that find a pending entry wait on this channel before
// accessing the client. A nil initErr means success.
ready chan struct{}
initErr error
// Per-backend in-flight limiting keyed by target host:port. // Per-backend in-flight limiting keyed by target host:port.
// TODO: clean up stale entries when backend targets change. // TODO: clean up stale entries when backend targets change.
inflightMu sync.Mutex inflightMu sync.Mutex
@@ -137,6 +142,11 @@ type NetBird struct {
clients map[types.AccountID]*clientEntry clients map[types.AccountID]*clientEntry
initLogOnce sync.Once initLogOnce sync.Once
statusNotifier statusNotifier statusNotifier statusNotifier
// OnAddPeer, when set, is called after AddPeer completes for a new account
// (i.e. when a new client was actually created, not when an existing one
// was reused). The duration covers keygen + gRPC CreateProxyPeer + embed.New.
OnAddPeer func(d time.Duration, err error)
} }
// ClientDebugInfo contains debug information about a client. // ClientDebugInfo contains debug information about a client.
@@ -157,6 +167,9 @@ type skipTLSVerifyContextKey struct{}
// AddPeer registers a service for an account. If the account doesn't have a client yet, // AddPeer registers a service for an account. If the account doesn't have a client yet,
// one is created by authenticating with the management server using the provided token. // one is created by authenticating with the management server using the provided token.
// Multiple services can share the same client. // Multiple services can share the same client.
//
// Client creation (WG keygen, gRPC, embed.New) runs without holding clientsMux
// so that concurrent AddPeer calls for different accounts execute in parallel.
func (n *NetBird) AddPeer(ctx context.Context, accountID types.AccountID, key ServiceKey, authToken string, serviceID types.ServiceID) error { func (n *NetBird) AddPeer(ctx context.Context, accountID types.AccountID, key ServiceKey, authToken string, serviceID types.ServiceID) error {
si := serviceInfo{serviceID: serviceID} si := serviceInfo{serviceID: serviceID}
@@ -164,10 +177,23 @@ func (n *NetBird) AddPeer(ctx context.Context, accountID types.AccountID, key Se
entry, exists := n.clients[accountID] entry, exists := n.clients[accountID]
if exists { if exists {
ready := entry.ready
entry.services[key] = si entry.services[key] = si
started := entry.started started := entry.started
n.clientsMux.Unlock() n.clientsMux.Unlock()
// If the entry is still being initialized by another goroutine, wait.
if ready != nil {
select {
case <-ready:
case <-ctx.Done():
return ctx.Err()
}
if entry.initErr != nil {
return fmt.Errorf("peer initialization failed: %w", entry.initErr)
}
}
n.logger.WithFields(log.Fields{ n.logger.WithFields(log.Fields{
"account_id": accountID, "account_id": accountID,
"service_key": key, "service_key": key,
@@ -184,15 +210,43 @@ func (n *NetBird) AddPeer(ctx context.Context, accountID types.AccountID, key Se
return nil return nil
} }
entry, err := n.createClientEntry(ctx, accountID, key, authToken, si) // Insert a placeholder so other goroutines calling AddPeer for the same
// account will wait on the ready channel instead of starting a second
// client creation.
entry = &clientEntry{
services: map[ServiceKey]serviceInfo{key: si},
ready: make(chan struct{}),
}
n.clients[accountID] = entry
n.clientsMux.Unlock()
createStart := time.Now()
created, err := n.createClientEntry(ctx, accountID, key, authToken, si)
if n.OnAddPeer != nil {
n.OnAddPeer(time.Since(createStart), err)
}
if err != nil { if err != nil {
entry.initErr = err
close(entry.ready)
n.clientsMux.Lock()
delete(n.clients, accountID)
n.clientsMux.Unlock() n.clientsMux.Unlock()
return err return err
} }
n.clients[accountID] = entry // Transfer any services that were registered by concurrent AddPeer calls
// while we were creating the client.
n.clientsMux.Lock()
for k, v := range entry.services {
created.services[k] = v
}
created.ready = nil
n.clients[accountID] = created
n.clientsMux.Unlock() n.clientsMux.Unlock()
close(entry.ready)
n.logger.WithFields(log.Fields{ n.logger.WithFields(log.Fields{
"account_id": accountID, "account_id": accountID,
"service_key": key, "service_key": key,
@@ -200,13 +254,13 @@ func (n *NetBird) AddPeer(ctx context.Context, accountID types.AccountID, key Se
// Attempt to start the client in the background; if this fails we will // Attempt to start the client in the background; if this fails we will
// retry on the first request via RoundTrip. // retry on the first request via RoundTrip.
go n.runClientStartup(ctx, accountID, entry.client) go n.runClientStartup(ctx, accountID, created.client)
return nil return nil
} }
// createClientEntry generates a WireGuard keypair, authenticates with management, // createClientEntry generates a WireGuard keypair, authenticates with management,
// and creates an embedded NetBird client. Must be called with clientsMux held. // and creates an embedded NetBird client.
func (n *NetBird) createClientEntry(ctx context.Context, accountID types.AccountID, key ServiceKey, authToken string, si serviceInfo) (*clientEntry, error) { func (n *NetBird) createClientEntry(ctx context.Context, accountID types.AccountID, key ServiceKey, authToken string, si serviceInfo) (*clientEntry, error) {
serviceID := si.serviceID serviceID := si.serviceID
n.logger.WithFields(log.Fields{ n.logger.WithFields(log.Fields{

View File

@@ -0,0 +1,300 @@
package proxy
import (
"context"
"fmt"
"net"
"testing"
"time"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc"
"github.com/netbirdio/netbird/proxy/internal/auth"
"github.com/netbirdio/netbird/proxy/internal/conntrack"
"github.com/netbirdio/netbird/proxy/internal/crowdsec"
proxymetrics "github.com/netbirdio/netbird/proxy/internal/metrics"
"github.com/netbirdio/netbird/proxy/internal/proxy"
"github.com/netbirdio/netbird/proxy/internal/roundtrip"
nbtcp "github.com/netbirdio/netbird/proxy/internal/tcp"
"github.com/netbirdio/netbird/proxy/internal/types"
udprelay "github.com/netbirdio/netbird/proxy/internal/udp"
"github.com/netbirdio/netbird/shared/management/proto"
"go.opentelemetry.io/otel/metric/noop"
)
// latencyMockClient simulates realistic gRPC latency for management calls.
type latencyMockClient struct {
proto.ProxyServiceClient
createPeerDelay time.Duration
statusUpdateDelay time.Duration
}
func (m *latencyMockClient) SendStatusUpdate(ctx context.Context, _ *proto.SendStatusUpdateRequest, _ ...grpc.CallOption) (*proto.SendStatusUpdateResponse, error) {
if m.statusUpdateDelay > 0 {
select {
case <-time.After(m.statusUpdateDelay):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return &proto.SendStatusUpdateResponse{}, nil
}
func (m *latencyMockClient) CreateProxyPeer(ctx context.Context, _ *proto.CreateProxyPeerRequest, _ ...grpc.CallOption) (*proto.CreateProxyPeerResponse, error) {
if m.createPeerDelay > 0 {
select {
case <-time.After(m.createPeerDelay):
case <-ctx.Done():
return nil, ctx.Err()
}
}
return &proto.CreateProxyPeerResponse{Success: true}, nil
}
type discardWriter struct{}
func (discardWriter) Write(p []byte) (int, error) { return len(p), nil }
func benchServerWithLatency(b *testing.B, createPeerDelay, statusDelay time.Duration) *Server {
b.Helper()
logger := log.New()
logger.SetLevel(log.FatalLevel)
logger.SetOutput(&discardWriter{})
meter, err := proxymetrics.New(context.Background(), noop.Meter{})
if err != nil {
b.Fatal(err)
}
mgmtClient := &latencyMockClient{
createPeerDelay: createPeerDelay,
statusUpdateDelay: statusDelay,
}
nb := roundtrip.NewNetBird("bench-proxy", "bench.test",
roundtrip.ClientConfig{MgmtAddr: "http://bench.test:9999"},
logger, nil, mgmtClient)
mainRouter := nbtcp.NewRouter(logger, func(accountID types.AccountID) (types.DialContextFunc, error) {
return (&net.Dialer{}).DialContext, nil
}, &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 443})
return &Server{
Logger: logger,
mgmtClient: mgmtClient,
netbird: nb,
proxy: proxy.NewReverseProxy(nil, "auto", nil, logger),
auth: auth.NewMiddleware(logger, nil, nil),
mainRouter: mainRouter,
mainPort: 443,
meter: meter,
hijackTracker: conntrack.HijackTracker{},
crowdsecRegistry: crowdsec.NewRegistry("", "", log.NewEntry(logger)),
crowdsecServices: make(map[types.ServiceID]bool),
lastMappings: make(map[types.ServiceID]*proto.ProxyMapping),
portRouters: make(map[uint16]*portRouter),
svcPorts: make(map[types.ServiceID][]uint16),
udpRelays: make(map[types.ServiceID]*udprelay.Relay),
}
}
// generateHTTPMappings creates N HTTP-mode mappings with the given update type.
// All belong to a single account to share the embedded client.
func generateHTTPMappings(n int, updateType proto.ProxyMappingUpdateType) []*proto.ProxyMapping {
mappings := make([]*proto.ProxyMapping, n)
for i := range n {
mappings[i] = &proto.ProxyMapping{
Type: updateType,
Id: fmt.Sprintf("svc-%d", i),
AccountId: "account-1",
Domain: fmt.Sprintf("svc-%d.bench.example.com", i),
Mode: "http",
Path: []*proto.PathMapping{
{
Path: "/",
Target: fmt.Sprintf("http://10.0.%d.%d:8080", (i/256)%256, i%256),
},
},
Auth: &proto.Authentication{},
}
}
return mappings
}
// generateMultiAccountHTTPMappings creates N HTTP-mode CREATED mappings spread
// across the given number of accounts. This stresses the AddPeer new-account
// path which calls CreateProxyPeer + embed.New per unique account.
func generateMultiAccountHTTPMappings(n, accounts int) []*proto.ProxyMapping {
mappings := make([]*proto.ProxyMapping, n)
for i := range n {
mappings[i] = &proto.ProxyMapping{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED,
Id: fmt.Sprintf("svc-%d", i),
AccountId: fmt.Sprintf("account-%d", i%accounts),
Domain: fmt.Sprintf("svc-%d.bench.example.com", i),
Mode: "http",
Path: []*proto.PathMapping{
{
Path: "/",
Target: fmt.Sprintf("http://10.0.%d.%d:8080", (i/256)%256, i%256),
},
},
Auth: &proto.Authentication{},
}
}
return mappings
}
// generateMixedMappings creates mappings with a realistic distribution:
// 70% HTTP create, 15% modify existing, 10% TLS on main port, 5% remove.
// All use a single account to avoid embed.New dialing.
func generateMixedMappings(n int) []*proto.ProxyMapping {
mappings := make([]*proto.ProxyMapping, n)
for i := range n {
var m *proto.ProxyMapping
switch {
case i%20 < 14: // 70% HTTP create
m = &proto.ProxyMapping{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED,
Id: fmt.Sprintf("svc-http-%d", i),
AccountId: "account-1",
Domain: fmt.Sprintf("svc-%d.bench.example.com", i),
Mode: "http",
Path: []*proto.PathMapping{
{Path: "/", Target: fmt.Sprintf("http://10.0.%d.%d:8080", (i/256)%256, i%256)},
{Path: "/api", Target: fmt.Sprintf("http://10.0.%d.%d:8081", (i/256)%256, i%256)},
},
Auth: &proto.Authentication{},
}
case i%20 < 17: // 15% modify
m = &proto.ProxyMapping{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_MODIFIED,
Id: fmt.Sprintf("svc-http-%d", i%100),
AccountId: "account-1",
Domain: fmt.Sprintf("svc-%d.bench.example.com", i%100),
Mode: "http",
Path: []*proto.PathMapping{
{Path: "/", Target: fmt.Sprintf("http://10.1.%d.%d:8080", (i/256)%256, i%256)},
},
Auth: &proto.Authentication{},
}
case i%20 < 19: // 10% TLS passthrough on main port
m = &proto.ProxyMapping{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED,
Id: fmt.Sprintf("svc-tls-%d", i),
AccountId: "account-1",
Domain: fmt.Sprintf("tls-%d.bench.example.com", i),
Mode: "tls",
ListenPort: 443,
Path: []*proto.PathMapping{
{Path: "/", Target: fmt.Sprintf("10.2.%d.%d:443", (i/256)%256, i%256)},
},
}
default: // 5% remove
m = &proto.ProxyMapping{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED,
Id: fmt.Sprintf("svc-http-%d", i%50),
AccountId: "account-1",
Domain: fmt.Sprintf("svc-%d.bench.example.com", i%50),
Mode: "http",
}
}
mappings[i] = m
}
return mappings
}
const (
createPeerLatency = 100 * time.Millisecond
statusUpdateLatency = 50 * time.Millisecond
)
// BenchmarkProcessMappings_HTTPCreate_SingleAccount benchmarks the initial sync
// scenario: N HTTP mappings all on a single account. Only the first mapping
// triggers CreateProxyPeer (100ms gRPC). The rest just register with the
// existing client. This is the "best case" production path.
func BenchmarkProcessMappings_HTTPCreate_SingleAccount(b *testing.B) {
for _, n := range []int{100, 1000, 5000} {
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
mappings := generateHTTPMappings(n, proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED)
for range b.N {
s := benchServerWithLatency(b, createPeerLatency, statusUpdateLatency)
s.processMappings(b.Context(), mappings)
}
})
}
}
// BenchmarkProcessMappings_HTTPCreate_MultiAccount benchmarks the worst-case
// initial sync: every mapping belongs to a different account, so each one
// triggers a full CreateProxyPeer gRPC round-trip (100ms) + embed.New.
// With 500 accounts this serializes to ~50s of blocking I/O.
func BenchmarkProcessMappings_HTTPCreate_MultiAccount(b *testing.B) {
for _, tc := range []struct {
mappings int
accounts int
}{
{100, 10},
{100, 50},
{1000, 50},
{1000, 200},
{3000, 500},
} {
b.Run(fmt.Sprintf("mappings=%d/accounts=%d", tc.mappings, tc.accounts), func(b *testing.B) {
mappings := generateMultiAccountHTTPMappings(tc.mappings, tc.accounts)
for range b.N {
s := benchServerWithLatency(b, createPeerLatency, statusUpdateLatency)
s.processMappings(b.Context(), mappings)
}
})
}
}
// BenchmarkProcessMappings_Mixed benchmarks a realistic mixed workload
// of creates, modifies, TLS, and removes with production-like latency.
// TLS mappings call SendStatusUpdate (50ms each), serialized.
func BenchmarkProcessMappings_Mixed(b *testing.B) {
for _, n := range []int{100, 1000, 5000} {
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
mappings := generateMixedMappings(n)
for range b.N {
s := benchServerWithLatency(b, createPeerLatency, statusUpdateLatency)
creates := generateHTTPMappings(100, proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED)
s.processMappings(b.Context(), creates)
s.processMappings(b.Context(), mappings)
}
})
}
}
// BenchmarkProcessMappings_ModifyOnly benchmarks bulk modification of
// already-registered mappings (no new peers needed, no gRPC).
func BenchmarkProcessMappings_ModifyOnly(b *testing.B) {
for _, n := range []int{100, 1000, 5000} {
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
creates := generateHTTPMappings(n, proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED)
modifies := generateHTTPMappings(n, proto.ProxyMappingUpdateType_UPDATE_TYPE_MODIFIED)
for range b.N {
s := benchServerWithLatency(b, createPeerLatency, statusUpdateLatency)
s.processMappings(b.Context(), creates)
s.processMappings(b.Context(), modifies)
}
})
}
}
// BenchmarkProcessMappings_NoLatency measures pure CPU/allocation overhead
// with zero I/O latency for profiling purposes.
func BenchmarkProcessMappings_NoLatency(b *testing.B) {
for _, n := range []int{1000, 5000} {
b.Run(fmt.Sprintf("n=%d", n), func(b *testing.B) {
mappings := generateHTTPMappings(n, proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED)
for range b.N {
s := benchServerWithLatency(b, 0, 0)
s.processMappings(b.Context(), mappings)
}
})
}
}

View File

@@ -32,9 +32,11 @@ import (
"go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric"
"golang.org/x/exp/maps" "golang.org/x/exp/maps"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
grpcstatus "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"github.com/netbirdio/netbird/proxy/internal/accesslog" "github.com/netbirdio/netbird/proxy/internal/accesslog"
@@ -282,6 +284,7 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
WGPort: s.WireguardPort, WGPort: s.WireguardPort,
PreSharedKey: s.PreSharedKey, PreSharedKey: s.PreSharedKey,
}, s.Logger, s, s.mgmtClient) }, s.Logger, s, s.mgmtClient)
s.netbird.OnAddPeer = s.meter.RecordAddPeerDuration
// Create health checker before the mapping worker so it can track // Create health checker before the mapping worker so it can track
// management connectivity from the first stream connection. // management connectivity from the first stream connection.
@@ -938,6 +941,9 @@ func (s *Server) newManagementMappingWorker(ctx context.Context, client proto.Pr
Clock: backoff.SystemClock, Clock: backoff.SystemClock,
} }
// syncSupported tracks whether management supports SyncMappings.
// Starts true; set to false on first Unimplemented error.
syncSupported := true
initialSyncDone := false initialSyncDone := false
operation := func() error { operation := func() error {
@@ -949,36 +955,25 @@ func (s *Server) newManagementMappingWorker(ctx context.Context, client proto.Pr
s.healthChecker.SetManagementConnected(false) s.healthChecker.SetManagementConnected(false)
} }
supportsCrowdSec := s.crowdsecRegistry.Available() var streamErr error
mappingClient, err := client.GetMappingUpdate(ctx, &proto.GetMappingUpdateRequest{ if syncSupported {
ProxyId: s.ID, streamErr = s.trySyncMappings(ctx, client, &initialSyncDone)
Version: s.Version, if isSyncUnimplemented(streamErr) {
StartedAt: timestamppb.New(s.startTime), syncSupported = false
Address: s.ProxyURL, s.Logger.Info("management does not support SyncMappings, falling back to GetMappingUpdate")
Capabilities: &proto.ProxyCapabilities{ streamErr = s.tryGetMappingUpdate(ctx, client, &initialSyncDone)
SupportsCustomPorts: &s.SupportsCustomPorts, }
RequireSubdomain: &s.RequireSubdomain, } else {
SupportsCrowdsec: &supportsCrowdSec, streamErr = s.tryGetMappingUpdate(ctx, client, &initialSyncDone)
},
})
if err != nil {
return fmt.Errorf("create mapping stream: %w", err)
} }
if s.healthChecker != nil {
s.healthChecker.SetManagementConnected(true)
}
s.Logger.Debug("management mapping stream established")
// Stream established — reset backoff so the next failure retries quickly.
bo.Reset()
streamErr := s.handleMappingStream(ctx, mappingClient, &initialSyncDone)
if s.healthChecker != nil { if s.healthChecker != nil {
s.healthChecker.SetManagementConnected(false) s.healthChecker.SetManagementConnected(false)
} }
// Stream established — reset backoff so the next failure retries quickly.
bo.Reset()
if streamErr == nil { if streamErr == nil {
return fmt.Errorf("stream closed by server") return fmt.Errorf("stream closed by server")
} }
@@ -995,56 +990,187 @@ func (s *Server) newManagementMappingWorker(ctx context.Context, client proto.Pr
} }
} }
func (s *Server) handleMappingStream(ctx context.Context, mappingClient proto.ProxyService_GetMappingUpdateClient, initialSyncDone *bool) error { func (s *Server) proxyCapabilities() *proto.ProxyCapabilities {
supportsCrowdSec := s.crowdsecRegistry.Available()
return &proto.ProxyCapabilities{
SupportsCustomPorts: &s.SupportsCustomPorts,
RequireSubdomain: &s.RequireSubdomain,
SupportsCrowdsec: &supportsCrowdSec,
}
}
func (s *Server) tryGetMappingUpdate(ctx context.Context, client proto.ProxyServiceClient, initialSyncDone *bool) error {
connectTime := time.Now()
mappingClient, err := client.GetMappingUpdate(ctx, &proto.GetMappingUpdateRequest{
ProxyId: s.ID,
Version: s.Version,
StartedAt: timestamppb.New(s.startTime),
Address: s.ProxyURL,
Capabilities: s.proxyCapabilities(),
})
if err != nil {
return fmt.Errorf("create mapping stream: %w", err)
}
if s.healthChecker != nil {
s.healthChecker.SetManagementConnected(true)
}
s.Logger.Debug("management mapping stream established (GetMappingUpdate)")
return s.handleMappingStream(ctx, mappingClient, initialSyncDone, connectTime)
}
func (s *Server) trySyncMappings(ctx context.Context, client proto.ProxyServiceClient, initialSyncDone *bool) error {
connectTime := time.Now()
stream, err := client.SyncMappings(ctx)
if err != nil {
return fmt.Errorf("create sync stream: %w", err)
}
// Send init message.
if err := stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: s.ID,
Version: s.Version,
StartedAt: timestamppb.New(s.startTime),
Address: s.ProxyURL,
Capabilities: s.proxyCapabilities(),
},
},
}); err != nil {
return fmt.Errorf("send sync init: %w", err)
}
if s.healthChecker != nil {
s.healthChecker.SetManagementConnected(true)
}
s.Logger.Debug("management mapping stream established (SyncMappings)")
return s.handleSyncMappingsStream(ctx, stream, initialSyncDone, connectTime)
}
func isSyncUnimplemented(err error) bool {
if err == nil {
return false
}
st, ok := grpcstatus.FromError(err)
return ok && st.Code() == codes.Unimplemented
}
func (s *Server) handleSyncMappingsStream(ctx context.Context, stream proto.ProxyService_SyncMappingsClient, initialSyncDone *bool, connectTime time.Time) error {
select { select {
case <-s.routerReady: case <-s.routerReady:
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
} }
var snapshotIDs map[types.ServiceID]struct{} tracker := s.newSnapshotTracker(initialSyncDone, connectTime)
if !*initialSyncDone {
snapshotIDs = make(map[types.ServiceID]struct{}) for {
} select {
case <-ctx.Done():
return ctx.Err()
default:
msg, err := stream.Recv()
switch {
case errors.Is(err, io.EOF):
return nil
case err != nil:
return fmt.Errorf("receive msg: %w", err)
}
batchStart := time.Now()
s.Logger.Debug("Received mapping update, starting processing")
s.processMappings(ctx, msg.GetMapping())
s.Logger.Debug("Processing mapping update completed")
tracker.recordBatch(ctx, s, msg.GetMapping(), msg.GetInitialSyncComplete(), batchStart)
if err := stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
}); err != nil {
return fmt.Errorf("send ack: %w", err)
}
}
}
}
func (s *Server) handleMappingStream(ctx context.Context, mappingClient proto.ProxyService_GetMappingUpdateClient, initialSyncDone *bool, connectTime time.Time) error {
select {
case <-s.routerReady:
case <-ctx.Done():
return ctx.Err()
}
tracker := s.newSnapshotTracker(initialSyncDone, connectTime)
for { for {
// Check for context completion to gracefully shutdown.
select { select {
case <-ctx.Done(): case <-ctx.Done():
// Shutting down.
return ctx.Err() return ctx.Err()
default: default:
msg, err := mappingClient.Recv() msg, err := mappingClient.Recv()
switch { switch {
case errors.Is(err, io.EOF): case errors.Is(err, io.EOF):
// Mapping connection gracefully terminated by server.
return nil return nil
case err != nil: case err != nil:
// Something has gone horribly wrong, return and hope the parent retries the connection.
return fmt.Errorf("receive msg: %w", err) return fmt.Errorf("receive msg: %w", err)
} }
batchStart := time.Now()
s.Logger.Debug("Received mapping update, starting processing") s.Logger.Debug("Received mapping update, starting processing")
s.processMappings(ctx, msg.GetMapping()) s.processMappings(ctx, msg.GetMapping())
s.Logger.Debug("Processing mapping update completed") s.Logger.Debug("Processing mapping update completed")
tracker.recordBatch(ctx, s, msg.GetMapping(), msg.GetInitialSyncComplete(), batchStart)
if !*initialSyncDone {
for _, m := range msg.GetMapping() {
snapshotIDs[types.ServiceID(m.GetId())] = struct{}{}
}
if msg.GetInitialSyncComplete() {
s.reconcileSnapshot(ctx, snapshotIDs)
snapshotIDs = nil
if s.healthChecker != nil {
s.healthChecker.SetInitialSyncComplete()
}
*initialSyncDone = true
s.Logger.Info("Initial mapping sync complete")
}
}
} }
} }
} }
// snapshotTracker accumulates service IDs during the initial snapshot and
// finalises sync state when the complete flag arrives.
type snapshotTracker struct {
done *bool
connectTime time.Time
snapshotIDs map[types.ServiceID]struct{}
}
func (s *Server) newSnapshotTracker(done *bool, connectTime time.Time) *snapshotTracker {
var ids map[types.ServiceID]struct{}
if !*done {
ids = make(map[types.ServiceID]struct{})
}
return &snapshotTracker{done: done, connectTime: connectTime, snapshotIDs: ids}
}
func (t *snapshotTracker) recordBatch(ctx context.Context, s *Server, mappings []*proto.ProxyMapping, syncComplete bool, batchStart time.Time) {
if *t.done {
return
}
if s.meter != nil {
s.meter.RecordSnapshotBatchDuration(time.Since(batchStart))
}
for _, m := range mappings {
t.snapshotIDs[types.ServiceID(m.GetId())] = struct{}{}
}
if !syncComplete {
return
}
s.reconcileSnapshot(ctx, t.snapshotIDs)
t.snapshotIDs = nil
if s.healthChecker != nil {
s.healthChecker.SetInitialSyncComplete()
}
*t.done = true
if s.meter != nil {
s.meter.RecordSnapshotSyncDuration(time.Since(t.connectTime))
}
s.Logger.Info("Initial mapping sync complete")
}
// reconcileSnapshot removes local mappings that are absent from the snapshot. // reconcileSnapshot removes local mappings that are absent from the snapshot.
// This ensures services deleted while the proxy was disconnected get cleaned up. // This ensures services deleted while the proxy was disconnected get cleaned up.
func (s *Server) reconcileSnapshot(ctx context.Context, snapshotIDs map[types.ServiceID]struct{}) { func (s *Server) reconcileSnapshot(ctx context.Context, snapshotIDs map[types.ServiceID]struct{}) {
@@ -1067,6 +1193,8 @@ func (s *Server) reconcileSnapshot(ctx context.Context, snapshotIDs map[types.Se
} }
func (s *Server) processMappings(ctx context.Context, mappings []*proto.ProxyMapping) { func (s *Server) processMappings(ctx context.Context, mappings []*proto.ProxyMapping) {
s.ensurePeers(ctx, mappings)
for _, mapping := range mappings { for _, mapping := range mappings {
s.Logger.WithFields(log.Fields{ s.Logger.WithFields(log.Fields{
"type": mapping.GetType(), "type": mapping.GetType(),
@@ -1100,6 +1228,60 @@ func (s *Server) processMappings(ctx context.Context, mappings []*proto.ProxyMap
} }
} }
// ensurePeers pre-creates NetBird peers for all unique accounts referenced by
// CREATED mappings. Peers for different accounts are created concurrently,
// which avoids serializing N×100ms gRPC round-trips during large initial syncs.
func (s *Server) ensurePeers(ctx context.Context, mappings []*proto.ProxyMapping) {
// Collect one representative mapping per account that needs a new peer.
type peerReq struct {
accountID types.AccountID
svcKey roundtrip.ServiceKey
authToken string
svcID types.ServiceID
}
seen := make(map[types.AccountID]struct{})
var reqs []peerReq
for _, m := range mappings {
if m.GetType() != proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED {
continue
}
accountID := types.AccountID(m.GetAccountId())
if _, ok := seen[accountID]; ok {
continue
}
seen[accountID] = struct{}{}
if s.netbird.HasClient(accountID) {
continue
}
reqs = append(reqs, peerReq{
accountID: accountID,
svcKey: s.serviceKeyForMapping(m),
authToken: m.GetAuthToken(),
svcID: types.ServiceID(m.GetId()),
})
}
if len(reqs) <= 1 {
return
}
var wg sync.WaitGroup
wg.Add(len(reqs))
for _, r := range reqs {
go func() {
defer wg.Done()
if err := s.netbird.AddPeer(ctx, r.accountID, r.svcKey, r.authToken, r.svcID); err != nil {
s.Logger.WithFields(log.Fields{
"account_id": r.accountID,
"service_id": r.svcID,
"error": err,
}).Warn("failed to pre-create peer for account")
}
}()
}
wg.Wait()
}
// addMapping registers a service mapping and starts the appropriate relay or routes. // addMapping registers a service mapping and starts the appropriate relay or routes.
func (s *Server) addMapping(ctx context.Context, mapping *proto.ProxyMapping) error { func (s *Server) addMapping(ctx context.Context, mapping *proto.ProxyMapping) error {
accountID := types.AccountID(mapping.GetAccountId()) accountID := types.AccountID(mapping.GetAccountId())

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"testing" "testing"
"time"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@@ -139,7 +140,7 @@ func TestHandleMappingStream_BatchedSnapshotSyncComplete(t *testing.T) {
} }
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, syncDone, "sync should be marked done after final batch") assert.True(t, syncDone, "sync should be marked done after final batch")
} }
@@ -164,7 +165,7 @@ func TestHandleMappingStream_PostSyncDoesNotReconcile(t *testing.T) {
} }
syncDone := true // sync already completed in a previous stream syncDone := true // sync already completed in a previous stream
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
require.NoError(t, err) require.NoError(t, err)
assert.Len(t, s.lastMappings, 2, assert.Len(t, s.lastMappings, 2,
@@ -185,7 +186,7 @@ func TestHandleMappingStream_ImmediateEOF_NoReconciliation(t *testing.T) {
stream := &mockMappingStream{} // no messages → immediate EOF stream := &mockMappingStream{} // no messages → immediate EOF
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), stream, &syncDone) err := s.handleMappingStream(context.Background(), stream, &syncDone, time.Time{})
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, syncDone, "sync should not be marked done on immediate EOF") assert.False(t, syncDone, "sync should not be marked done on immediate EOF")
@@ -218,7 +219,7 @@ func TestHandleMappingStream_ErrorMidSync_NoReconciliation(t *testing.T) {
s.lastMappings["svc-stale"] = &proto.ProxyMapping{Id: "svc-stale", AccountId: "acct-1"} s.lastMappings["svc-stale"] = &proto.ProxyMapping{Id: "svc-stale", AccountId: "acct-1"}
syncDone := false syncDone := false
err := s.handleMappingStream(context.Background(), &mockErrRecvStream{}, &syncDone) err := s.handleMappingStream(context.Background(), &mockErrRecvStream{}, &syncDone, time.Time{})
assert.Error(t, err) assert.Error(t, err)
assert.False(t, syncDone) assert.False(t, syncDone)

525
proxy/sync_mappings_test.go Normal file
View File

@@ -0,0 +1,525 @@
package proxy
import (
"context"
"errors"
"fmt"
"net"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials/insecure"
grpcstatus "google.golang.org/grpc/status"
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
"github.com/netbirdio/netbird/shared/management/proto"
)
func TestIntegration_SyncMappings_HappyPath(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()
conn, err := grpc.NewClient(setup.grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.SyncMappings(ctx)
require.NoError(t, err)
// Send init.
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: "sync-proxy-1",
Version: "test-v1",
Address: "test.proxy.io",
},
},
})
require.NoError(t, err)
mappingsByID := make(map[string]*proto.ProxyMapping)
for {
msg, err := stream.Recv()
require.NoError(t, err)
for _, m := range msg.GetMapping() {
mappingsByID[m.GetId()] = m
}
// Ack every batch.
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
})
require.NoError(t, err)
if msg.GetInitialSyncComplete() {
break
}
}
assert.Len(t, mappingsByID, 2, "Should receive 2 mappings")
rp1 := mappingsByID["rp-1"]
require.NotNil(t, rp1)
assert.Equal(t, "app1.test.proxy.io", rp1.GetDomain())
assert.Equal(t, "test-account-1", rp1.GetAccountId())
assert.Equal(t, proto.ProxyMappingUpdateType_UPDATE_TYPE_CREATED, rp1.GetType())
assert.NotEmpty(t, rp1.GetAuthToken(), "Should have auth token")
rp2 := mappingsByID["rp-2"]
require.NotNil(t, rp2)
assert.Equal(t, "app2.test.proxy.io", rp2.GetDomain())
}
func TestIntegration_SyncMappings_BackPressure(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()
// Add enough services to guarantee multiple batches (default batch size 500).
addServicesToStore(t, setup, 600, "test.proxy.io")
conn, err := grpc.NewClient(setup.grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
stream, err := client.SyncMappings(ctx)
require.NoError(t, err)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: "sync-proxy-backpressure",
Version: "test-v1",
Address: "test.proxy.io",
},
},
})
require.NoError(t, err)
// Strategy: receive batch 1, then hold for a significant delay before
// acking. If back-pressure works, batch 2 cannot arrive until after
// the ack is sent — so its receive timestamp must be >= the ack
// timestamp. If management were fire-and-forget, all batches would
// already be buffered in the gRPC transport and batch 2 would arrive
// well before the ack time.
const ackDelay = 300 * time.Millisecond
type batchEvent struct {
recvAt time.Time
ackAt time.Time
count int
}
var batches []batchEvent
var totalMappings int
for {
msg, err := stream.Recv()
require.NoError(t, err)
recvAt := time.Now()
totalMappings += len(msg.GetMapping())
// Delay the ack on non-final batches to create a measurable gap.
if !msg.GetInitialSyncComplete() {
time.Sleep(ackDelay)
}
ackAt := time.Now()
batches = append(batches, batchEvent{
recvAt: recvAt,
ackAt: ackAt,
count: len(msg.GetMapping()),
})
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
})
require.NoError(t, err)
if msg.GetInitialSyncComplete() {
break
}
}
// 2 original + 600 added = 602 services total.
assert.Equal(t, 602, totalMappings, "should receive all 602 mappings")
require.GreaterOrEqual(t, len(batches), 2, "need at least 2 batches to verify back-pressure")
// For every batch after the first, its receive time must be after the
// previous batch's ack time. This proves management waited for the ack
// before sending the next batch.
for i := 1; i < len(batches); i++ {
prevAckAt := batches[i-1].ackAt
thisRecvAt := batches[i].recvAt
assert.True(t, !thisRecvAt.Before(prevAckAt),
"batch %d received at %v, but batch %d was acked at %v — "+
"management sent the next batch before receiving the ack",
i, thisRecvAt, i-1, prevAckAt)
}
}
func TestIntegration_SyncMappings_IncrementalUpdate(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()
conn, err := grpc.NewClient(setup.grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.SyncMappings(ctx)
require.NoError(t, err)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: "sync-proxy-incremental",
Version: "test-v1",
Address: "test.proxy.io",
},
},
})
require.NoError(t, err)
// Drain initial snapshot.
for {
msg, err := stream.Recv()
require.NoError(t, err)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
})
require.NoError(t, err)
if msg.GetInitialSyncComplete() {
break
}
}
// Now send an incremental update via the management server.
setup.proxyService.SendServiceUpdate(&proto.GetMappingUpdateResponse{
Mapping: []*proto.ProxyMapping{{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED,
Id: "rp-1",
AccountId: "test-account-1",
Domain: "app1.test.proxy.io",
}},
})
// Receive the incremental update on the sync stream.
msg, err := stream.Recv()
require.NoError(t, err)
require.NotEmpty(t, msg.GetMapping())
assert.Equal(t, "rp-1", msg.GetMapping()[0].GetId())
assert.Equal(t, proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED, msg.GetMapping()[0].GetType())
}
func TestIntegration_SyncMappings_MixedProxyVersions(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()
conn, err := grpc.NewClient(setup.grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Old proxy uses GetMappingUpdate.
legacyStream, err := client.GetMappingUpdate(ctx, &proto.GetMappingUpdateRequest{
ProxyId: "legacy-proxy",
Version: "old-v1",
Address: "test.proxy.io",
})
require.NoError(t, err)
var legacyMappings []*proto.ProxyMapping
for {
msg, err := legacyStream.Recv()
require.NoError(t, err)
legacyMappings = append(legacyMappings, msg.GetMapping()...)
if msg.GetInitialSyncComplete() {
break
}
}
// New proxy uses SyncMappings.
syncStream, err := client.SyncMappings(ctx)
require.NoError(t, err)
err = syncStream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: "new-proxy",
Version: "new-v2",
Address: "test.proxy.io",
},
},
})
require.NoError(t, err)
var syncMappings []*proto.ProxyMapping
for {
msg, err := syncStream.Recv()
require.NoError(t, err)
syncMappings = append(syncMappings, msg.GetMapping()...)
err = syncStream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
})
require.NoError(t, err)
if msg.GetInitialSyncComplete() {
break
}
}
// Both should receive the same set of mappings.
assert.Equal(t, len(legacyMappings), len(syncMappings),
"legacy and sync proxies should receive the same number of mappings")
legacyIDs := make(map[string]bool)
for _, m := range legacyMappings {
legacyIDs[m.GetId()] = true
}
for _, m := range syncMappings {
assert.True(t, legacyIDs[m.GetId()],
"mapping %s should be present in both streams", m.GetId())
}
// Both proxies should be connected.
proxies := setup.proxyService.GetConnectedProxies()
assert.Contains(t, proxies, "legacy-proxy")
assert.Contains(t, proxies, "new-proxy")
// Both should receive incremental updates.
setup.proxyService.SendServiceUpdate(&proto.GetMappingUpdateResponse{
Mapping: []*proto.ProxyMapping{{
Type: proto.ProxyMappingUpdateType_UPDATE_TYPE_REMOVED,
Id: "rp-1",
AccountId: "test-account-1",
Domain: "app1.test.proxy.io",
}},
})
// Legacy proxy receives via GetMappingUpdateResponse.
legacyMsg, err := legacyStream.Recv()
require.NoError(t, err)
assert.Equal(t, "rp-1", legacyMsg.GetMapping()[0].GetId())
// Sync proxy receives via SyncMappingsResponse.
syncMsg, err := syncStream.Recv()
require.NoError(t, err)
assert.Equal(t, "rp-1", syncMsg.GetMapping()[0].GetId())
}
func TestIntegration_SyncMappings_Reconnect(t *testing.T) {
setup := setupIntegrationTest(t)
defer setup.cleanup()
conn, err := grpc.NewClient(setup.grpcAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
proxyID := "sync-proxy-reconnect"
receiveMappings := func() []*proto.ProxyMapping {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
stream, err := client.SyncMappings(ctx)
require.NoError(t, err)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: proxyID,
Version: "test-v1",
Address: "test.proxy.io",
},
},
})
require.NoError(t, err)
var mappings []*proto.ProxyMapping
for {
msg, err := stream.Recv()
require.NoError(t, err)
mappings = append(mappings, msg.GetMapping()...)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Ack{Ack: &proto.SyncMappingsAck{}},
})
require.NoError(t, err)
if msg.GetInitialSyncComplete() {
break
}
}
return mappings
}
first := receiveMappings()
time.Sleep(100 * time.Millisecond)
second := receiveMappings()
assert.Equal(t, len(first), len(second),
"should receive same mappings on reconnect")
firstIDs := make(map[string]bool)
for _, m := range first {
firstIDs[m.GetId()] = true
}
for _, m := range second {
assert.True(t, firstIDs[m.GetId()],
"mapping %s should be present in both connections", m.GetId())
}
}
// --- Fallback tests: old management returns Unimplemented ---
// unimplementedProxyServer embeds UnimplementedProxyServiceServer so
// SyncMappings returns codes.Unimplemented while GetMappingUpdate works.
type unimplementedSyncServer struct {
proto.UnimplementedProxyServiceServer
getMappingCalls atomic.Int32
}
func (s *unimplementedSyncServer) GetMappingUpdate(_ *proto.GetMappingUpdateRequest, stream proto.ProxyService_GetMappingUpdateServer) error {
s.getMappingCalls.Add(1)
return stream.Send(&proto.GetMappingUpdateResponse{
Mapping: []*proto.ProxyMapping{{Id: "svc-1", AccountId: "acct-1", Domain: "example.com"}},
InitialSyncComplete: true,
})
}
func TestIntegration_FallbackToGetMappingUpdate(t *testing.T) {
// Start a gRPC server that does NOT implement SyncMappings.
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
srv := &unimplementedSyncServer{}
grpcServer := grpc.NewServer()
proto.RegisterProxyServiceServer(grpcServer, srv)
go func() { _ = grpcServer.Serve(lis) }()
defer grpcServer.GracefulStop()
conn, err := grpc.NewClient(lis.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials()))
require.NoError(t, err)
defer conn.Close()
client := proto.NewProxyServiceClient(conn)
// Try SyncMappings — should get Unimplemented.
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
stream, err := client.SyncMappings(ctx)
require.NoError(t, err)
err = stream.Send(&proto.SyncMappingsRequest{
Msg: &proto.SyncMappingsRequest_Init{
Init: &proto.SyncMappingsInit{
ProxyId: "test-proxy",
Address: "test.example.com",
},
},
})
require.NoError(t, err)
_, err = stream.Recv()
require.Error(t, err)
st, ok := grpcstatus.FromError(err)
require.True(t, ok)
assert.Equal(t, codes.Unimplemented, st.Code(),
"unimplemented SyncMappings should return Unimplemented code")
// isSyncUnimplemented should detect this.
assert.True(t, isSyncUnimplemented(err))
// The actual fallback: GetMappingUpdate should work.
legacyStream, err := client.GetMappingUpdate(ctx, &proto.GetMappingUpdateRequest{
ProxyId: "test-proxy",
Address: "test.example.com",
})
require.NoError(t, err)
msg, err := legacyStream.Recv()
require.NoError(t, err)
assert.True(t, msg.GetInitialSyncComplete())
assert.Len(t, msg.GetMapping(), 1)
assert.Equal(t, int32(1), srv.getMappingCalls.Load())
}
func TestIsSyncUnimplemented(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{"nil error", nil, false},
{"non-grpc error", errors.New("random"), false},
{"grpc internal", grpcstatus.Error(codes.Internal, "fail"), false},
{"grpc unavailable", grpcstatus.Error(codes.Unavailable, "fail"), false},
{"grpc unimplemented", grpcstatus.Error(codes.Unimplemented, "method not found"), true},
{
"wrapped unimplemented",
fmt.Errorf("create sync stream: %w", grpcstatus.Error(codes.Unimplemented, "nope")),
// grpc/status.FromError unwraps in recent versions of grpc-go.
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.want, isSyncUnimplemented(tt.err))
})
}
}
// addServicesToStore adds n extra services to the test store for the given cluster.
func addServicesToStore(t *testing.T, setup *integrationTestSetup, n int, cluster string) {
t.Helper()
ctx := context.Background()
for i := 0; i < n; i++ {
svc := &service.Service{
ID: fmt.Sprintf("extra-svc-%d", i),
AccountID: "test-account-1",
Name: fmt.Sprintf("Extra Service %d", i),
Domain: fmt.Sprintf("extra-%d.test.proxy.io", i),
ProxyCluster: cluster,
Enabled: true,
Targets: []*service.Target{{
Path: strPtr("/"),
Host: fmt.Sprintf("10.0.1.%d", i%256),
Port: 8080,
Protocol: "http",
TargetId: fmt.Sprintf("peer-extra-%d", i),
TargetType: "peer",
Enabled: true,
}},
}
require.NoError(t, setup.store.CreateService(ctx, svc))
}
}

View File

@@ -9,10 +9,22 @@ then
fi fi
old_pwd=$(pwd) old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0")) script_path=$(dirname "$(realpath "$0")")
cd "$script_path" cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 repo_root=$(git rev-parse --show-toplevel)
# shellcheck source=/dev/null
. "$repo_root/proto-tools.env"
actual_protoc=$(protoc --version | awk '{print $2}')
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
exit 1
fi
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
protoc -I ./ ./management.proto --go_out=../ --go-grpc_out=../ protoc -I ./ ./management.proto --go_out=../ --go-grpc_out=../
protoc -I ./ ./proxy_service.proto --go_out=../ --go-grpc_out=../ protoc -I ./ ./proxy_service.proto --go_out=../ --go-grpc_out=../
cd "$old_pwd" cd "$old_pwd"

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v6.33.1
// source: management.proto
package proto package proto
@@ -11,8 +15,23 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const (
ManagementService_Login_FullMethodName = "/management.ManagementService/Login"
ManagementService_Sync_FullMethodName = "/management.ManagementService/Sync"
ManagementService_GetServerKey_FullMethodName = "/management.ManagementService/GetServerKey"
ManagementService_IsHealthy_FullMethodName = "/management.ManagementService/isHealthy"
ManagementService_GetDeviceAuthorizationFlow_FullMethodName = "/management.ManagementService/GetDeviceAuthorizationFlow"
ManagementService_GetPKCEAuthorizationFlow_FullMethodName = "/management.ManagementService/GetPKCEAuthorizationFlow"
ManagementService_SyncMeta_FullMethodName = "/management.ManagementService/SyncMeta"
ManagementService_Logout_FullMethodName = "/management.ManagementService/Logout"
ManagementService_Job_FullMethodName = "/management.ManagementService/Job"
ManagementService_CreateExpose_FullMethodName = "/management.ManagementService/CreateExpose"
ManagementService_RenewExpose_FullMethodName = "/management.ManagementService/RenewExpose"
ManagementService_StopExpose_FullMethodName = "/management.ManagementService/StopExpose"
)
// ManagementServiceClient is the client API for ManagementService service. // ManagementServiceClient is the client API for ManagementService service.
// //
@@ -25,7 +44,7 @@ type ManagementServiceClient interface {
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update // For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
// The initial SyncResponse contains all of the available peers so the local state can be refreshed // The initial SyncResponse contains all of the available peers so the local state can be refreshed
// Returns encrypted SyncResponse in EncryptedMessage.Body // Returns encrypted SyncResponse in EncryptedMessage.Body
Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (ManagementService_SyncClient, error) Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (grpc.ServerStreamingClient[EncryptedMessage], error)
// Exposes a Wireguard public key of the Management service. // Exposes a Wireguard public key of the Management service.
// This key is used to support message encryption between client and server // This key is used to support message encryption between client and server
GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error) GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error)
@@ -51,7 +70,7 @@ type ManagementServiceClient interface {
// Logout logs out the peer and removes it from the management server // Logout logs out the peer and removes it from the management server
Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error)
// Executes a job on a target peer (e.g., debug bundle) // Executes a job on a target peer (e.g., debug bundle)
Job(ctx context.Context, opts ...grpc.CallOption) (ManagementService_JobClient, error) Job(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage], error)
// CreateExpose creates a temporary reverse proxy service for a peer // CreateExpose creates a temporary reverse proxy service for a peer
CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
// RenewExpose extends the TTL of an active expose session // RenewExpose extends the TTL of an active expose session
@@ -69,20 +88,22 @@ func NewManagementServiceClient(cc grpc.ClientConnInterface) ManagementServiceCl
} }
func (c *managementServiceClient) Login(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) Login(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/Login", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_Login_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return out, nil return out, nil
} }
func (c *managementServiceClient) Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (ManagementService_SyncClient, error) { func (c *managementServiceClient) Sync(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (grpc.ServerStreamingClient[EncryptedMessage], error) {
stream, err := c.cc.NewStream(ctx, &ManagementService_ServiceDesc.Streams[0], "/management.ManagementService/Sync", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ManagementService_ServiceDesc.Streams[0], ManagementService_Sync_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &managementServiceSyncClient{stream} x := &grpc.GenericClientStream[EncryptedMessage, EncryptedMessage]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil { if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err return nil, err
} }
@@ -92,26 +113,13 @@ func (c *managementServiceClient) Sync(ctx context.Context, in *EncryptedMessage
return x, nil return x, nil
} }
type ManagementService_SyncClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Recv() (*EncryptedMessage, error) type ManagementService_SyncClient = grpc.ServerStreamingClient[EncryptedMessage]
grpc.ClientStream
}
type managementServiceSyncClient struct {
grpc.ClientStream
}
func (x *managementServiceSyncClient) Recv() (*EncryptedMessage, error) {
m := new(EncryptedMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *managementServiceClient) GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error) { func (c *managementServiceClient) GetServerKey(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*ServerKeyResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ServerKeyResponse) out := new(ServerKeyResponse)
err := c.cc.Invoke(ctx, "/management.ManagementService/GetServerKey", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_GetServerKey_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -119,8 +127,9 @@ func (c *managementServiceClient) GetServerKey(ctx context.Context, in *Empty, o
} }
func (c *managementServiceClient) IsHealthy(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { func (c *managementServiceClient) IsHealthy(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty) out := new(Empty)
err := c.cc.Invoke(ctx, "/management.ManagementService/isHealthy", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_IsHealthy_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -128,8 +137,9 @@ func (c *managementServiceClient) IsHealthy(ctx context.Context, in *Empty, opts
} }
func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/GetDeviceAuthorizationFlow", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_GetDeviceAuthorizationFlow_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -137,8 +147,9 @@ func (c *managementServiceClient) GetDeviceAuthorizationFlow(ctx context.Context
} }
func (c *managementServiceClient) GetPKCEAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) GetPKCEAuthorizationFlow(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/GetPKCEAuthorizationFlow", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_GetPKCEAuthorizationFlow_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -146,8 +157,9 @@ func (c *managementServiceClient) GetPKCEAuthorizationFlow(ctx context.Context,
} }
func (c *managementServiceClient) SyncMeta(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) { func (c *managementServiceClient) SyncMeta(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty) out := new(Empty)
err := c.cc.Invoke(ctx, "/management.ManagementService/SyncMeta", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_SyncMeta_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -155,48 +167,32 @@ func (c *managementServiceClient) SyncMeta(ctx context.Context, in *EncryptedMes
} }
func (c *managementServiceClient) Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) { func (c *managementServiceClient) Logout(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty) out := new(Empty)
err := c.cc.Invoke(ctx, "/management.ManagementService/Logout", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_Logout_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return out, nil return out, nil
} }
func (c *managementServiceClient) Job(ctx context.Context, opts ...grpc.CallOption) (ManagementService_JobClient, error) { func (c *managementServiceClient) Job(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage], error) {
stream, err := c.cc.NewStream(ctx, &ManagementService_ServiceDesc.Streams[1], "/management.ManagementService/Job", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ManagementService_ServiceDesc.Streams[1], ManagementService_Job_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &managementServiceJobClient{stream} x := &grpc.GenericClientStream[EncryptedMessage, EncryptedMessage]{ClientStream: stream}
return x, nil return x, nil
} }
type ManagementService_JobClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*EncryptedMessage) error type ManagementService_JobClient = grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage]
Recv() (*EncryptedMessage, error)
grpc.ClientStream
}
type managementServiceJobClient struct {
grpc.ClientStream
}
func (x *managementServiceJobClient) Send(m *EncryptedMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *managementServiceJobClient) Recv() (*EncryptedMessage, error) {
m := new(EncryptedMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func (c *managementServiceClient) CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) CreateExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/CreateExpose", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_CreateExpose_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -204,8 +200,9 @@ func (c *managementServiceClient) CreateExpose(ctx context.Context, in *Encrypte
} }
func (c *managementServiceClient) RenewExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) RenewExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/RenewExpose", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_RenewExpose_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -213,8 +210,9 @@ func (c *managementServiceClient) RenewExpose(ctx context.Context, in *Encrypted
} }
func (c *managementServiceClient) StopExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *managementServiceClient) StopExpose(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/management.ManagementService/StopExpose", in, out, opts...) err := c.cc.Invoke(ctx, ManagementService_StopExpose_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -223,7 +221,7 @@ func (c *managementServiceClient) StopExpose(ctx context.Context, in *EncryptedM
// ManagementServiceServer is the server API for ManagementService service. // ManagementServiceServer is the server API for ManagementService service.
// All implementations must embed UnimplementedManagementServiceServer // All implementations must embed UnimplementedManagementServiceServer
// for forward compatibility // for forward compatibility.
type ManagementServiceServer interface { type ManagementServiceServer interface {
// Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey // Login logs in peer. In case server returns codes.PermissionDenied this endpoint can be used to register Peer providing LoginRequest.setupKey
// Returns encrypted LoginResponse in EncryptedMessage.Body // Returns encrypted LoginResponse in EncryptedMessage.Body
@@ -232,7 +230,7 @@ type ManagementServiceServer interface {
// For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update // For example, if a new peer has been added to an account all other connected peers will receive this peer's Wireguard public key as an update
// The initial SyncResponse contains all of the available peers so the local state can be refreshed // The initial SyncResponse contains all of the available peers so the local state can be refreshed
// Returns encrypted SyncResponse in EncryptedMessage.Body // Returns encrypted SyncResponse in EncryptedMessage.Body
Sync(*EncryptedMessage, ManagementService_SyncServer) error Sync(*EncryptedMessage, grpc.ServerStreamingServer[EncryptedMessage]) error
// Exposes a Wireguard public key of the Management service. // Exposes a Wireguard public key of the Management service.
// This key is used to support message encryption between client and server // This key is used to support message encryption between client and server
GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error) GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error)
@@ -258,7 +256,7 @@ type ManagementServiceServer interface {
// Logout logs out the peer and removes it from the management server // Logout logs out the peer and removes it from the management server
Logout(context.Context, *EncryptedMessage) (*Empty, error) Logout(context.Context, *EncryptedMessage) (*Empty, error)
// Executes a job on a target peer (e.g., debug bundle) // Executes a job on a target peer (e.g., debug bundle)
Job(ManagementService_JobServer) error Job(grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]) error
// CreateExpose creates a temporary reverse proxy service for a peer // CreateExpose creates a temporary reverse proxy service for a peer
CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
// RenewExpose extends the TTL of an active expose session // RenewExpose extends the TTL of an active expose session
@@ -268,47 +266,51 @@ type ManagementServiceServer interface {
mustEmbedUnimplementedManagementServiceServer() mustEmbedUnimplementedManagementServiceServer()
} }
// UnimplementedManagementServiceServer must be embedded to have forward compatible implementations. // UnimplementedManagementServiceServer must be embedded to have
type UnimplementedManagementServiceServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedManagementServiceServer struct{}
func (UnimplementedManagementServiceServer) Login(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) Login(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method Login not implemented") return nil, status.Error(codes.Unimplemented, "method Login not implemented")
} }
func (UnimplementedManagementServiceServer) Sync(*EncryptedMessage, ManagementService_SyncServer) error { func (UnimplementedManagementServiceServer) Sync(*EncryptedMessage, grpc.ServerStreamingServer[EncryptedMessage]) error {
return status.Errorf(codes.Unimplemented, "method Sync not implemented") return status.Error(codes.Unimplemented, "method Sync not implemented")
} }
func (UnimplementedManagementServiceServer) GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error) { func (UnimplementedManagementServiceServer) GetServerKey(context.Context, *Empty) (*ServerKeyResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetServerKey not implemented") return nil, status.Error(codes.Unimplemented, "method GetServerKey not implemented")
} }
func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) (*Empty, error) { func (UnimplementedManagementServiceServer) IsHealthy(context.Context, *Empty) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method IsHealthy not implemented") return nil, status.Error(codes.Unimplemented, "method IsHealthy not implemented")
} }
func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) GetDeviceAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented") return nil, status.Error(codes.Unimplemented, "method GetDeviceAuthorizationFlow not implemented")
} }
func (UnimplementedManagementServiceServer) GetPKCEAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) GetPKCEAuthorizationFlow(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetPKCEAuthorizationFlow not implemented") return nil, status.Error(codes.Unimplemented, "method GetPKCEAuthorizationFlow not implemented")
} }
func (UnimplementedManagementServiceServer) SyncMeta(context.Context, *EncryptedMessage) (*Empty, error) { func (UnimplementedManagementServiceServer) SyncMeta(context.Context, *EncryptedMessage) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SyncMeta not implemented") return nil, status.Error(codes.Unimplemented, "method SyncMeta not implemented")
} }
func (UnimplementedManagementServiceServer) Logout(context.Context, *EncryptedMessage) (*Empty, error) { func (UnimplementedManagementServiceServer) Logout(context.Context, *EncryptedMessage) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented") return nil, status.Error(codes.Unimplemented, "method Logout not implemented")
} }
func (UnimplementedManagementServiceServer) Job(ManagementService_JobServer) error { func (UnimplementedManagementServiceServer) Job(grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]) error {
return status.Errorf(codes.Unimplemented, "method Job not implemented") return status.Error(codes.Unimplemented, "method Job not implemented")
} }
func (UnimplementedManagementServiceServer) CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) CreateExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateExpose not implemented") return nil, status.Error(codes.Unimplemented, "method CreateExpose not implemented")
} }
func (UnimplementedManagementServiceServer) RenewExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) RenewExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method RenewExpose not implemented") return nil, status.Error(codes.Unimplemented, "method RenewExpose not implemented")
} }
func (UnimplementedManagementServiceServer) StopExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedManagementServiceServer) StopExpose(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method StopExpose not implemented") return nil, status.Error(codes.Unimplemented, "method StopExpose not implemented")
} }
func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {} func (UnimplementedManagementServiceServer) mustEmbedUnimplementedManagementServiceServer() {}
func (UnimplementedManagementServiceServer) testEmbeddedByValue() {}
// UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeManagementServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ManagementServiceServer will // Use of this interface is not recommended, as added methods to ManagementServiceServer will
@@ -318,6 +320,13 @@ type UnsafeManagementServiceServer interface {
} }
func RegisterManagementServiceServer(s grpc.ServiceRegistrar, srv ManagementServiceServer) { func RegisterManagementServiceServer(s grpc.ServiceRegistrar, srv ManagementServiceServer) {
// If the following call panics, it indicates UnimplementedManagementServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&ManagementService_ServiceDesc, srv) s.RegisterService(&ManagementService_ServiceDesc, srv)
} }
@@ -331,7 +340,7 @@ func _ManagementService_Login_Handler(srv interface{}, ctx context.Context, dec
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/Login", FullMethod: ManagementService_Login_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).Login(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).Login(ctx, req.(*EncryptedMessage))
@@ -344,21 +353,11 @@ func _ManagementService_Sync_Handler(srv interface{}, stream grpc.ServerStream)
if err := stream.RecvMsg(m); err != nil { if err := stream.RecvMsg(m); err != nil {
return err return err
} }
return srv.(ManagementServiceServer).Sync(m, &managementServiceSyncServer{stream}) return srv.(ManagementServiceServer).Sync(m, &grpc.GenericServerStream[EncryptedMessage, EncryptedMessage]{ServerStream: stream})
} }
type ManagementService_SyncServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*EncryptedMessage) error type ManagementService_SyncServer = grpc.ServerStreamingServer[EncryptedMessage]
grpc.ServerStream
}
type managementServiceSyncServer struct {
grpc.ServerStream
}
func (x *managementServiceSyncServer) Send(m *EncryptedMessage) error {
return x.ServerStream.SendMsg(m)
}
func _ManagementService_GetServerKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _ManagementService_GetServerKey_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(Empty) in := new(Empty)
@@ -370,7 +369,7 @@ func _ManagementService_GetServerKey_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/GetServerKey", FullMethod: ManagementService_GetServerKey_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).GetServerKey(ctx, req.(*Empty)) return srv.(ManagementServiceServer).GetServerKey(ctx, req.(*Empty))
@@ -388,7 +387,7 @@ func _ManagementService_IsHealthy_Handler(srv interface{}, ctx context.Context,
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/isHealthy", FullMethod: ManagementService_IsHealthy_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).IsHealthy(ctx, req.(*Empty)) return srv.(ManagementServiceServer).IsHealthy(ctx, req.(*Empty))
@@ -406,7 +405,7 @@ func _ManagementService_GetDeviceAuthorizationFlow_Handler(srv interface{}, ctx
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/GetDeviceAuthorizationFlow", FullMethod: ManagementService_GetDeviceAuthorizationFlow_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).GetDeviceAuthorizationFlow(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).GetDeviceAuthorizationFlow(ctx, req.(*EncryptedMessage))
@@ -424,7 +423,7 @@ func _ManagementService_GetPKCEAuthorizationFlow_Handler(srv interface{}, ctx co
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/GetPKCEAuthorizationFlow", FullMethod: ManagementService_GetPKCEAuthorizationFlow_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).GetPKCEAuthorizationFlow(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).GetPKCEAuthorizationFlow(ctx, req.(*EncryptedMessage))
@@ -442,7 +441,7 @@ func _ManagementService_SyncMeta_Handler(srv interface{}, ctx context.Context, d
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/SyncMeta", FullMethod: ManagementService_SyncMeta_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).SyncMeta(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).SyncMeta(ctx, req.(*EncryptedMessage))
@@ -460,7 +459,7 @@ func _ManagementService_Logout_Handler(srv interface{}, ctx context.Context, dec
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/Logout", FullMethod: ManagementService_Logout_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).Logout(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).Logout(ctx, req.(*EncryptedMessage))
@@ -469,30 +468,11 @@ func _ManagementService_Logout_Handler(srv interface{}, ctx context.Context, dec
} }
func _ManagementService_Job_Handler(srv interface{}, stream grpc.ServerStream) error { func _ManagementService_Job_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(ManagementServiceServer).Job(&managementServiceJobServer{stream}) return srv.(ManagementServiceServer).Job(&grpc.GenericServerStream[EncryptedMessage, EncryptedMessage]{ServerStream: stream})
} }
type ManagementService_JobServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*EncryptedMessage) error type ManagementService_JobServer = grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]
Recv() (*EncryptedMessage, error)
grpc.ServerStream
}
type managementServiceJobServer struct {
grpc.ServerStream
}
func (x *managementServiceJobServer) Send(m *EncryptedMessage) error {
return x.ServerStream.SendMsg(m)
}
func (x *managementServiceJobServer) Recv() (*EncryptedMessage, error) {
m := new(EncryptedMessage)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
func _ManagementService_CreateExpose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _ManagementService_CreateExpose_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(EncryptedMessage) in := new(EncryptedMessage)
@@ -504,7 +484,7 @@ func _ManagementService_CreateExpose_Handler(srv interface{}, ctx context.Contex
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/CreateExpose", FullMethod: ManagementService_CreateExpose_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).CreateExpose(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).CreateExpose(ctx, req.(*EncryptedMessage))
@@ -522,7 +502,7 @@ func _ManagementService_RenewExpose_Handler(srv interface{}, ctx context.Context
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/RenewExpose", FullMethod: ManagementService_RenewExpose_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).RenewExpose(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).RenewExpose(ctx, req.(*EncryptedMessage))
@@ -540,7 +520,7 @@ func _ManagementService_StopExpose_Handler(srv interface{}, ctx context.Context,
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ManagementService/StopExpose", FullMethod: ManagementService_StopExpose_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ManagementServiceServer).StopExpose(ctx, req.(*EncryptedMessage)) return srv.(ManagementServiceServer).StopExpose(ctx, req.(*EncryptedMessage))

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,15 @@ import "google/protobuf/timestamp.proto";
service ProxyService { service ProxyService {
rpc GetMappingUpdate(GetMappingUpdateRequest) returns (stream GetMappingUpdateResponse); rpc GetMappingUpdate(GetMappingUpdateRequest) returns (stream GetMappingUpdateResponse);
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
// stream and then sends an ack after each batch is fully processed.
// Management waits for the ack before sending the next batch, providing
// application-level back-pressure during large initial syncs.
// Old proxies continue using GetMappingUpdate; old management servers
// return Unimplemented for this RPC and proxies fall back.
rpc SyncMappings(stream SyncMappingsRequest) returns (stream SyncMappingsResponse);
rpc SendAccessLog(SendAccessLogRequest) returns (SendAccessLogResponse); rpc SendAccessLog(SendAccessLogRequest) returns (SendAccessLogResponse);
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse); rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse);
@@ -246,3 +255,35 @@ message ValidateSessionResponse {
string user_email = 3; string user_email = 3;
string denied_reason = 4; string denied_reason = 4;
} }
// SyncMappingsRequest is sent by the proxy on the bidirectional SyncMappings
// stream. The first message MUST be an init; all subsequent messages MUST be
// acks.
message SyncMappingsRequest {
oneof msg {
SyncMappingsInit init = 1;
SyncMappingsAck ack = 2;
}
}
// SyncMappingsInit is the first message on the stream, carrying the same
// identification fields as GetMappingUpdateRequest.
message SyncMappingsInit {
string proxy_id = 1;
string version = 2;
google.protobuf.Timestamp started_at = 3;
string address = 4;
ProxyCapabilities capabilities = 5;
}
// SyncMappingsAck is sent by the proxy after it has fully processed a batch.
// Management waits for this before sending the next batch.
message SyncMappingsAck {}
// SyncMappingsResponse is a batch of mappings sent by management.
// Identical semantics to GetMappingUpdateResponse.
message SyncMappingsResponse {
repeated ProxyMapping mapping = 1;
// initial_sync_complete is set on the last message of the initial snapshot.
bool initial_sync_complete = 2;
}

View File

@@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v6.33.1
// source: proxy_service.proto
package proto package proto
@@ -11,14 +15,36 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const (
ProxyService_GetMappingUpdate_FullMethodName = "/management.ProxyService/GetMappingUpdate"
ProxyService_SyncMappings_FullMethodName = "/management.ProxyService/SyncMappings"
ProxyService_SendAccessLog_FullMethodName = "/management.ProxyService/SendAccessLog"
ProxyService_Authenticate_FullMethodName = "/management.ProxyService/Authenticate"
ProxyService_SendStatusUpdate_FullMethodName = "/management.ProxyService/SendStatusUpdate"
ProxyService_CreateProxyPeer_FullMethodName = "/management.ProxyService/CreateProxyPeer"
ProxyService_GetOIDCURL_FullMethodName = "/management.ProxyService/GetOIDCURL"
ProxyService_ValidateSession_FullMethodName = "/management.ProxyService/ValidateSession"
)
// ProxyServiceClient is the client API for ProxyService service. // ProxyServiceClient is the client API for ProxyService service.
// //
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
//
// ProxyService - Management is the SERVER, Proxy is the CLIENT
// Proxy initiates connection to management
type ProxyServiceClient interface { type ProxyServiceClient interface {
GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (ProxyService_GetMappingUpdateClient, error) GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetMappingUpdateResponse], error)
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
// stream and then sends an ack after each batch is fully processed.
// Management waits for the ack before sending the next batch, providing
// application-level back-pressure during large initial syncs.
// Old proxies continue using GetMappingUpdate; old management servers
// return Unimplemented for this RPC and proxies fall back.
SyncMappings(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncMappingsRequest, SyncMappingsResponse], error)
SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error) SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error)
Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error) Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error)
SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error) SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error)
@@ -37,12 +63,13 @@ func NewProxyServiceClient(cc grpc.ClientConnInterface) ProxyServiceClient {
return &proxyServiceClient{cc} return &proxyServiceClient{cc}
} }
func (c *proxyServiceClient) GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (ProxyService_GetMappingUpdateClient, error) { func (c *proxyServiceClient) GetMappingUpdate(ctx context.Context, in *GetMappingUpdateRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[GetMappingUpdateResponse], error) {
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[0], "/management.ProxyService/GetMappingUpdate", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[0], ProxyService_GetMappingUpdate_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &proxyServiceGetMappingUpdateClient{stream} x := &grpc.GenericClientStream[GetMappingUpdateRequest, GetMappingUpdateResponse]{ClientStream: stream}
if err := x.ClientStream.SendMsg(in); err != nil { if err := x.ClientStream.SendMsg(in); err != nil {
return nil, err return nil, err
} }
@@ -52,26 +79,26 @@ func (c *proxyServiceClient) GetMappingUpdate(ctx context.Context, in *GetMappin
return x, nil return x, nil
} }
type ProxyService_GetMappingUpdateClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Recv() (*GetMappingUpdateResponse, error) type ProxyService_GetMappingUpdateClient = grpc.ServerStreamingClient[GetMappingUpdateResponse]
grpc.ClientStream
}
type proxyServiceGetMappingUpdateClient struct { func (c *proxyServiceClient) SyncMappings(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[SyncMappingsRequest, SyncMappingsResponse], error) {
grpc.ClientStream cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
} stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[1], ProxyService_SyncMappings_FullMethodName, cOpts...)
if err != nil {
func (x *proxyServiceGetMappingUpdateClient) Recv() (*GetMappingUpdateResponse, error) {
m := new(GetMappingUpdateResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err return nil, err
} }
return m, nil x := &grpc.GenericClientStream[SyncMappingsRequest, SyncMappingsResponse]{ClientStream: stream}
return x, nil
} }
// This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
type ProxyService_SyncMappingsClient = grpc.BidiStreamingClient[SyncMappingsRequest, SyncMappingsResponse]
func (c *proxyServiceClient) SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error) { func (c *proxyServiceClient) SendAccessLog(ctx context.Context, in *SendAccessLogRequest, opts ...grpc.CallOption) (*SendAccessLogResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SendAccessLogResponse) out := new(SendAccessLogResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/SendAccessLog", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_SendAccessLog_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -79,8 +106,9 @@ func (c *proxyServiceClient) SendAccessLog(ctx context.Context, in *SendAccessLo
} }
func (c *proxyServiceClient) Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error) { func (c *proxyServiceClient) Authenticate(ctx context.Context, in *AuthenticateRequest, opts ...grpc.CallOption) (*AuthenticateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AuthenticateResponse) out := new(AuthenticateResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/Authenticate", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_Authenticate_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -88,8 +116,9 @@ func (c *proxyServiceClient) Authenticate(ctx context.Context, in *AuthenticateR
} }
func (c *proxyServiceClient) SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error) { func (c *proxyServiceClient) SendStatusUpdate(ctx context.Context, in *SendStatusUpdateRequest, opts ...grpc.CallOption) (*SendStatusUpdateResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(SendStatusUpdateResponse) out := new(SendStatusUpdateResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/SendStatusUpdate", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_SendStatusUpdate_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,8 +126,9 @@ func (c *proxyServiceClient) SendStatusUpdate(ctx context.Context, in *SendStatu
} }
func (c *proxyServiceClient) CreateProxyPeer(ctx context.Context, in *CreateProxyPeerRequest, opts ...grpc.CallOption) (*CreateProxyPeerResponse, error) { func (c *proxyServiceClient) CreateProxyPeer(ctx context.Context, in *CreateProxyPeerRequest, opts ...grpc.CallOption) (*CreateProxyPeerResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(CreateProxyPeerResponse) out := new(CreateProxyPeerResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/CreateProxyPeer", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_CreateProxyPeer_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -106,8 +136,9 @@ func (c *proxyServiceClient) CreateProxyPeer(ctx context.Context, in *CreateProx
} }
func (c *proxyServiceClient) GetOIDCURL(ctx context.Context, in *GetOIDCURLRequest, opts ...grpc.CallOption) (*GetOIDCURLResponse, error) { func (c *proxyServiceClient) GetOIDCURL(ctx context.Context, in *GetOIDCURLRequest, opts ...grpc.CallOption) (*GetOIDCURLResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(GetOIDCURLResponse) out := new(GetOIDCURLResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/GetOIDCURL", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_GetOIDCURL_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -115,8 +146,9 @@ func (c *proxyServiceClient) GetOIDCURL(ctx context.Context, in *GetOIDCURLReque
} }
func (c *proxyServiceClient) ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error) { func (c *proxyServiceClient) ValidateSession(ctx context.Context, in *ValidateSessionRequest, opts ...grpc.CallOption) (*ValidateSessionResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ValidateSessionResponse) out := new(ValidateSessionResponse)
err := c.cc.Invoke(ctx, "/management.ProxyService/ValidateSession", in, out, opts...) err := c.cc.Invoke(ctx, ProxyService_ValidateSession_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -125,9 +157,20 @@ func (c *proxyServiceClient) ValidateSession(ctx context.Context, in *ValidateSe
// ProxyServiceServer is the server API for ProxyService service. // ProxyServiceServer is the server API for ProxyService service.
// All implementations must embed UnimplementedProxyServiceServer // All implementations must embed UnimplementedProxyServiceServer
// for forward compatibility // for forward compatibility.
//
// ProxyService - Management is the SERVER, Proxy is the CLIENT
// Proxy initiates connection to management
type ProxyServiceServer interface { type ProxyServiceServer interface {
GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error GetMappingUpdate(*GetMappingUpdateRequest, grpc.ServerStreamingServer[GetMappingUpdateResponse]) error
// SyncMappings is a bidirectional stream that replaces GetMappingUpdate for
// new proxies. The proxy sends an initial SyncMappingsRequest to start the
// stream and then sends an ack after each batch is fully processed.
// Management waits for the ack before sending the next batch, providing
// application-level back-pressure during large initial syncs.
// Old proxies continue using GetMappingUpdate; old management servers
// return Unimplemented for this RPC and proxies fall back.
SyncMappings(grpc.BidiStreamingServer[SyncMappingsRequest, SyncMappingsResponse]) error
SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error) SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error)
Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error) Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error)
SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error) SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error)
@@ -139,32 +182,39 @@ type ProxyServiceServer interface {
mustEmbedUnimplementedProxyServiceServer() mustEmbedUnimplementedProxyServiceServer()
} }
// UnimplementedProxyServiceServer must be embedded to have forward compatible implementations. // UnimplementedProxyServiceServer must be embedded to have
type UnimplementedProxyServiceServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedProxyServiceServer struct{}
func (UnimplementedProxyServiceServer) GetMappingUpdate(*GetMappingUpdateRequest, ProxyService_GetMappingUpdateServer) error { func (UnimplementedProxyServiceServer) GetMappingUpdate(*GetMappingUpdateRequest, grpc.ServerStreamingServer[GetMappingUpdateResponse]) error {
return status.Errorf(codes.Unimplemented, "method GetMappingUpdate not implemented") return status.Error(codes.Unimplemented, "method GetMappingUpdate not implemented")
}
func (UnimplementedProxyServiceServer) SyncMappings(grpc.BidiStreamingServer[SyncMappingsRequest, SyncMappingsResponse]) error {
return status.Error(codes.Unimplemented, "method SyncMappings not implemented")
} }
func (UnimplementedProxyServiceServer) SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error) { func (UnimplementedProxyServiceServer) SendAccessLog(context.Context, *SendAccessLogRequest) (*SendAccessLogResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendAccessLog not implemented") return nil, status.Error(codes.Unimplemented, "method SendAccessLog not implemented")
} }
func (UnimplementedProxyServiceServer) Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error) { func (UnimplementedProxyServiceServer) Authenticate(context.Context, *AuthenticateRequest) (*AuthenticateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Authenticate not implemented") return nil, status.Error(codes.Unimplemented, "method Authenticate not implemented")
} }
func (UnimplementedProxyServiceServer) SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error) { func (UnimplementedProxyServiceServer) SendStatusUpdate(context.Context, *SendStatusUpdateRequest) (*SendStatusUpdateResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method SendStatusUpdate not implemented") return nil, status.Error(codes.Unimplemented, "method SendStatusUpdate not implemented")
} }
func (UnimplementedProxyServiceServer) CreateProxyPeer(context.Context, *CreateProxyPeerRequest) (*CreateProxyPeerResponse, error) { func (UnimplementedProxyServiceServer) CreateProxyPeer(context.Context, *CreateProxyPeerRequest) (*CreateProxyPeerResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateProxyPeer not implemented") return nil, status.Error(codes.Unimplemented, "method CreateProxyPeer not implemented")
} }
func (UnimplementedProxyServiceServer) GetOIDCURL(context.Context, *GetOIDCURLRequest) (*GetOIDCURLResponse, error) { func (UnimplementedProxyServiceServer) GetOIDCURL(context.Context, *GetOIDCURLRequest) (*GetOIDCURLResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOIDCURL not implemented") return nil, status.Error(codes.Unimplemented, "method GetOIDCURL not implemented")
} }
func (UnimplementedProxyServiceServer) ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) { func (UnimplementedProxyServiceServer) ValidateSession(context.Context, *ValidateSessionRequest) (*ValidateSessionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ValidateSession not implemented") return nil, status.Error(codes.Unimplemented, "method ValidateSession not implemented")
} }
func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {} func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {}
func (UnimplementedProxyServiceServer) testEmbeddedByValue() {}
// UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service. // UnsafeProxyServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to ProxyServiceServer will // Use of this interface is not recommended, as added methods to ProxyServiceServer will
@@ -174,6 +224,13 @@ type UnsafeProxyServiceServer interface {
} }
func RegisterProxyServiceServer(s grpc.ServiceRegistrar, srv ProxyServiceServer) { func RegisterProxyServiceServer(s grpc.ServiceRegistrar, srv ProxyServiceServer) {
// If the following call panics, it indicates UnimplementedProxyServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&ProxyService_ServiceDesc, srv) s.RegisterService(&ProxyService_ServiceDesc, srv)
} }
@@ -182,21 +239,18 @@ func _ProxyService_GetMappingUpdate_Handler(srv interface{}, stream grpc.ServerS
if err := stream.RecvMsg(m); err != nil { if err := stream.RecvMsg(m); err != nil {
return err return err
} }
return srv.(ProxyServiceServer).GetMappingUpdate(m, &proxyServiceGetMappingUpdateServer{stream}) return srv.(ProxyServiceServer).GetMappingUpdate(m, &grpc.GenericServerStream[GetMappingUpdateRequest, GetMappingUpdateResponse]{ServerStream: stream})
} }
type ProxyService_GetMappingUpdateServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*GetMappingUpdateResponse) error type ProxyService_GetMappingUpdateServer = grpc.ServerStreamingServer[GetMappingUpdateResponse]
grpc.ServerStream
func _ProxyService_SyncMappings_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(ProxyServiceServer).SyncMappings(&grpc.GenericServerStream[SyncMappingsRequest, SyncMappingsResponse]{ServerStream: stream})
} }
type proxyServiceGetMappingUpdateServer struct { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
grpc.ServerStream type ProxyService_SyncMappingsServer = grpc.BidiStreamingServer[SyncMappingsRequest, SyncMappingsResponse]
}
func (x *proxyServiceGetMappingUpdateServer) Send(m *GetMappingUpdateResponse) error {
return x.ServerStream.SendMsg(m)
}
func _ProxyService_SendAccessLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _ProxyService_SendAccessLog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendAccessLogRequest) in := new(SendAccessLogRequest)
@@ -208,7 +262,7 @@ func _ProxyService_SendAccessLog_Handler(srv interface{}, ctx context.Context, d
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/SendAccessLog", FullMethod: ProxyService_SendAccessLog_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).SendAccessLog(ctx, req.(*SendAccessLogRequest)) return srv.(ProxyServiceServer).SendAccessLog(ctx, req.(*SendAccessLogRequest))
@@ -226,7 +280,7 @@ func _ProxyService_Authenticate_Handler(srv interface{}, ctx context.Context, de
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/Authenticate", FullMethod: ProxyService_Authenticate_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).Authenticate(ctx, req.(*AuthenticateRequest)) return srv.(ProxyServiceServer).Authenticate(ctx, req.(*AuthenticateRequest))
@@ -244,7 +298,7 @@ func _ProxyService_SendStatusUpdate_Handler(srv interface{}, ctx context.Context
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/SendStatusUpdate", FullMethod: ProxyService_SendStatusUpdate_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).SendStatusUpdate(ctx, req.(*SendStatusUpdateRequest)) return srv.(ProxyServiceServer).SendStatusUpdate(ctx, req.(*SendStatusUpdateRequest))
@@ -262,7 +316,7 @@ func _ProxyService_CreateProxyPeer_Handler(srv interface{}, ctx context.Context,
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/CreateProxyPeer", FullMethod: ProxyService_CreateProxyPeer_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).CreateProxyPeer(ctx, req.(*CreateProxyPeerRequest)) return srv.(ProxyServiceServer).CreateProxyPeer(ctx, req.(*CreateProxyPeerRequest))
@@ -280,7 +334,7 @@ func _ProxyService_GetOIDCURL_Handler(srv interface{}, ctx context.Context, dec
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/GetOIDCURL", FullMethod: ProxyService_GetOIDCURL_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).GetOIDCURL(ctx, req.(*GetOIDCURLRequest)) return srv.(ProxyServiceServer).GetOIDCURL(ctx, req.(*GetOIDCURLRequest))
@@ -298,7 +352,7 @@ func _ProxyService_ValidateSession_Handler(srv interface{}, ctx context.Context,
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/management.ProxyService/ValidateSession", FullMethod: ProxyService_ValidateSession_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ProxyServiceServer).ValidateSession(ctx, req.(*ValidateSessionRequest)) return srv.(ProxyServiceServer).ValidateSession(ctx, req.(*ValidateSessionRequest))
@@ -344,6 +398,12 @@ var ProxyService_ServiceDesc = grpc.ServiceDesc{
Handler: _ProxyService_GetMappingUpdate_Handler, Handler: _ProxyService_GetMappingUpdate_Handler,
ServerStreams: true, ServerStreams: true,
}, },
{
StreamName: "SyncMappings",
Handler: _ProxyService_SyncMappings_Handler,
ServerStreams: true,
ClientStreams: true,
},
}, },
Metadata: "proxy_service.proto", Metadata: "proxy_service.proto",
} }

View File

@@ -9,9 +9,21 @@ then
fi fi
old_pwd=$(pwd) old_pwd=$(pwd)
script_path=$(dirname $(realpath "$0")) script_path=$(dirname "$(realpath "$0")")
cd "$script_path" cd "$script_path"
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1 repo_root=$(git rev-parse --show-toplevel)
# shellcheck source=/dev/null
. "$repo_root/proto-tools.env"
actual_protoc=$(protoc --version | awk '{print $2}')
if [[ "$actual_protoc" != "$PROTOC_VERSION" ]]; then
echo "ERROR: protoc version $actual_protoc differs from pinned $PROTOC_VERSION" >&2
echo "Install protoc $PROTOC_VERSION from https://github.com/protocolbuffers/protobuf/releases" >&2
exit 1
fi
go install "google.golang.org/protobuf/cmd/protoc-gen-go@${PROTOC_GEN_GO_VERSION}"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc@${PROTOC_GEN_GO_GRPC_VERSION}"
protoc -I ./ ./signalexchange.proto --go_out=../ --go-grpc_out=../ protoc -I ./ ./signalexchange.proto --go_out=../ --go-grpc_out=../
cd "$old_pwd" cd "$old_pwd"

View File

@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.26.0 // protoc-gen-go v1.36.6
// protoc v3.21.12 // protoc v6.33.1
// source: signalexchange.proto // source: signalexchange.proto
package proto package proto
@@ -12,6 +12,7 @@ import (
_ "google.golang.org/protobuf/types/descriptorpb" _ "google.golang.org/protobuf/types/descriptorpb"
reflect "reflect" reflect "reflect"
sync "sync" sync "sync"
unsafe "unsafe"
) )
const ( const (
@@ -80,25 +81,22 @@ func (Body_Type) EnumDescriptor() ([]byte, []int) {
// Used for sending through signal. // Used for sending through signal.
// The body of this message is the Body message encrypted with the Wireguard private key and the remote Peer key // The body of this message is the Body message encrypted with the Wireguard private key and the remote Peer key
type EncryptedMessage struct { type EncryptedMessage struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Wireguard public key // Wireguard public key
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
// Wireguard public key of the remote peer to connect to // Wireguard public key of the remote peer to connect to
RemoteKey string `protobuf:"bytes,3,opt,name=remoteKey,proto3" json:"remoteKey,omitempty"` RemoteKey string `protobuf:"bytes,3,opt,name=remoteKey,proto3" json:"remoteKey,omitempty"`
// encrypted message Body // encrypted message Body
Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` Body []byte `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *EncryptedMessage) Reset() { func (x *EncryptedMessage) Reset() {
*x = EncryptedMessage{} *x = EncryptedMessage{}
if protoimpl.UnsafeEnabled { mi := &file_signalexchange_proto_msgTypes[0]
mi := &file_signalexchange_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *EncryptedMessage) String() string { func (x *EncryptedMessage) String() string {
@@ -109,7 +107,7 @@ func (*EncryptedMessage) ProtoMessage() {}
func (x *EncryptedMessage) ProtoReflect() protoreflect.Message { func (x *EncryptedMessage) ProtoReflect() protoreflect.Message {
mi := &file_signalexchange_proto_msgTypes[0] mi := &file_signalexchange_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -147,24 +145,21 @@ func (x *EncryptedMessage) GetBody() []byte {
// A decrypted representation of the EncryptedMessage. Used locally before/after encryption // A decrypted representation of the EncryptedMessage. Used locally before/after encryption
type Message struct { type Message struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// WireGuard public key // WireGuard public key
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
// WireGuard public key of the remote peer to connect to // WireGuard public key of the remote peer to connect to
RemoteKey string `protobuf:"bytes,3,opt,name=remoteKey,proto3" json:"remoteKey,omitempty"` RemoteKey string `protobuf:"bytes,3,opt,name=remoteKey,proto3" json:"remoteKey,omitempty"`
Body *Body `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"` Body *Body `protobuf:"bytes,4,opt,name=body,proto3" json:"body,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *Message) Reset() { func (x *Message) Reset() {
*x = Message{} *x = Message{}
if protoimpl.UnsafeEnabled { mi := &file_signalexchange_proto_msgTypes[1]
mi := &file_signalexchange_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *Message) String() string { func (x *Message) String() string {
@@ -175,7 +170,7 @@ func (*Message) ProtoMessage() {}
func (x *Message) ProtoReflect() protoreflect.Message { func (x *Message) ProtoReflect() protoreflect.Message {
mi := &file_signalexchange_proto_msgTypes[1] mi := &file_signalexchange_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -214,12 +209,9 @@ func (x *Message) GetBody() *Body {
// Actual body of the message that can contain credentials (type OFFER/ANSWER) or connection Candidate // Actual body of the message that can contain credentials (type OFFER/ANSWER) or connection Candidate
// This part will be encrypted // This part will be encrypted
type Body struct { type Body struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Type Body_Type `protobuf:"varint,1,opt,name=type,proto3,enum=signalexchange.Body_Type" json:"type,omitempty"`
unknownFields protoimpl.UnknownFields Payload string `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
Type Body_Type `protobuf:"varint,1,opt,name=type,proto3,enum=signalexchange.Body_Type" json:"type,omitempty"`
Payload string `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
// wgListenPort is an actual WireGuard listen port // wgListenPort is an actual WireGuard listen port
WgListenPort uint32 `protobuf:"varint,3,opt,name=wgListenPort,proto3" json:"wgListenPort,omitempty"` WgListenPort uint32 `protobuf:"varint,3,opt,name=wgListenPort,proto3" json:"wgListenPort,omitempty"`
NetBirdVersion string `protobuf:"bytes,4,opt,name=netBirdVersion,proto3" json:"netBirdVersion,omitempty"` NetBirdVersion string `protobuf:"bytes,4,opt,name=netBirdVersion,proto3" json:"netBirdVersion,omitempty"`
@@ -236,15 +228,15 @@ type Body struct {
// fallback dial target when DNS resolution of relayServerAddress fails. // fallback dial target when DNS resolution of relayServerAddress fails.
// SNI/TLS verification still uses relayServerAddress. // SNI/TLS verification still uses relayServerAddress.
RelayServerIP []byte `protobuf:"bytes,11,opt,name=relayServerIP,proto3,oneof" json:"relayServerIP,omitempty"` RelayServerIP []byte `protobuf:"bytes,11,opt,name=relayServerIP,proto3,oneof" json:"relayServerIP,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *Body) Reset() { func (x *Body) Reset() {
*x = Body{} *x = Body{}
if protoimpl.UnsafeEnabled { mi := &file_signalexchange_proto_msgTypes[2]
mi := &file_signalexchange_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *Body) String() string { func (x *Body) String() string {
@@ -255,7 +247,7 @@ func (*Body) ProtoMessage() {}
func (x *Body) ProtoReflect() protoreflect.Message { func (x *Body) ProtoReflect() protoreflect.Message {
mi := &file_signalexchange_proto_msgTypes[2] mi := &file_signalexchange_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -342,20 +334,17 @@ func (x *Body) GetRelayServerIP() []byte {
// Mode indicates a connection mode // Mode indicates a connection mode
type Mode struct { type Mode struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache Direct *bool `protobuf:"varint,1,opt,name=direct,proto3,oneof" json:"direct,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Direct *bool `protobuf:"varint,1,opt,name=direct,proto3,oneof" json:"direct,omitempty"`
} }
func (x *Mode) Reset() { func (x *Mode) Reset() {
*x = Mode{} *x = Mode{}
if protoimpl.UnsafeEnabled { mi := &file_signalexchange_proto_msgTypes[3]
mi := &file_signalexchange_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *Mode) String() string { func (x *Mode) String() string {
@@ -366,7 +355,7 @@ func (*Mode) ProtoMessage() {}
func (x *Mode) ProtoReflect() protoreflect.Message { func (x *Mode) ProtoReflect() protoreflect.Message {
mi := &file_signalexchange_proto_msgTypes[3] mi := &file_signalexchange_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -389,22 +378,19 @@ func (x *Mode) GetDirect() bool {
} }
type RosenpassConfig struct { type RosenpassConfig struct {
state protoimpl.MessageState state protoimpl.MessageState `protogen:"open.v1"`
sizeCache protoimpl.SizeCache RosenpassPubKey []byte `protobuf:"bytes,1,opt,name=rosenpassPubKey,proto3" json:"rosenpassPubKey,omitempty"`
unknownFields protoimpl.UnknownFields
RosenpassPubKey []byte `protobuf:"bytes,1,opt,name=rosenpassPubKey,proto3" json:"rosenpassPubKey,omitempty"`
// rosenpassServerAddr is an IP:port of the rosenpass service // rosenpassServerAddr is an IP:port of the rosenpass service
RosenpassServerAddr string `protobuf:"bytes,2,opt,name=rosenpassServerAddr,proto3" json:"rosenpassServerAddr,omitempty"` RosenpassServerAddr string `protobuf:"bytes,2,opt,name=rosenpassServerAddr,proto3" json:"rosenpassServerAddr,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
} }
func (x *RosenpassConfig) Reset() { func (x *RosenpassConfig) Reset() {
*x = RosenpassConfig{} *x = RosenpassConfig{}
if protoimpl.UnsafeEnabled { mi := &file_signalexchange_proto_msgTypes[4]
mi := &file_signalexchange_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi)
ms.StoreMessageInfo(mi)
}
} }
func (x *RosenpassConfig) String() string { func (x *RosenpassConfig) String() string {
@@ -415,7 +401,7 @@ func (*RosenpassConfig) ProtoMessage() {}
func (x *RosenpassConfig) ProtoReflect() protoreflect.Message { func (x *RosenpassConfig) ProtoReflect() protoreflect.Message {
mi := &file_signalexchange_proto_msgTypes[4] mi := &file_signalexchange_proto_msgTypes[4]
if protoimpl.UnsafeEnabled && x != nil { if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil { if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi) ms.StoreMessageInfo(mi)
@@ -446,100 +432,66 @@ func (x *RosenpassConfig) GetRosenpassServerAddr() string {
var File_signalexchange_proto protoreflect.FileDescriptor var File_signalexchange_proto protoreflect.FileDescriptor
var file_signalexchange_proto_rawDesc = []byte{ const file_signalexchange_proto_rawDesc = "" +
0x0a, 0x14, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, "\n" +
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, "\x14signalexchange.proto\x12\x0esignalexchange\x1a google/protobuf/descriptor.proto\"V\n" +
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, "\x10EncryptedMessage\x12\x10\n" +
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, "\x03key\x18\x02 \x01(\tR\x03key\x12\x1c\n" +
0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x56, 0x0a, 0x10, 0x45, 0x6e, 0x63, 0x72, "\tremoteKey\x18\x03 \x01(\tR\tremoteKey\x12\x12\n" +
0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, "\x04body\x18\x04 \x01(\fR\x04body\"c\n" +
0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, "\aMessage\x12\x10\n" +
0x0a, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, "\x03key\x18\x02 \x01(\tR\x03key\x12\x1c\n" +
0x09, 0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, "\tremoteKey\x18\x03 \x01(\tR\tremoteKey\x12(\n" +
0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, "\x04body\x18\x04 \x01(\v2\x14.signalexchange.BodyR\x04body\"\xc3\x04\n" +
0x22, 0x63, 0x0a, 0x07, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, "\x04Body\x12-\n" +
0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, "\x04type\x18\x01 \x01(\x0e2\x19.signalexchange.Body.TypeR\x04type\x12\x18\n" +
0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, "\apayload\x18\x02 \x01(\tR\apayload\x12\"\n" +
0x52, 0x09, 0x72, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x28, 0x0a, 0x04, 0x62, "\fwgListenPort\x18\x03 \x01(\rR\fwgListenPort\x12&\n" +
0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, "\x0enetBirdVersion\x18\x04 \x01(\tR\x0enetBirdVersion\x12(\n" +
0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f, 0x64, 0x79, 0x52, "\x04mode\x18\x05 \x01(\v2\x14.signalexchange.ModeR\x04mode\x12,\n" +
0x04, 0x62, 0x6f, 0x64, 0x79, 0x22, 0xc3, 0x04, 0x0a, 0x04, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x2d, "\x11featuresSupported\x18\x06 \x03(\rR\x11featuresSupported\x12I\n" +
0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x19, 0x2e, 0x73, "\x0frosenpassConfig\x18\a \x01(\v2\x1f.signalexchange.RosenpassConfigR\x0frosenpassConfig\x123\n" +
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x42, 0x6f, "\x12relayServerAddress\x18\b \x01(\tH\x00R\x12relayServerAddress\x88\x01\x01\x12!\n" +
0x64, 0x79, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, "\tsessionId\x18\n" +
0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, " \x01(\fH\x01R\tsessionId\x88\x01\x01\x12)\n" +
0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x22, 0x0a, 0x0c, 0x77, 0x67, 0x4c, 0x69, 0x73, "\rrelayServerIP\x18\v \x01(\fH\x02R\rrelayServerIP\x88\x01\x01\"C\n" +
0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x77, "\x04Type\x12\t\n" +
0x67, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x6e, "\x05OFFER\x10\x00\x12\n" +
0x65, 0x74, 0x42, 0x69, 0x72, 0x64, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, "\n" +
0x01, 0x28, 0x09, 0x52, 0x0e, 0x6e, 0x65, 0x74, 0x42, 0x69, 0x72, 0x64, 0x56, 0x65, 0x72, 0x73, "\x06ANSWER\x10\x01\x12\r\n" +
0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, "\tCANDIDATE\x10\x02\x12\b\n" +
0x0b, 0x32, 0x14, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, "\x04MODE\x10\x04\x12\v\n" +
0x67, 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x2c, 0x0a, "\aGO_IDLE\x10\x05B\x15\n" +
0x11, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, "\x13_relayServerAddressB\f\n" +
0x65, 0x64, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x11, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, "\n" +
0x65, 0x73, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, 0x0f, 0x72, "_sessionIdB\x10\n" +
0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x07, "\x0e_relayServerIPJ\x04\b\t\x10\n" +
0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, "\".\n" +
0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x52, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, "\x04Mode\x12\x1b\n" +
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, "\x06direct\x18\x01 \x01(\bH\x00R\x06direct\x88\x01\x01B\t\n" +
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x33, 0x0a, 0x12, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, "\a_direct\"m\n" +
0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x08, 0x20, 0x01, "\x0fRosenpassConfig\x12(\n" +
0x28, 0x09, 0x48, 0x00, 0x52, 0x12, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, "\x0frosenpassPubKey\x18\x01 \x01(\fR\x0frosenpassPubKey\x120\n" +
0x72, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x88, 0x01, 0x01, 0x12, 0x21, 0x0a, 0x09, 0x73, "\x13rosenpassServerAddr\x18\x02 \x01(\tR\x13rosenpassServerAddr2\xb9\x01\n" +
0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x01, "\x0eSignalExchange\x12L\n" +
0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x88, 0x01, 0x01, 0x12, 0x29, "\x04Send\x12 .signalexchange.EncryptedMessage\x1a .signalexchange.EncryptedMessage\"\x00\x12Y\n" +
0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x50, 0x18, "\rConnectStream\x12 .signalexchange.EncryptedMessage\x1a .signalexchange.EncryptedMessage\"\x00(\x010\x01B\bZ\x06/protob\x06proto3"
0x0b, 0x20, 0x01, 0x28, 0x0c, 0x48, 0x02, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65,
0x72, 0x76, 0x65, 0x72, 0x49, 0x50, 0x88, 0x01, 0x01, 0x22, 0x43, 0x0a, 0x04, 0x54, 0x79, 0x70,
0x65, 0x12, 0x09, 0x0a, 0x05, 0x4f, 0x46, 0x46, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06,
0x41, 0x4e, 0x53, 0x57, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x41, 0x4e, 0x44,
0x49, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x4f, 0x44, 0x45, 0x10,
0x04, 0x12, 0x0b, 0x0a, 0x07, 0x47, 0x4f, 0x5f, 0x49, 0x44, 0x4c, 0x45, 0x10, 0x05, 0x42, 0x15,
0x0a, 0x13, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64,
0x64, 0x72, 0x65, 0x73, 0x73, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f,
0x6e, 0x49, 0x64, 0x42, 0x10, 0x0a, 0x0e, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x79, 0x53, 0x65, 0x72,
0x76, 0x65, 0x72, 0x49, 0x50, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x2e, 0x0a, 0x04, 0x4d,
0x6f, 0x64, 0x65, 0x12, 0x1b, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x01, 0x20,
0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x88, 0x01, 0x01,
0x42, 0x09, 0x0a, 0x07, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x6d, 0x0a, 0x0f, 0x52,
0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x28,
0x0a, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61,
0x73, 0x73, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x72, 0x6f, 0x73, 0x65,
0x6e, 0x70, 0x61, 0x73, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x18,
0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x6f, 0x73, 0x65, 0x6e, 0x70, 0x61, 0x73, 0x73,
0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x41, 0x64, 0x64, 0x72, 0x32, 0xb9, 0x01, 0x0a, 0x0e, 0x53,
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x45, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x4c, 0x0a,
0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78,
0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c,
0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74,
0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x00, 0x12, 0x59, 0x0a, 0x0d, 0x43,
0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x20, 0x2e, 0x73,
0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x45, 0x6e,
0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x1a, 0x20,
0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e,
0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x08, 0x5a, 0x06, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var ( var (
file_signalexchange_proto_rawDescOnce sync.Once file_signalexchange_proto_rawDescOnce sync.Once
file_signalexchange_proto_rawDescData = file_signalexchange_proto_rawDesc file_signalexchange_proto_rawDescData []byte
) )
func file_signalexchange_proto_rawDescGZIP() []byte { func file_signalexchange_proto_rawDescGZIP() []byte {
file_signalexchange_proto_rawDescOnce.Do(func() { file_signalexchange_proto_rawDescOnce.Do(func() {
file_signalexchange_proto_rawDescData = protoimpl.X.CompressGZIP(file_signalexchange_proto_rawDescData) file_signalexchange_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_signalexchange_proto_rawDesc), len(file_signalexchange_proto_rawDesc)))
}) })
return file_signalexchange_proto_rawDescData return file_signalexchange_proto_rawDescData
} }
var file_signalexchange_proto_enumTypes = make([]protoimpl.EnumInfo, 1) var file_signalexchange_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_signalexchange_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_signalexchange_proto_goTypes = []interface{}{ var file_signalexchange_proto_goTypes = []any{
(Body_Type)(0), // 0: signalexchange.Body.Type (Body_Type)(0), // 0: signalexchange.Body.Type
(*EncryptedMessage)(nil), // 1: signalexchange.EncryptedMessage (*EncryptedMessage)(nil), // 1: signalexchange.EncryptedMessage
(*Message)(nil), // 2: signalexchange.Message (*Message)(nil), // 2: signalexchange.Message
@@ -568,75 +520,13 @@ func file_signalexchange_proto_init() {
if File_signalexchange_proto != nil { if File_signalexchange_proto != nil {
return return
} }
if !protoimpl.UnsafeEnabled { file_signalexchange_proto_msgTypes[2].OneofWrappers = []any{}
file_signalexchange_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { file_signalexchange_proto_msgTypes[3].OneofWrappers = []any{}
switch v := v.(*EncryptedMessage); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signalexchange_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Message); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signalexchange_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Body); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signalexchange_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Mode); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_signalexchange_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*RosenpassConfig); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_signalexchange_proto_msgTypes[2].OneofWrappers = []interface{}{}
file_signalexchange_proto_msgTypes[3].OneofWrappers = []interface{}{}
type x struct{} type x struct{}
out := protoimpl.TypeBuilder{ out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{ File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(), GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_signalexchange_proto_rawDesc, RawDescriptor: unsafe.Slice(unsafe.StringData(file_signalexchange_proto_rawDesc), len(file_signalexchange_proto_rawDesc)),
NumEnums: 1, NumEnums: 1,
NumMessages: 5, NumMessages: 5,
NumExtensions: 0, NumExtensions: 0,
@@ -648,7 +538,6 @@ func file_signalexchange_proto_init() {
MessageInfos: file_signalexchange_proto_msgTypes, MessageInfos: file_signalexchange_proto_msgTypes,
}.Build() }.Build()
File_signalexchange_proto = out.File File_signalexchange_proto = out.File
file_signalexchange_proto_rawDesc = nil
file_signalexchange_proto_goTypes = nil file_signalexchange_proto_goTypes = nil
file_signalexchange_proto_depIdxs = nil file_signalexchange_proto_depIdxs = nil
} }

View File

@@ -1,4 +1,8 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.1
// - protoc v6.33.1
// source: signalexchange.proto
package proto package proto
@@ -11,8 +15,13 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. // Requires gRPC-Go v1.64.0 or later.
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion9
const (
SignalExchange_Send_FullMethodName = "/signalexchange.SignalExchange/Send"
SignalExchange_ConnectStream_FullMethodName = "/signalexchange.SignalExchange/ConnectStream"
)
// SignalExchangeClient is the client API for SignalExchange service. // SignalExchangeClient is the client API for SignalExchange service.
// //
@@ -21,7 +30,7 @@ type SignalExchangeClient interface {
// Synchronously connect to the Signal Exchange service offering connection candidates and waiting for connection candidates from the other party (remote peer) // Synchronously connect to the Signal Exchange service offering connection candidates and waiting for connection candidates from the other party (remote peer)
Send(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) Send(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error)
// Connect to the Signal Exchange service offering connection candidates and maintain a channel for receiving candidates from the other party (remote peer) // Connect to the Signal Exchange service offering connection candidates and maintain a channel for receiving candidates from the other party (remote peer)
ConnectStream(ctx context.Context, opts ...grpc.CallOption) (SignalExchange_ConnectStreamClient, error) ConnectStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage], error)
} }
type signalExchangeClient struct { type signalExchangeClient struct {
@@ -33,67 +42,54 @@ func NewSignalExchangeClient(cc grpc.ClientConnInterface) SignalExchangeClient {
} }
func (c *signalExchangeClient) Send(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) { func (c *signalExchangeClient) Send(ctx context.Context, in *EncryptedMessage, opts ...grpc.CallOption) (*EncryptedMessage, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(EncryptedMessage) out := new(EncryptedMessage)
err := c.cc.Invoke(ctx, "/signalexchange.SignalExchange/Send", in, out, opts...) err := c.cc.Invoke(ctx, SignalExchange_Send_FullMethodName, in, out, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return out, nil return out, nil
} }
func (c *signalExchangeClient) ConnectStream(ctx context.Context, opts ...grpc.CallOption) (SignalExchange_ConnectStreamClient, error) { func (c *signalExchangeClient) ConnectStream(ctx context.Context, opts ...grpc.CallOption) (grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage], error) {
stream, err := c.cc.NewStream(ctx, &SignalExchange_ServiceDesc.Streams[0], "/signalexchange.SignalExchange/ConnectStream", opts...) cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &SignalExchange_ServiceDesc.Streams[0], SignalExchange_ConnectStream_FullMethodName, cOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
x := &signalExchangeConnectStreamClient{stream} x := &grpc.GenericClientStream[EncryptedMessage, EncryptedMessage]{ClientStream: stream}
return x, nil return x, nil
} }
type SignalExchange_ConnectStreamClient interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*EncryptedMessage) error type SignalExchange_ConnectStreamClient = grpc.BidiStreamingClient[EncryptedMessage, EncryptedMessage]
Recv() (*EncryptedMessage, error)
grpc.ClientStream
}
type signalExchangeConnectStreamClient struct {
grpc.ClientStream
}
func (x *signalExchangeConnectStreamClient) Send(m *EncryptedMessage) error {
return x.ClientStream.SendMsg(m)
}
func (x *signalExchangeConnectStreamClient) Recv() (*EncryptedMessage, error) {
m := new(EncryptedMessage)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// SignalExchangeServer is the server API for SignalExchange service. // SignalExchangeServer is the server API for SignalExchange service.
// All implementations must embed UnimplementedSignalExchangeServer // All implementations must embed UnimplementedSignalExchangeServer
// for forward compatibility // for forward compatibility.
type SignalExchangeServer interface { type SignalExchangeServer interface {
// Synchronously connect to the Signal Exchange service offering connection candidates and waiting for connection candidates from the other party (remote peer) // Synchronously connect to the Signal Exchange service offering connection candidates and waiting for connection candidates from the other party (remote peer)
Send(context.Context, *EncryptedMessage) (*EncryptedMessage, error) Send(context.Context, *EncryptedMessage) (*EncryptedMessage, error)
// Connect to the Signal Exchange service offering connection candidates and maintain a channel for receiving candidates from the other party (remote peer) // Connect to the Signal Exchange service offering connection candidates and maintain a channel for receiving candidates from the other party (remote peer)
ConnectStream(SignalExchange_ConnectStreamServer) error ConnectStream(grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]) error
mustEmbedUnimplementedSignalExchangeServer() mustEmbedUnimplementedSignalExchangeServer()
} }
// UnimplementedSignalExchangeServer must be embedded to have forward compatible implementations. // UnimplementedSignalExchangeServer must be embedded to have
type UnimplementedSignalExchangeServer struct { // forward compatible implementations.
} //
// NOTE: this should be embedded by value instead of pointer to avoid a nil
// pointer dereference when methods are called.
type UnimplementedSignalExchangeServer struct{}
func (UnimplementedSignalExchangeServer) Send(context.Context, *EncryptedMessage) (*EncryptedMessage, error) { func (UnimplementedSignalExchangeServer) Send(context.Context, *EncryptedMessage) (*EncryptedMessage, error) {
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") return nil, status.Error(codes.Unimplemented, "method Send not implemented")
} }
func (UnimplementedSignalExchangeServer) ConnectStream(SignalExchange_ConnectStreamServer) error { func (UnimplementedSignalExchangeServer) ConnectStream(grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]) error {
return status.Errorf(codes.Unimplemented, "method ConnectStream not implemented") return status.Error(codes.Unimplemented, "method ConnectStream not implemented")
} }
func (UnimplementedSignalExchangeServer) mustEmbedUnimplementedSignalExchangeServer() {} func (UnimplementedSignalExchangeServer) mustEmbedUnimplementedSignalExchangeServer() {}
func (UnimplementedSignalExchangeServer) testEmbeddedByValue() {}
// UnsafeSignalExchangeServer may be embedded to opt out of forward compatibility for this service. // UnsafeSignalExchangeServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to SignalExchangeServer will // Use of this interface is not recommended, as added methods to SignalExchangeServer will
@@ -103,6 +99,13 @@ type UnsafeSignalExchangeServer interface {
} }
func RegisterSignalExchangeServer(s grpc.ServiceRegistrar, srv SignalExchangeServer) { func RegisterSignalExchangeServer(s grpc.ServiceRegistrar, srv SignalExchangeServer) {
// If the following call panics, it indicates UnimplementedSignalExchangeServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
t.testEmbeddedByValue()
}
s.RegisterService(&SignalExchange_ServiceDesc, srv) s.RegisterService(&SignalExchange_ServiceDesc, srv)
} }
@@ -116,7 +119,7 @@ func _SignalExchange_Send_Handler(srv interface{}, ctx context.Context, dec func
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: "/signalexchange.SignalExchange/Send", FullMethod: SignalExchange_Send_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(SignalExchangeServer).Send(ctx, req.(*EncryptedMessage)) return srv.(SignalExchangeServer).Send(ctx, req.(*EncryptedMessage))
@@ -125,30 +128,11 @@ func _SignalExchange_Send_Handler(srv interface{}, ctx context.Context, dec func
} }
func _SignalExchange_ConnectStream_Handler(srv interface{}, stream grpc.ServerStream) error { func _SignalExchange_ConnectStream_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(SignalExchangeServer).ConnectStream(&signalExchangeConnectStreamServer{stream}) return srv.(SignalExchangeServer).ConnectStream(&grpc.GenericServerStream[EncryptedMessage, EncryptedMessage]{ServerStream: stream})
} }
type SignalExchange_ConnectStreamServer interface { // This type alias is provided for backwards compatibility with existing code that references the prior non-generic stream type by name.
Send(*EncryptedMessage) error type SignalExchange_ConnectStreamServer = grpc.BidiStreamingServer[EncryptedMessage, EncryptedMessage]
Recv() (*EncryptedMessage, error)
grpc.ServerStream
}
type signalExchangeConnectStreamServer struct {
grpc.ServerStream
}
func (x *signalExchangeConnectStreamServer) Send(m *EncryptedMessage) error {
return x.ServerStream.SendMsg(m)
}
func (x *signalExchangeConnectStreamServer) Recv() (*EncryptedMessage, error) {
m := new(EncryptedMessage)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// SignalExchange_ServiceDesc is the grpc.ServiceDesc for SignalExchange service. // SignalExchange_ServiceDesc is the grpc.ServiceDesc for SignalExchange service.
// It's only intended for direct use with grpc.RegisterService, // It's only intended for direct use with grpc.RegisterService,