mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 13:09:07 +02:00
trying embedded caddy reverse proxy
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,159 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package proxy;
|
||||
|
||||
option go_package = "github.com/netbirdio/netbird/proxy/pkg/grpc/proto";
|
||||
|
||||
import "google/protobuf/timestamp.proto";
|
||||
|
||||
// ProxyService defines the bidirectional streaming service
|
||||
// The proxy runs this service, control service connects as client
|
||||
service ProxyService {
|
||||
// Stream establishes a bidirectional stream between proxy and control service
|
||||
// Control service (client) sends ControlMessage, Proxy (server) sends ProxyMessage
|
||||
rpc Stream(stream ControlMessage) returns (stream ProxyMessage);
|
||||
}
|
||||
|
||||
// ProxyMessage represents messages sent from proxy to control service
|
||||
message ProxyMessage {
|
||||
oneof message {
|
||||
ProxyStats stats = 1;
|
||||
ProxyEvent event = 2;
|
||||
ProxyLog log = 3;
|
||||
ProxyHeartbeat heartbeat = 4;
|
||||
ProxyRequestData request_data = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// ControlMessage represents messages sent from control service to proxy
|
||||
message ControlMessage {
|
||||
oneof message {
|
||||
ControlEvent event = 1;
|
||||
ControlCommand command = 2;
|
||||
ControlConfig config = 3;
|
||||
ExposedServiceEvent exposed_service = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyStats contains proxy statistics
|
||||
message ProxyStats {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
uint64 total_requests = 2;
|
||||
uint64 active_connections = 3;
|
||||
uint64 bytes_sent = 4;
|
||||
uint64 bytes_received = 5;
|
||||
double cpu_usage = 6;
|
||||
double memory_usage_mb = 7;
|
||||
map<string, uint64> status_code_counts = 8;
|
||||
}
|
||||
|
||||
// ProxyEvent represents events from the proxy
|
||||
message ProxyEvent {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
EventType type = 2;
|
||||
string message = 3;
|
||||
map<string, string> metadata = 4;
|
||||
|
||||
enum EventType {
|
||||
UNKNOWN = 0;
|
||||
STARTED = 1;
|
||||
STOPPED = 2;
|
||||
ERROR = 3;
|
||||
BACKEND_UNAVAILABLE = 4;
|
||||
BACKEND_RECOVERED = 5;
|
||||
CONFIG_UPDATED = 6;
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyLog represents log entries
|
||||
message ProxyLog {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
LogLevel level = 2;
|
||||
string message = 3;
|
||||
map<string, string> fields = 4;
|
||||
|
||||
enum LogLevel {
|
||||
DEBUG = 0;
|
||||
INFO = 1;
|
||||
WARN = 2;
|
||||
ERROR = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// ProxyHeartbeat is sent periodically to keep connection alive
|
||||
message ProxyHeartbeat {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
string proxy_id = 2;
|
||||
}
|
||||
|
||||
// ControlEvent represents events from control service
|
||||
message ControlEvent {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
string event_id = 2;
|
||||
string message = 3;
|
||||
}
|
||||
|
||||
// ControlCommand represents commands sent to proxy
|
||||
message ControlCommand {
|
||||
string command_id = 1;
|
||||
CommandType type = 2;
|
||||
map<string, string> parameters = 3;
|
||||
|
||||
enum CommandType {
|
||||
UNKNOWN = 0;
|
||||
RELOAD_CONFIG = 1;
|
||||
ENABLE_DEBUG = 2;
|
||||
DISABLE_DEBUG = 3;
|
||||
GET_STATS = 4;
|
||||
SHUTDOWN = 5;
|
||||
}
|
||||
}
|
||||
|
||||
// ControlConfig contains configuration updates from control service
|
||||
message ControlConfig {
|
||||
string config_version = 1;
|
||||
map<string, string> settings = 2;
|
||||
}
|
||||
|
||||
// ExposedServiceEvent represents exposed service lifecycle events
|
||||
message ExposedServiceEvent {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
EventType type = 2;
|
||||
string service_id = 3;
|
||||
PeerConfig peer_config = 4;
|
||||
UpstreamConfig upstream_config = 5;
|
||||
|
||||
enum EventType {
|
||||
UNKNOWN = 0;
|
||||
CREATED = 1;
|
||||
UPDATED = 2;
|
||||
REMOVED = 3;
|
||||
}
|
||||
}
|
||||
|
||||
// PeerConfig contains WireGuard peer configuration
|
||||
message PeerConfig {
|
||||
string peer_id = 1;
|
||||
string public_key = 2;
|
||||
repeated string allowed_ips = 3;
|
||||
string endpoint = 4;
|
||||
string tunnel_ip = 5;
|
||||
uint32 persistent_keepalive = 6;
|
||||
}
|
||||
|
||||
// UpstreamConfig contains reverse proxy upstream configuration
|
||||
message UpstreamConfig {
|
||||
string domain = 1;
|
||||
map<string, string> path_mappings = 2; // path -> port
|
||||
}
|
||||
|
||||
// ProxyRequestData contains metadata about requests routed through the reverse proxy
|
||||
message ProxyRequestData {
|
||||
google.protobuf.Timestamp timestamp = 1;
|
||||
string service_id = 2;
|
||||
string path = 3;
|
||||
int64 duration_ms = 4;
|
||||
string method = 5; // HTTP method (GET, POST, PUT, DELETE, etc.)
|
||||
int32 response_code = 6;
|
||||
string source_ip = 7;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
|
||||
package proto
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// 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.
|
||||
type ProxyServiceClient interface {
|
||||
// Stream establishes a bidirectional stream between proxy and control service
|
||||
// Control service (client) sends ControlMessage, Proxy (server) sends ProxyMessage
|
||||
Stream(ctx context.Context, opts ...grpc.CallOption) (ProxyService_StreamClient, error)
|
||||
}
|
||||
|
||||
type proxyServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewProxyServiceClient(cc grpc.ClientConnInterface) ProxyServiceClient {
|
||||
return &proxyServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *proxyServiceClient) Stream(ctx context.Context, opts ...grpc.CallOption) (ProxyService_StreamClient, error) {
|
||||
stream, err := c.cc.NewStream(ctx, &ProxyService_ServiceDesc.Streams[0], "/proxy.ProxyService/Stream", opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x := &proxyServiceStreamClient{stream}
|
||||
return x, nil
|
||||
}
|
||||
|
||||
type ProxyService_StreamClient interface {
|
||||
Send(*ControlMessage) error
|
||||
Recv() (*ProxyMessage, error)
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
type proxyServiceStreamClient struct {
|
||||
grpc.ClientStream
|
||||
}
|
||||
|
||||
func (x *proxyServiceStreamClient) Send(m *ControlMessage) error {
|
||||
return x.ClientStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *proxyServiceStreamClient) Recv() (*ProxyMessage, error) {
|
||||
m := new(ProxyMessage)
|
||||
if err := x.ClientStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ProxyServiceServer is the server API for ProxyService service.
|
||||
// All implementations must embed UnimplementedProxyServiceServer
|
||||
// for forward compatibility
|
||||
type ProxyServiceServer interface {
|
||||
// Stream establishes a bidirectional stream between proxy and control service
|
||||
// Control service (client) sends ControlMessage, Proxy (server) sends ProxyMessage
|
||||
Stream(ProxyService_StreamServer) error
|
||||
mustEmbedUnimplementedProxyServiceServer()
|
||||
}
|
||||
|
||||
// UnimplementedProxyServiceServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedProxyServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedProxyServiceServer) Stream(ProxyService_StreamServer) error {
|
||||
return status.Errorf(codes.Unimplemented, "method Stream not implemented")
|
||||
}
|
||||
func (UnimplementedProxyServiceServer) mustEmbedUnimplementedProxyServiceServer() {}
|
||||
|
||||
// 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
|
||||
// result in compilation errors.
|
||||
type UnsafeProxyServiceServer interface {
|
||||
mustEmbedUnimplementedProxyServiceServer()
|
||||
}
|
||||
|
||||
func RegisterProxyServiceServer(s grpc.ServiceRegistrar, srv ProxyServiceServer) {
|
||||
s.RegisterService(&ProxyService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _ProxyService_Stream_Handler(srv interface{}, stream grpc.ServerStream) error {
|
||||
return srv.(ProxyServiceServer).Stream(&proxyServiceStreamServer{stream})
|
||||
}
|
||||
|
||||
type ProxyService_StreamServer interface {
|
||||
Send(*ProxyMessage) error
|
||||
Recv() (*ControlMessage, error)
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
type proxyServiceStreamServer struct {
|
||||
grpc.ServerStream
|
||||
}
|
||||
|
||||
func (x *proxyServiceStreamServer) Send(m *ProxyMessage) error {
|
||||
return x.ServerStream.SendMsg(m)
|
||||
}
|
||||
|
||||
func (x *proxyServiceStreamServer) Recv() (*ControlMessage, error) {
|
||||
m := new(ControlMessage)
|
||||
if err := x.ServerStream.RecvMsg(m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ProxyService_ServiceDesc is the grpc.ServiceDesc for ProxyService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var ProxyService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "proxy.ProxyService",
|
||||
HandlerType: (*ProxyServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
StreamName: "Stream",
|
||||
Handler: _ProxyService_Stream_Handler,
|
||||
ServerStreams: true,
|
||||
ClientStreams: true,
|
||||
},
|
||||
},
|
||||
Metadata: "pkg/grpc/proto/proxy.proto",
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/keepalive"
|
||||
|
||||
pb "github.com/netbirdio/netbird/proxy/pkg/grpc/proto"
|
||||
)
|
||||
|
||||
// StreamHandler handles incoming messages from control service
|
||||
type StreamHandler interface {
|
||||
HandleControlEvent(ctx context.Context, event *pb.ControlEvent) error
|
||||
HandleControlCommand(ctx context.Context, command *pb.ControlCommand) error
|
||||
HandleControlConfig(ctx context.Context, config *pb.ControlConfig) error
|
||||
HandleExposedServiceEvent(ctx context.Context, event *pb.ExposedServiceEvent) error
|
||||
}
|
||||
|
||||
// Server represents the gRPC server running on the proxy
|
||||
type Server struct {
|
||||
pb.UnimplementedProxyServiceServer
|
||||
|
||||
listenAddr string
|
||||
grpcServer *grpc.Server
|
||||
handler StreamHandler
|
||||
|
||||
mu sync.RWMutex
|
||||
streams map[string]*StreamContext
|
||||
isRunning bool
|
||||
}
|
||||
|
||||
// StreamContext holds the context for each active stream
|
||||
type StreamContext struct {
|
||||
stream pb.ProxyService_StreamServer
|
||||
sendChan chan *pb.ProxyMessage
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
controlID string // ID of the connected control service
|
||||
}
|
||||
|
||||
// Config holds gRPC server configuration
|
||||
type Config struct {
|
||||
ListenAddr string
|
||||
Handler StreamHandler
|
||||
}
|
||||
|
||||
// NewServer creates a new gRPC server
|
||||
func NewServer(config Config) *Server {
|
||||
return &Server{
|
||||
listenAddr: config.ListenAddr,
|
||||
handler: config.Handler,
|
||||
streams: make(map[string]*StreamContext),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the gRPC server
|
||||
func (s *Server) Start() error {
|
||||
s.mu.Lock()
|
||||
if s.isRunning {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("gRPC server already running")
|
||||
}
|
||||
s.isRunning = true
|
||||
s.mu.Unlock()
|
||||
|
||||
lis, err := net.Listen("tcp", s.listenAddr)
|
||||
if err != nil {
|
||||
s.mu.Lock()
|
||||
s.isRunning = false
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("failed to listen: %w", err)
|
||||
}
|
||||
|
||||
// Configure gRPC server with keepalive
|
||||
s.grpcServer = grpc.NewServer(
|
||||
grpc.KeepaliveParams(keepalive.ServerParameters{
|
||||
Time: 30 * time.Second,
|
||||
Timeout: 10 * time.Second,
|
||||
}),
|
||||
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
||||
MinTime: 10 * time.Second,
|
||||
PermitWithoutStream: true,
|
||||
}),
|
||||
)
|
||||
|
||||
pb.RegisterProxyServiceServer(s.grpcServer, s)
|
||||
|
||||
log.Infof("gRPC server listening on %s", s.listenAddr)
|
||||
|
||||
if err := s.grpcServer.Serve(lis); err != nil {
|
||||
s.mu.Lock()
|
||||
s.isRunning = false
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("failed to serve: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop gracefully stops the gRPC server
|
||||
func (s *Server) Stop(ctx context.Context) error {
|
||||
s.mu.Lock()
|
||||
if !s.isRunning {
|
||||
s.mu.Unlock()
|
||||
return fmt.Errorf("gRPC server not running")
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
log.Info("Stopping gRPC server...")
|
||||
|
||||
// Cancel all active streams
|
||||
s.mu.Lock()
|
||||
for _, streamCtx := range s.streams {
|
||||
streamCtx.cancel()
|
||||
close(streamCtx.sendChan)
|
||||
}
|
||||
s.streams = make(map[string]*StreamContext)
|
||||
s.mu.Unlock()
|
||||
|
||||
// Graceful stop with timeout
|
||||
stopped := make(chan struct{})
|
||||
go func() {
|
||||
s.grpcServer.GracefulStop()
|
||||
close(stopped)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopped:
|
||||
log.Info("gRPC server stopped gracefully")
|
||||
case <-ctx.Done():
|
||||
log.Warn("gRPC server graceful stop timeout, forcing stop")
|
||||
s.grpcServer.Stop()
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
s.isRunning = false
|
||||
s.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stream implements the bidirectional streaming RPC
|
||||
// The control service connects as client, proxy is server
|
||||
// Control service sends ControlMessage, Proxy sends ProxyMessage
|
||||
func (s *Server) Stream(stream pb.ProxyService_StreamServer) error {
|
||||
ctx, cancel := context.WithCancel(stream.Context())
|
||||
defer cancel()
|
||||
|
||||
controlID := fmt.Sprintf("control-%d", time.Now().Unix())
|
||||
|
||||
// Create stream context
|
||||
streamCtx := &StreamContext{
|
||||
stream: stream,
|
||||
sendChan: make(chan *pb.ProxyMessage, 100),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
controlID: controlID,
|
||||
}
|
||||
|
||||
// Register stream
|
||||
s.mu.Lock()
|
||||
s.streams[controlID] = streamCtx
|
||||
s.mu.Unlock()
|
||||
|
||||
log.Infof("Control service connected: %s", controlID)
|
||||
|
||||
// Start goroutine to send ProxyMessages to control service
|
||||
sendDone := make(chan error, 1)
|
||||
go s.sendLoop(streamCtx, sendDone)
|
||||
|
||||
// Start goroutine to receive ControlMessages from control service
|
||||
recvDone := make(chan error, 1)
|
||||
go s.receiveLoop(streamCtx, recvDone)
|
||||
|
||||
// Wait for either send or receive to complete
|
||||
select {
|
||||
case err := <-sendDone:
|
||||
log.Infof("Control service %s send loop ended: %v", controlID, err)
|
||||
return err
|
||||
case err := <-recvDone:
|
||||
log.Infof("Control service %s receive loop ended: %v", controlID, err)
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
log.Infof("Control service %s context done: %v", controlID, ctx.Err())
|
||||
return ctx.Err()
|
||||
}
|
||||
}
|
||||
|
||||
// sendLoop handles sending ProxyMessages to the control service
|
||||
func (s *Server) sendLoop(streamCtx *StreamContext, done chan<- error) {
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-streamCtx.sendChan:
|
||||
if !ok {
|
||||
done <- nil
|
||||
return
|
||||
}
|
||||
|
||||
// Send ProxyMessage to control service
|
||||
if err := streamCtx.stream.Send(msg); err != nil {
|
||||
log.Errorf("Failed to send message to control service: %v", err)
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
case <-streamCtx.ctx.Done():
|
||||
done <- streamCtx.ctx.Err()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// receiveLoop handles receiving ControlMessages from the control service
|
||||
func (s *Server) receiveLoop(streamCtx *StreamContext, done chan<- error) {
|
||||
for {
|
||||
// Receive ControlMessage from control service (client)
|
||||
controlMsg, err := streamCtx.stream.Recv()
|
||||
if err != nil {
|
||||
log.Debugf("Stream receive error: %v", err)
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Handle different ControlMessage types
|
||||
switch m := controlMsg.Message.(type) {
|
||||
case *pb.ControlMessage_Event:
|
||||
if s.handler != nil {
|
||||
if err := s.handler.HandleControlEvent(streamCtx.ctx, m.Event); err != nil {
|
||||
log.Errorf("Failed to handle control event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
case *pb.ControlMessage_Command:
|
||||
if s.handler != nil {
|
||||
if err := s.handler.HandleControlCommand(streamCtx.ctx, m.Command); err != nil {
|
||||
log.Errorf("Failed to handle control command: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
case *pb.ControlMessage_Config:
|
||||
if s.handler != nil {
|
||||
if err := s.handler.HandleControlConfig(streamCtx.ctx, m.Config); err != nil {
|
||||
log.Errorf("Failed to handle control config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
case *pb.ControlMessage_ExposedService:
|
||||
if s.handler != nil {
|
||||
if err := s.handler.HandleExposedServiceEvent(streamCtx.ctx, m.ExposedService); err != nil {
|
||||
log.Errorf("Failed to handle exposed service event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
log.Warnf("Received unknown control message type: %T", m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendProxyMessage sends a ProxyMessage to all connected control services
|
||||
func (s *Server) SendProxyMessage(msg *pb.ProxyMessage) {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
for _, streamCtx := range s.streams {
|
||||
select {
|
||||
case streamCtx.sendChan <- msg:
|
||||
// Message queued successfully
|
||||
default:
|
||||
log.Warn("Send channel full, dropping message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveStreams returns the number of active streams
|
||||
func (s *Server) GetActiveStreams() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return len(s.streams)
|
||||
}
|
||||
Reference in New Issue
Block a user