diff --git a/cmd/server/main.go b/cmd/server/main.go
index d5f03d0f323808b96a9acaeeec96846f48aaae2b..b435a7ec5b57fb7f53855661cee54ebfa5daf886 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -10,12 +10,19 @@ import (
 	"fmt"
 	"io"
 	"log"
+	"log/slog"
+	"net"
+	"net/url"
 	"os"
 	"os/signal"
 	"path/filepath"
 	"strconv"
 	"strings"
+	"sync"
+	"time"
 
+	agent "0xacab.org/leap/menshen-agent"
+	"0xacab.org/leap/menshen/models"
 	"0xacab.org/leap/obfsvpn/obfsvpn"
 	"0xacab.org/leap/obfsvpn/server"
 	"github.com/spf13/pflag"
@@ -37,21 +44,27 @@ var (
 	kcpReceiveWindowSize = "kcp-receive-window-size"
 	kcpReadBuffer        = "kcp-read-buffer"
 	//nolint:gosec // gosec somehow thinks this next line is Potential hardcoded credentials 🤷
-	kcpWriteBuffer        = "kcp-write-buffer"
-	kcpNoDelay            = "kcp-no-delay"
-	kcpInterval           = "kcp-interval"
-	kcpResend             = "kcp-resend"
-	kcpDisableFlowControl = "kcp-disable-flow-control"
-	kcpMTU                = "kcp-mtu"
-	persist               = "persist"
-	port                  = "port"
-	portSeed              = "port-seed"
-	portCount             = "port-count"
-	minHopPort            = "min-hop-port"
-	maxHopPort            = "max-hop-port"
-	stateDir              = "state"
-	remote                = "remote"
-	verbose               = "v"
+	kcpWriteBuffer              = "kcp-write-buffer"
+	kcpNoDelay                  = "kcp-no-delay"
+	kcpInterval                 = "kcp-interval"
+	kcpResend                   = "kcp-resend"
+	kcpDisableFlowControl       = "kcp-disable-flow-control"
+	kcpMTU                      = "kcp-mtu"
+	persist                     = "persist"
+	port                        = "port"
+	portSeed                    = "port-seed"
+	portCount                   = "port-count"
+	minHopPort                  = "min-hop-port"
+	maxHopPort                  = "max-hop-port"
+	stateDir                    = "state"
+	remote                      = "remote"
+	verbose                     = "v"
+	menshenAgentEnabled         = "menshen-agent-enabled"
+	menshenURL                  = "menshen-url"
+	menshenAgentHeartbeatMillis = "menshen-agent-heartbeat-millis"
+	menshenAgentKey             = "menshen-agent-key"
+	location                    = "location"
+	hostname                    = "hostname"
 )
 
 var (
@@ -63,31 +76,37 @@ var (
 var ErrObfs4DataAlreadyExists = errors.New("obfs4 data already exists")
 
 type config struct {
-	addr                  string
-	cfgFile               string
-	port                  int
-	hop                   bool
-	udp                   bool
-	quic                  bool
-	quicTLSCertFile       string
-	quicTLSKeyFile        string
-	kcp                   bool
-	kcpSendWindowSize     int
-	kcpReceiveWindowSize  int
-	kcpReadBuffer         int
-	kcpWriteBuffer        int
-	kcpNoDelay            bool
-	kcpInterval           int
-	kcpResend             int
-	kcpDisableFlowControl bool
-	kcpMTU                int
-	portSeed              int64
-	portCount             uint
-	minHopPort            uint
-	maxHopPort            uint
-	stateDir              string
-	remote                string
-	verbose               bool
+	addr                        string
+	cfgFile                     string
+	port                        int
+	hop                         bool
+	udp                         bool
+	quic                        bool
+	quicTLSCertFile             string
+	quicTLSKeyFile              string
+	kcp                         bool
+	kcpSendWindowSize           int
+	kcpReceiveWindowSize        int
+	kcpReadBuffer               int
+	kcpWriteBuffer              int
+	kcpNoDelay                  bool
+	kcpInterval                 int
+	kcpResend                   int
+	kcpDisableFlowControl       bool
+	kcpMTU                      int
+	portSeed                    int64
+	portCount                   uint
+	minHopPort                  uint
+	maxHopPort                  uint
+	stateDir                    string
+	remote                      string
+	verbose                     bool
+	menshenAgentEnabled         bool
+	menshenURL                  string
+	menshenAgentHeartbeatMillis uint
+	menshenAgentKey             string
+	location                    string
+	hostname                    string
 }
 
 func newConfigFromViper(logger *log.Logger) *config {
@@ -131,13 +150,19 @@ func newConfigFromViper(logger *log.Logger) *config {
 		kcpDisableFlowControl: viper.GetBool(kcpDisableFlowControl),
 		kcpMTU:                viper.GetInt(kcpMTU),
 
-		portSeed:   viper.GetInt64(portSeed),
-		portCount:  viper.GetUint(portCount),
-		minHopPort: viper.GetUint(minHopPort),
-		maxHopPort: viper.GetUint(maxHopPort),
-		stateDir:   getStateDir(),
-		remote:     viper.GetString(remote),
-		verbose:    viper.GetBool(verbose),
+		portSeed:                    viper.GetInt64(portSeed),
+		portCount:                   viper.GetUint(portCount),
+		minHopPort:                  viper.GetUint(minHopPort),
+		maxHopPort:                  viper.GetUint(maxHopPort),
+		stateDir:                    getStateDir(),
+		remote:                      viper.GetString(remote),
+		verbose:                     viper.GetBool(verbose),
+		menshenAgentEnabled:         viper.GetBool(menshenAgentEnabled),
+		menshenURL:                  viper.GetString(menshenURL),
+		menshenAgentHeartbeatMillis: viper.GetUint(menshenAgentHeartbeatMillis),
+		menshenAgentKey:             viper.GetString(menshenAgentKey),
+		location:                    viper.GetString(location),
+		hostname:                    viper.GetString(hostname),
 	}
 
 	// Sanity check on the configuration
@@ -249,6 +274,13 @@ func main() {
 	flag.Bool(verbose, false, "Enable verbose logging")
 	flag.String(remote, "", "The remote address to connect to, usually an OpenVPN endpoint")
 
+	flag.Bool(menshenAgentEnabled, false, "Whether to enable menshen-agent which will send heartbeat updates to a menshen server")
+	flag.String(menshenURL, "", "The menshen URL to send heartbeat updates to. Do *not* include a path")
+	flag.Uint(menshenAgentHeartbeatMillis, 5000, "The millisecond interval for the menshen-agent to send heartbeat updates")
+	flag.String(menshenAgentKey, "", "The private preshared key to sign menshen heartbeat updates with so that they can be authenticated by the menshen server")
+	flag.String(location, "", "The bridge's location. This is for the menshen-agent to be able to tell menshen where it is")
+	flag.String(hostname, "", "A unique identifier for the bridge. This is only really necessary for menshen-agent to dynamically update menshen inventory")
+
 	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
 	pflag.Parse()
 	err := viper.BindPFlags(pflag.CommandLine)
@@ -279,6 +311,7 @@ func main() {
 	}
 
 	viper.AutomaticEnv()
+
 	justGenKey := viper.GetBool(genkey)
 	persist := viper.GetBool(persist)
 	if justGenKey || !persist {
@@ -301,6 +334,16 @@ func main() {
 		debug.SetOutput(os.Stderr)
 	}
 
+	slevel := slog.LevelInfo
+	if viper.GetBool(verbose) {
+		slevel = slog.LevelDebug
+	}
+
+	// Configure slog to log to stderr just to match normal logger behaviour from above
+	slogger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
+		Level: slevel,
+	}))
+
 	if (cfg.maxHopPort - cfg.minHopPort) < cfg.portCount {
 		logger.Fatalf("configured port hopping boundaries (range of %d ports) cannot be smaller than port count %d.", (cfg.maxHopPort - cfg.minHopPort), cfg.portCount)
 	}
@@ -345,16 +388,79 @@ func main() {
 		quicConfig.TLSCert = cert
 	}
 
+	var menshenAgentConfig server.MenshenAgentConfig
+	if !cfg.menshenAgentEnabled {
+		menshenAgentConfig = server.MenshenAgentConfig{Enabled: false}
+	} else {
+		if cfg.menshenURL == "" || cfg.menshenAgentKey == "" || cfg.location == "" || cfg.hostname == "" {
+			log.Fatalf("menshen-agent enabled, but invalid configuration: %+v", cfg)
+		}
+
+		menshenURL, err := url.Parse(cfg.menshenURL)
+		if err != nil {
+			log.Fatalf("menshenURL failed parsing: %v", err)
+		}
+
+		var wg sync.WaitGroup
+		errChan := make(chan error)
+
+		go func() {
+			for err := range errChan {
+				slogger.Error("received error from menshen-agent", "err", err)
+			}
+		}()
+
+		transport := "tcp"
+		if cfg.udp {
+			transport = "udp"
+		} else if cfg.kcp {
+			transport = "kcp"
+		}
+
+		menshenAgentConfig = server.MenshenAgentConfig{
+			Enabled: true,
+			Config: agent.Config[models.ModelsBridge]{
+				Logger:       slogger,
+				MenshenURL:   menshenURL,
+				Heartbeat:    time.Millisecond * time.Duration(cfg.menshenAgentHeartbeatMillis),
+				PresharedKey: cfg.menshenAgentKey,
+				ErrorChan:    errChan,
+				WG:           &wg,
+
+				Host: &models.ModelsBridge{
+					Healthy:   true,
+					Location:  cfg.location,
+					Host:      cfg.hostname,
+					Transport: transport,
+					Port:      int64(cfg.port),
+				},
+			},
+		}
+
+		ipAddr := net.ParseIP(cfg.addr)
+		if ipAddr == nil {
+			log.Fatalf("Cannot parse addr: %v", cfg.addr)
+		}
+		if ipAddr.To4() != nil {
+			menshenAgentConfig.Host.IPAddr = cfg.addr
+		} else if ipAddr.To16() != nil {
+			menshenAgentConfig.Host.Ip6Addr = cfg.addr
+		} else {
+			log.Fatalf("Addr not ipv4 OR ipv6(??): %v", cfg.addr)
+		}
+	}
+
 	serverCfg := server.Config{
-		Obfs4Config: *obfs4Config,
-		StateDir:    cfg.stateDir,
-		PortSeed:    cfg.portSeed,
-		PortCount:   cfg.portCount,
-		MinHopPort:  cfg.minHopPort,
-		MaxHopPort:  cfg.maxHopPort,
-		OpenvpnAddr: cfg.remote,
-		KCPConfig:   kcpConfig,
-		QUICConfig:  quicConfig,
+		Obfs4Config:        *obfs4Config,
+		StateDir:           cfg.stateDir,
+		PortSeed:           cfg.portSeed,
+		PortCount:          cfg.portCount,
+		MinHopPort:         cfg.minHopPort,
+		MaxHopPort:         cfg.maxHopPort,
+		OpenvpnAddr:        cfg.remote,
+		KCPConfig:          kcpConfig,
+		QUICConfig:         quicConfig,
+		MenshenAgentConfig: menshenAgentConfig,
 	}
 	serverFactory := server.NewTCPServer
 	serverCfg.Obfs4ListenIP = cfg.addr
diff --git a/go.mod b/go.mod
index 77009ac9cab1861ba9f865bdd1f803b47ba224ff..d3d2416875691486077611e46573dd34a8009e3a 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,8 @@ go 1.22
 toolchain go1.22.2
 
 require (
+	0xacab.org/leap/menshen v0.0.0-20250313153840-1ef381b04b64
+	0xacab.org/leap/menshen-agent v0.0.0-20250313140933-8a982548c666
 	github.com/quic-go/quic-go v0.47.0
 	github.com/spf13/pflag v1.0.5
 	github.com/spf13/viper v1.16.0
@@ -15,16 +17,34 @@ require (
 
 require (
 	filippo.io/edwards25519 v1.0.0 // indirect
+	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 	github.com/dchest/siphash v1.2.3 // indirect
 	github.com/fsnotify/fsnotify v1.6.0 // indirect
+	github.com/go-logr/logr v1.4.1 // indirect
+	github.com/go-logr/stdr v1.2.2 // indirect
+	github.com/go-openapi/analysis v0.23.0 // indirect
+	github.com/go-openapi/errors v0.22.0 // indirect
+	github.com/go-openapi/jsonpointer v0.21.0 // indirect
+	github.com/go-openapi/jsonreference v0.21.0 // indirect
+	github.com/go-openapi/loads v0.22.0 // indirect
+	github.com/go-openapi/runtime v0.28.0 // indirect
+	github.com/go-openapi/spec v0.21.0 // indirect
+	github.com/go-openapi/strfmt v0.23.0 // indirect
+	github.com/go-openapi/swag v0.23.0 // indirect
+	github.com/go-openapi/validate v0.24.0 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
+	github.com/google/uuid v1.6.0 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
 	github.com/klauspost/reedsolomon v1.12.0 // indirect
 	github.com/magiconair/properties v1.8.7 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/oklog/ulid v1.3.1 // indirect
 	github.com/onsi/ginkgo/v2 v2.9.5 // indirect
+	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.8 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/spf13/afero v1.9.5 // indirect
@@ -36,13 +56,18 @@ require (
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect
 	gitlab.com/yawning/edwards25519-extra.git v0.0.0-20220726154925-def713fd18e4 // indirect
+	go.mongodb.org/mongo-driver v1.14.0 // indirect
+	go.opentelemetry.io/otel v1.24.0 // indirect
+	go.opentelemetry.io/otel/metric v1.24.0 // indirect
+	go.opentelemetry.io/otel/trace v1.24.0 // indirect
 	go.uber.org/mock v0.4.0 // indirect
-	golang.org/x/crypto v0.26.0 // indirect
+	golang.org/x/crypto v0.31.0 // indirect
 	golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
 	golang.org/x/mod v0.17.0 // indirect
 	golang.org/x/net v0.28.0 // indirect
-	golang.org/x/sys v0.23.0 // indirect
-	golang.org/x/text v0.17.0 // indirect
+	golang.org/x/sync v0.10.0 // indirect
+	golang.org/x/sys v0.28.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
diff --git a/go.sum b/go.sum
index 93be55fb34aa8fef4f0999e27ffe0cea70e0e78d..41b702e178071d892130ea39b2737c81de56a4ca 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,7 @@
+0xacab.org/leap/menshen v0.0.0-20250313153840-1ef381b04b64 h1:6acy4fxGhwUxsa9X17GIAfUsFy+leMW4vNKCBH3/Kdg=
+0xacab.org/leap/menshen v0.0.0-20250313153840-1ef381b04b64/go.mod h1:CxwRrHLOU7QSd7fgY9pZXs5nStnycOgkJ4d7Z89KJdY=
+0xacab.org/leap/menshen-agent v0.0.0-20250313140933-8a982548c666 h1:tXQ2kccL+JceoppGF2Giw+8ZwtfeUnuYwURjhrrjc5k=
+0xacab.org/leap/menshen-agent v0.0.0-20250313140933-8a982548c666/go.mod h1:9kPqSzsZATHh+JslJ33gUNxHYrADKA7b66q2iM5F13Y=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -41,6 +45,8 @@ filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
 filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
+github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
@@ -50,8 +56,9 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
 github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -67,8 +74,31 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
 github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
-github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
+github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
+github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU=
+github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
+github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
+github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
+github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
+github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
+github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
+github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
+github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
+github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc=
+github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
+github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
+github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
+github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
+github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
+github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
 github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -96,8 +126,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
 github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
-github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
-github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -128,6 +158,8 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE
 github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
@@ -137,6 +169,8 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
 github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
 github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -154,25 +188,32 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
 github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q=
 github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
 github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
 github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
+github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
+github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
 github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
 github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
-github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
+github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y=
 github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
-github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
-github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
+github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
 github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
 github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
 github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
@@ -187,14 +228,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
 github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
 github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
 github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40=
@@ -217,12 +260,22 @@ gitlab.com/yawning/obfs4.git v0.0.0-20230723031256-efdc692691f7 h1:2vJqMxVZ1VXjI
 gitlab.com/yawning/obfs4.git v0.0.0-20230723031256-efdc692691f7/go.mod h1:48ziD3G+SuffZkrm0tNWC8RdJ/TW5avehZdorFNGgGc=
 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.4.0 h1:Y7fHDMy11yyjM+YlHfcM3svaujdL+m5DqS444wbj8o4=
 gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib v1.4.0/go.mod h1:70bhd4JKW/+1HLfm+TMrgHJsUHG4coelMWwiVEJ2gAg=
+go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
+go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
+go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
+go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
+go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
+go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
+go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
+go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
+go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
 go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
 go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -234,8 +287,8 @@ golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
-golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
+golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
+golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -327,8 +380,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ
 golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
-golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
+golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -366,8 +419,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
-golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -377,8 +430,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -528,8 +581,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
 google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
 google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
diff --git a/server/server.go b/server/server.go
index 314d8c9ebf30f3d87501960d202965c516bfed21..07bee338886615e2918f5bd5cee42ed90759e50e 100644
--- a/server/server.go
+++ b/server/server.go
@@ -1,6 +1,10 @@
 package server
 
-import "0xacab.org/leap/obfsvpn/obfsvpn"
+import (
+	agent "0xacab.org/leap/menshen-agent"
+	"0xacab.org/leap/menshen/models"
+	"0xacab.org/leap/obfsvpn/obfsvpn"
+)
 
 const transportName = "obfs4"
 
@@ -13,18 +17,26 @@ type Obfs4Config struct {
 	IatMode    int    `json:"iat-mode"`
 }
 
+// MenshenAgentConfig is for configuring menshen-agent behaviour
+type MenshenAgentConfig struct {
+	Enabled bool
+
+	agent.Config[models.ModelsBridge]
+}
+
 // Config is the configuration for the obfsvpn server
 type Config struct {
-	OpenvpnAddr     string
-	Obfs4Config     Obfs4Config
-	StateDir        string
-	Obfs4ListenIP   string
-	Obfs4ListenPort int
-	HoppingEnabled  bool
-	PortSeed        int64
-	PortCount       uint
-	MinHopPort      uint
-	MaxHopPort      uint
-	KCPConfig       obfsvpn.KCPConfig
-	QUICConfig      obfsvpn.QUICConfig
+	OpenvpnAddr        string
+	Obfs4Config        Obfs4Config
+	StateDir           string
+	Obfs4ListenIP      string
+	Obfs4ListenPort    int
+	HoppingEnabled     bool
+	PortSeed           int64
+	PortCount          uint
+	MinHopPort         uint
+	MaxHopPort         uint
+	KCPConfig          obfsvpn.KCPConfig
+	QUICConfig         obfsvpn.QUICConfig
+	MenshenAgentConfig MenshenAgentConfig
 }
diff --git a/server/tcpserver.go b/server/tcpserver.go
index e9c17c277e38f91f8e92be6cc18db16b90832798..0c87bbc7994aa1cec16ea285198183af4ea91c2d 100644
--- a/server/tcpserver.go
+++ b/server/tcpserver.go
@@ -10,33 +10,52 @@ import (
 	"os"
 	"strconv"
 
+	agent "0xacab.org/leap/menshen-agent"
+	"0xacab.org/leap/menshen/models"
 	"0xacab.org/leap/obfsvpn/obfsvpn"
+
 	//pt "git.torproject.org/pluggable-transports/goptlib.git"
 	pt "gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/goptlib"
 )
 
 // TCPServer is a obfsvpn server
 type TCPServer struct {
-	ctx    context.Context
-	cfg    Config
-	debug  *log.Logger
-	logger *log.Logger
-	stop   context.CancelFunc
+	ctx          context.Context
+	cfg          Config
+	debug        *log.Logger
+	logger       *log.Logger
+	stop         context.CancelFunc
+	menshenAgent *agent.Agent[models.ModelsBridge]
 }
 
 // NewTCPServer returns a new TCPServer
 func NewTCPServer(ctx context.Context, stop context.CancelFunc, cfg Config, logger, debug *log.Logger) BridgeServer {
-	return &TCPServer{
+	tcpServer := &TCPServer{
 		ctx:    ctx,
 		stop:   stop,
 		cfg:    cfg,
 		debug:  debug,
 		logger: logger,
 	}
+
+	if cfg.MenshenAgentConfig.Enabled {
+		menshenAgent := agent.NewAgent(ctx, cfg.MenshenAgentConfig.Config)
+		tcpServer.menshenAgent = menshenAgent
+	}
+	return tcpServer
 }
 
 // Start starts the obfsvpn server
 func (s *TCPServer) Start() error {
+	if s.menshenAgent != nil {
+		s.logger.Printf("Starting menshen agent")
+		// We don't need to do special stop handling as we've already passed it a cancellable context
+		err := s.menshenAgent.Start()
+		if err != nil {
+			return fmt.Errorf("failed to start menshen agent: %w", err)
+		}
+	}
+
 	listenConfig, err := obfsvpn.NewListenConfig(
 		s.cfg.Obfs4Config.NodeID, s.cfg.Obfs4Config.PrivateKey, s.cfg.Obfs4Config.PublicKey,
 		s.cfg.Obfs4Config.DRBGSeed,
diff --git a/server/udpserver.go b/server/udpserver.go
index fee88f082c2d3b30ea5b059888dea736b92870ea..edb9da58dea13b2d8c8cfc03cd40a990dd5b113b 100644
--- a/server/udpserver.go
+++ b/server/udpserver.go
@@ -8,31 +8,48 @@ import (
 	"net"
 	"strconv"
 
+	agent "0xacab.org/leap/menshen-agent"
+	"0xacab.org/leap/menshen/models"
 	"0xacab.org/leap/obfsvpn/obfsvpn"
 )
 
 // UDPServer is a obfsvpn server
 type UDPServer struct {
-	cfg    Config
-	logger *log.Logger
-	debug  *log.Logger
-	ctx    context.Context
-	stop   context.CancelFunc
+	cfg          Config
+	logger       *log.Logger
+	debug        *log.Logger
+	ctx          context.Context
+	stop         context.CancelFunc
+	menshenAgent *agent.Agent[models.ModelsBridge]
 }
 
 // NewUDPServer returns a new UDPServer
 func NewUDPServer(ctx context.Context, stop context.CancelFunc, cfg Config, logger, debug *log.Logger) BridgeServer {
-	return &UDPServer{
+	udpServer := &UDPServer{
 		ctx:    ctx,
 		stop:   stop,
 		cfg:    cfg,
 		debug:  debug,
 		logger: logger,
 	}
+	if cfg.MenshenAgentConfig.Enabled {
+		menshenAgent := agent.NewAgent(ctx, cfg.MenshenAgentConfig.Config)
+		udpServer.menshenAgent = menshenAgent
+	}
+	return udpServer
 }
 
 // Start starts the obfsvpn server
 func (s *UDPServer) Start() error {
+	if s.menshenAgent != nil {
+		s.logger.Printf("Starting menshen agent")
+		// We don't need to do special stop handling as we've already passed it a cancellable context
+		err := s.menshenAgent.Start()
+		if err != nil {
+			return fmt.Errorf("failed to start menshen agent: %w", err)
+		}
+	}
+
 	listenConfig, err := obfsvpn.NewListenConfig(
 		s.cfg.Obfs4Config.NodeID, s.cfg.Obfs4Config.PrivateKey, s.cfg.Obfs4Config.PublicKey,
 		s.cfg.Obfs4Config.DRBGSeed,