diff --git a/client.go b/client.go index 843ae1f..4b77116 100644 --- a/client.go +++ b/client.go @@ -426,6 +426,8 @@ type ClientStatus struct { Status statusc.Status `json:"status"` // Metadata is the metadata field send to the control server and other peers Metadata string `json:"metadata"` + // BuildVersion shows the build information of the client + BuildVersion string `json:"build-version"` // ServerAddr reports which server this client is connected ServerAddr string `json:"server-address"` // DirectAddr reports local direct connectsions server @@ -460,6 +462,7 @@ func (c *Client) Status(ctx context.Context) (ClientStatus, error) { return ClientStatus{ Status: stat, + BuildVersion: model.BuildVersion(), Metadata: c.metadata, ServerAddr: c.controlAddr.String(), DirectAddr: c.directAddr.String(), diff --git a/cmd/connet/relay.go b/cmd/connet/relay.go index bb6dffc..a9f5fdf 100644 --- a/cmd/connet/relay.go +++ b/cmd/connet/relay.go @@ -114,6 +114,7 @@ func relayRun(ctx context.Context, cfg RelayConfig, logger *slog.Logger) error { for ix, ingressCfg := range cfg.Ingresses { if ingressCfg.Addr == "" && !usedDefault { ingressCfg.Addr = ":19191" + ingressCfg.Hostports = []string{"localhost:19191"} usedDefault = true } if ingress, err := ingressCfg.parse(); err != nil { diff --git a/endpoint.go b/endpoint.go index 0f8bc10..a37358d 100644 --- a/endpoint.go +++ b/endpoint.go @@ -16,7 +16,6 @@ import ( "github.com/connet-dev/connet/pkg/statusc" "github.com/connet-dev/connet/proto" "github.com/connet-dev/connet/proto/pbclient" - "github.com/connet-dev/connet/proto/pbclientrelay" "github.com/quic-go/quic-go" ) @@ -55,7 +54,7 @@ type endpoint struct { // - an error happens in runPeer // - a terminal error happens in runAnnounce func newEndpoint(ctx context.Context, cl *Client, cfg endpointConfig, logger *slog.Logger) (*endpoint, error) { - p, err := newPeer(cl.directServer, cl.addrs, logger) + p, err := newPeer(cl.directServer, cl.addrs, cl.metadata, logger) if err != nil { return nil, err } @@ -284,27 +283,15 @@ func (ep *endpoint) runRelay(ctx context.Context, conn *quic.Conn) error { return fmt.Errorf("relay unexpected response") } - var relays []relayPeer - relays = append(relays, iterc.MapSlice(resp.Relay.Relays, func(relay *pbclient.Relay) relayPeer { - return relayPeer{proto: &pbclient.DirectRelay{ + var relays []*pbclient.DirectRelay + relays = append(relays, iterc.MapSlice(resp.Relay.Relays, func(relay *pbclient.Relay) *pbclient.DirectRelay { + return &pbclient.DirectRelay{ Id: relay.Id, Addresses: relay.Addresses, ServerCertificate: relay.ServerCertificate, - }} - })...) - relays = append(relays, iterc.MapSlice(resp.Relay.Directs, func(relay *pbclient.DirectRelay) relayPeer { - return relayPeer{ - proto: relay, - auth: &pbclientrelay.AuthenticateReq{ - Endpoint: ep.cfg.endpoint.PB(), - Role: ep.cfg.role.PB(), - AuthenticationSignature: relay.AuthenticationSignature, - Metadata: ep.client.metadata, - BuildVersion: model.BuildVersion(), - }, } - })...) + relays = append(relays, resp.Relay.Directs...) ep.peer.setRelays(relays) } diff --git a/nix/package.nix b/nix/package.nix index 759d25c..071ca3e 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -24,7 +24,7 @@ pkgs.buildGoModule fileset = sourceFiles; }; - vendorHash = "sha256-2OoJhngBZJ/u7GPDec4Bg0Dq/DMseFbENLGfJAYzj1o="; + vendorHash = "sha256-pNuIzSxAxv9KbpOFE0JYJ/ZO9rdVY+AT2erNBVMcyiI="; subPackages = [ "cmd/connet" ]; ldflags = [ "-X 'github.com/connet-dev/connet/model.Version=${lib.strings.fileContents ../VERSION}'" ]; diff --git a/peer.go b/peer.go index 7ffb488..ca62d58 100644 --- a/peer.go +++ b/peer.go @@ -21,7 +21,6 @@ import ( "github.com/connet-dev/connet/pkg/notify" "github.com/connet-dev/connet/pkg/reliable" "github.com/connet-dev/connet/proto/pbclient" - "github.com/connet-dev/connet/proto/pbclientrelay" "github.com/connet-dev/connet/proto/pbconnect" "github.com/connet-dev/connet/proto/pbmodel" "github.com/quic-go/quic-go" @@ -30,14 +29,15 @@ import ( type peer struct { self *notify.V[*pbclient.Peer] - relays *notify.V[[]relayPeer] + relays *notify.V[[]*pbclient.DirectRelay] relayConns *notify.V[map[relayID]*quic.Conn] peers *notify.V[[]*pbclient.RemotePeer] peerConns *notify.V[map[peerConnKey]*quic.Conn] - direct *directServer - addrs *notify.V[advertiseAddrs] + direct *directServer + addrs *notify.V[advertiseAddrs] + metadata string serverCert tls.Certificate clientCert tls.Certificate @@ -86,12 +86,7 @@ func (s peerStyle) isRelay() bool { return !s.isDirect() } -type relayPeer struct { - proto *pbclient.DirectRelay - auth *pbclientrelay.AuthenticateReq -} - -func newPeer(direct *directServer, addrs *notify.V[advertiseAddrs], logger *slog.Logger) (*peer, error) { +func newPeer(direct *directServer, addrs *notify.V[advertiseAddrs], metadata string, logger *slog.Logger) (*peer, error) { root, err := certc.NewRoot() if err != nil { return nil, err @@ -124,14 +119,15 @@ func newPeer(direct *directServer, addrs *notify.V[advertiseAddrs], logger *slog ClientCertificate: clientTLSCert.Leaf.Raw, }), - relays: notify.NewEmpty[[]relayPeer](), + relays: notify.NewEmpty[[]*pbclient.DirectRelay](), relayConns: notify.NewEmpty[map[relayID]*quic.Conn](), peers: notify.NewEmpty[[]*pbclient.RemotePeer](), peerConns: notify.NewEmpty[map[peerConnKey]*quic.Conn](), - direct: direct, - addrs: addrs, + direct: direct, + addrs: addrs, + metadata: metadata, serverCert: serverTLSCert, clientCert: clientTLSCert, @@ -139,7 +135,7 @@ func newPeer(direct *directServer, addrs *notify.V[advertiseAddrs], logger *slog }, nil } -func (p *peer) setRelays(relays []relayPeer) { +func (p *peer) setRelays(relays []*pbclient.DirectRelay) { p.relays.Set(relays) } @@ -176,22 +172,22 @@ func (p *peer) runDirectAddrs(ctx context.Context) error { func (p *peer) runRelays(ctx context.Context) error { runningRelays := map[relayID]*relay{} - return p.relays.Listen(ctx, func(relays []relayPeer) error { + return p.relays.Listen(ctx, func(relays []*pbclient.DirectRelay) error { p.logger.Debug("relays updated", "len", len(relays)) activeRelays := map[relayID]struct{}{} for _, relay := range relays { - id := relayID(relay.proto.Id) - hps := model.HostPortFromPBs(relay.proto.Addresses) + id := relayID(relay.Id) + hps := model.HostPortFromPBs(relay.Addresses) activeRelays[id] = struct{}{} - tlsCfg, err := newServerTLSConfig(relay.proto.ServerCertificate) + tlsCfg, err := newServerTLSConfig(relay.ServerCertificate) if err != nil { return err } - cfg := &relayConfig{tlsCfg, relay.auth} + cfg := &relayConfig{tlsCfg, relay.Authentication} if rlg := runningRelays[id]; rlg != nil { p.logger.Debug("updating relay", "id", id) rlg.serverConf.Store(cfg) @@ -389,14 +385,16 @@ func (p *peer) getECDHPublicKey(cfg *pbconnect.ECDHConfiguration) (*ecdh.PublicK type StatusPeer struct { // Relays show the status of each relay this peer is connected to - Relays map[string]StatusRelayConnection `json:"relays"` + Relays map[string]StatusRelay `json:"relays"` // Peers shows the status of each peer this peer is connected to Peers map[string]StatusRemotePeer `json:"peers"` } -type StatusRelayConnection struct { - ID string `json:"id"` - Address string `json:"address"` +type StatusRelay struct { + ID string `json:"id"` + Hostports []string `json:"hostports"` + Metadata string `json:"metadata"` + Connection string `json:"connection"` } type StatusRemotePeer struct { @@ -414,15 +412,25 @@ type StatusRemotePeerConnection struct { func (p *peer) status() (StatusPeer, error) { stat := StatusPeer{ - Relays: map[string]StatusRelayConnection{}, + Relays: map[string]StatusRelay{}, Peers: map[string]StatusRemotePeer{}, } - if relays, ok := p.relayConns.Peek(); ok { - for id, conn := range relays { - stat.Relays[string(id)] = StatusRelayConnection{ - ID: string(id), - Address: conn.RemoteAddr().String(), + if relays, ok := p.relays.Peek(); ok { + for _, relay := range relays { + stat.Relays[relay.Id] = StatusRelay{ + ID: relay.Id, + Metadata: relay.Metadata, + Hostports: iterc.MapSliceStrings(model.HostPortFromPBs(relay.Addresses)), + } + } + } + + if relayConns, ok := p.relayConns.Peek(); ok { + for id, conn := range relayConns { + if v, ok := stat.Relays[string(id)]; ok { + v.Connection = conn.RemoteAddr().String() + stat.Relays[string(id)] = v } } } diff --git a/proto/client.proto b/proto/client.proto index 1f5c2d6..600a870 100644 --- a/proto/client.proto +++ b/proto/client.proto @@ -76,5 +76,6 @@ message DirectRelay { string id = 1; // relay id as assigned by the control server repeated model.HostPort addresses = 2; bytes server_certificate = 3; // generic certificate used by this relay - bytes authentication_signature = 4; // endpoint/role specific signature to use at the relay + bytes authentication = 4; // endpoint/role specific auth to use at the relay + string metadata = 5; } diff --git a/proto/client_relay.proto b/proto/client_relay.proto index f201c1e..dce5b7d 100644 --- a/proto/client_relay.proto +++ b/proto/client_relay.proto @@ -2,16 +2,13 @@ syntax = "proto3"; package client_relay; import "error.proto"; -import "model.proto"; option go_package = "github.com/connet-dev/connet/proto/pbclientrelay"; message AuthenticateReq { - model.Endpoint endpoint = 1; - model.Role role = 2; - bytes authentication_signature = 3; - string metadata = 4; - string build_version = 5; + bytes authentication = 1; + string metadata = 2; + string build_version = 3; } message AuthenticateResp { diff --git a/proto/pbclient/client.pb.go b/proto/pbclient/client.pb.go index f5e6105..64f8dc0 100644 --- a/proto/pbclient/client.pb.go +++ b/proto/pbclient/client.pb.go @@ -454,13 +454,14 @@ func (x *Relay) GetServerCertificate() []byte { } type DirectRelay struct { - state protoimpl.MessageState `protogen:"open.v1"` - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // relay id as assigned by the control server - Addresses []*pbmodel.HostPort `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` - ServerCertificate []byte `protobuf:"bytes,3,opt,name=server_certificate,json=serverCertificate,proto3" json:"server_certificate,omitempty"` // generic certificate used by this relay - AuthenticationSignature []byte `protobuf:"bytes,4,opt,name=authentication_signature,json=authenticationSignature,proto3" json:"authentication_signature,omitempty"` // endpoint/role specific signature to use at the relay - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // relay id as assigned by the control server + Addresses []*pbmodel.HostPort `protobuf:"bytes,2,rep,name=addresses,proto3" json:"addresses,omitempty"` + ServerCertificate []byte `protobuf:"bytes,3,opt,name=server_certificate,json=serverCertificate,proto3" json:"server_certificate,omitempty"` // generic certificate used by this relay + Authentication []byte `protobuf:"bytes,4,opt,name=authentication,proto3" json:"authentication,omitempty"` // endpoint/role specific auth to use at the relay + Metadata string `protobuf:"bytes,5,opt,name=metadata,proto3" json:"metadata,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *DirectRelay) Reset() { @@ -514,13 +515,20 @@ func (x *DirectRelay) GetServerCertificate() []byte { return nil } -func (x *DirectRelay) GetAuthenticationSignature() []byte { +func (x *DirectRelay) GetAuthentication() []byte { if x != nil { - return x.AuthenticationSignature + return x.Authentication } return nil } +func (x *DirectRelay) GetMetadata() string { + if x != nil { + return x.Metadata + } + return "" +} + type Request_Announce struct { state protoimpl.MessageState `protogen:"open.v1"` Endpoint *pbmodel.Endpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` @@ -784,12 +792,13 @@ const file_client_proto_rawDesc = "" + "\x05Relay\x12\x0e\n" + "\x02id\x18\x03 \x01(\tR\x02id\x12-\n" + "\taddresses\x18\x04 \x03(\v2\x0f.model.HostPortR\taddresses\x12-\n" + - "\x12server_certificate\x18\x02 \x01(\fR\x11serverCertificate\"\xb6\x01\n" + + "\x12server_certificate\x18\x02 \x01(\fR\x11serverCertificate\"\xbf\x01\n" + "\vDirectRelay\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12-\n" + "\taddresses\x18\x02 \x03(\v2\x0f.model.HostPortR\taddresses\x12-\n" + - "\x12server_certificate\x18\x03 \x01(\fR\x11serverCertificate\x129\n" + - "\x18authentication_signature\x18\x04 \x01(\fR\x17authenticationSignatureB-Z+github.com/connet-dev/connet/proto/pbclientb\x06proto3" + "\x12server_certificate\x18\x03 \x01(\fR\x11serverCertificate\x12&\n" + + "\x0eauthentication\x18\x04 \x01(\fR\x0eauthentication\x12\x1a\n" + + "\bmetadata\x18\x05 \x01(\tR\bmetadataB-Z+github.com/connet-dev/connet/proto/pbclientb\x06proto3" var ( file_client_proto_rawDescOnce sync.Once diff --git a/proto/pbclientrelay/client_relay.pb.go b/proto/pbclientrelay/client_relay.pb.go index b35a99c..1ef4a41 100644 --- a/proto/pbclientrelay/client_relay.pb.go +++ b/proto/pbclientrelay/client_relay.pb.go @@ -8,7 +8,6 @@ package pbclientrelay import ( pberror "github.com/connet-dev/connet/proto/pberror" - pbmodel "github.com/connet-dev/connet/proto/pbmodel" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -24,14 +23,12 @@ const ( ) type AuthenticateReq struct { - state protoimpl.MessageState `protogen:"open.v1"` - Endpoint *pbmodel.Endpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - Role pbmodel.Role `protobuf:"varint,2,opt,name=role,proto3,enum=model.Role" json:"role,omitempty"` - AuthenticationSignature []byte `protobuf:"bytes,3,opt,name=authentication_signature,json=authenticationSignature,proto3" json:"authentication_signature,omitempty"` - Metadata string `protobuf:"bytes,4,opt,name=metadata,proto3" json:"metadata,omitempty"` - BuildVersion string `protobuf:"bytes,5,opt,name=build_version,json=buildVersion,proto3" json:"build_version,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Authentication []byte `protobuf:"bytes,1,opt,name=authentication,proto3" json:"authentication,omitempty"` + Metadata string `protobuf:"bytes,2,opt,name=metadata,proto3" json:"metadata,omitempty"` + BuildVersion string `protobuf:"bytes,3,opt,name=build_version,json=buildVersion,proto3" json:"build_version,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AuthenticateReq) Reset() { @@ -64,23 +61,9 @@ func (*AuthenticateReq) Descriptor() ([]byte, []int) { return file_client_relay_proto_rawDescGZIP(), []int{0} } -func (x *AuthenticateReq) GetEndpoint() *pbmodel.Endpoint { +func (x *AuthenticateReq) GetAuthentication() []byte { if x != nil { - return x.Endpoint - } - return nil -} - -func (x *AuthenticateReq) GetRole() pbmodel.Role { - if x != nil { - return x.Role - } - return pbmodel.Role(0) -} - -func (x *AuthenticateReq) GetAuthenticationSignature() []byte { - if x != nil { - return x.AuthenticationSignature + return x.Authentication } return nil } @@ -147,13 +130,11 @@ var File_client_relay_proto protoreflect.FileDescriptor const file_client_relay_proto_rawDesc = "" + "\n" + - "\x12client_relay.proto\x12\fclient_relay\x1a\verror.proto\x1a\vmodel.proto\"\xdb\x01\n" + - "\x0fAuthenticateReq\x12+\n" + - "\bendpoint\x18\x01 \x01(\v2\x0f.model.EndpointR\bendpoint\x12\x1f\n" + - "\x04role\x18\x02 \x01(\x0e2\v.model.RoleR\x04role\x129\n" + - "\x18authentication_signature\x18\x03 \x01(\fR\x17authenticationSignature\x12\x1a\n" + - "\bmetadata\x18\x04 \x01(\tR\bmetadata\x12#\n" + - "\rbuild_version\x18\x05 \x01(\tR\fbuildVersion\"6\n" + + "\x12client_relay.proto\x12\fclient_relay\x1a\verror.proto\"z\n" + + "\x0fAuthenticateReq\x12&\n" + + "\x0eauthentication\x18\x01 \x01(\fR\x0eauthentication\x12\x1a\n" + + "\bmetadata\x18\x02 \x01(\tR\bmetadata\x12#\n" + + "\rbuild_version\x18\x03 \x01(\tR\fbuildVersion\"6\n" + "\x10AuthenticateResp\x12\"\n" + "\x05error\x18\x01 \x01(\v2\f.error.ErrorR\x05errorB2Z0github.com/connet-dev/connet/proto/pbclientrelayb\x06proto3" @@ -173,19 +154,15 @@ var file_client_relay_proto_msgTypes = make([]protoimpl.MessageInfo, 2) var file_client_relay_proto_goTypes = []any{ (*AuthenticateReq)(nil), // 0: client_relay.AuthenticateReq (*AuthenticateResp)(nil), // 1: client_relay.AuthenticateResp - (*pbmodel.Endpoint)(nil), // 2: model.Endpoint - (pbmodel.Role)(0), // 3: model.Role - (*pberror.Error)(nil), // 4: error.Error + (*pberror.Error)(nil), // 2: error.Error } var file_client_relay_proto_depIdxs = []int32{ - 2, // 0: client_relay.AuthenticateReq.endpoint:type_name -> model.Endpoint - 3, // 1: client_relay.AuthenticateReq.role:type_name -> model.Role - 4, // 2: client_relay.AuthenticateResp.error:type_name -> error.Error - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name + 2, // 0: client_relay.AuthenticateResp.error:type_name -> error.Error + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name } func init() { file_client_relay_proto_init() } diff --git a/proto/pbrelay/relay.pb.go b/proto/pbrelay/relay.pb.go index a2a5f00..ba9f6fe 100644 --- a/proto/pbrelay/relay.pb.go +++ b/proto/pbrelay/relay.pb.go @@ -73,15 +73,16 @@ func (ChangeType) EnumDescriptor() ([]byte, []int) { } type AuthenticateReq struct { - state protoimpl.MessageState `protogen:"open.v1"` - Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` - Addresses []*pbmodel.HostPort `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` - ReconnectToken []byte `protobuf:"bytes,3,opt,name=reconnect_token,json=reconnectToken,proto3" json:"reconnect_token,omitempty"` - BuildVersion string `protobuf:"bytes,4,opt,name=build_version,json=buildVersion,proto3" json:"build_version,omitempty"` - Metadata string `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` - ServerCertificate []byte `protobuf:"bytes,7,opt,name=server_certificate,json=serverCertificate,proto3" json:"server_certificate,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Addresses []*pbmodel.HostPort `protobuf:"bytes,5,rep,name=addresses,proto3" json:"addresses,omitempty"` + ReconnectToken []byte `protobuf:"bytes,3,opt,name=reconnect_token,json=reconnectToken,proto3" json:"reconnect_token,omitempty"` + BuildVersion string `protobuf:"bytes,4,opt,name=build_version,json=buildVersion,proto3" json:"build_version,omitempty"` + Metadata string `protobuf:"bytes,6,opt,name=metadata,proto3" json:"metadata,omitempty"` + ServerCertificate []byte `protobuf:"bytes,7,opt,name=server_certificate,json=serverCertificate,proto3" json:"server_certificate,omitempty"` + RelayAuthenticationKey []byte `protobuf:"bytes,8,opt,name=relay_authentication_key,json=relayAuthenticationKey,proto3" json:"relay_authentication_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AuthenticateReq) Reset() { @@ -156,14 +157,21 @@ func (x *AuthenticateReq) GetServerCertificate() []byte { return nil } +func (x *AuthenticateReq) GetRelayAuthenticationKey() []byte { + if x != nil { + return x.RelayAuthenticationKey + } + return nil +} + type AuthenticateResp struct { - state protoimpl.MessageState `protogen:"open.v1"` - Error *pberror.Error `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` - ControlId string `protobuf:"bytes,2,opt,name=control_id,json=controlId,proto3" json:"control_id,omitempty"` - ReconnectToken []byte `protobuf:"bytes,3,opt,name=reconnect_token,json=reconnectToken,proto3" json:"reconnect_token,omitempty"` - AuthenticationVerifyKey []byte `protobuf:"bytes,4,opt,name=authentication_verify_key,json=authenticationVerifyKey,proto3" json:"authentication_verify_key,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Error *pberror.Error `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + ControlId string `protobuf:"bytes,2,opt,name=control_id,json=controlId,proto3" json:"control_id,omitempty"` + ReconnectToken []byte `protobuf:"bytes,3,opt,name=reconnect_token,json=reconnectToken,proto3" json:"reconnect_token,omitempty"` + ControlAuthenticationKey []byte `protobuf:"bytes,4,opt,name=control_authentication_key,json=controlAuthenticationKey,proto3" json:"control_authentication_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *AuthenticateResp) Reset() { @@ -217,9 +225,9 @@ func (x *AuthenticateResp) GetReconnectToken() []byte { return nil } -func (x *AuthenticateResp) GetAuthenticationVerifyKey() []byte { +func (x *AuthenticateResp) GetControlAuthenticationKey() []byte { if x != nil { - return x.AuthenticationVerifyKey + return x.ControlAuthenticationKey } return nil } @@ -433,12 +441,12 @@ func (x *ServersResp) GetRestart() bool { } type ClientAuthentication struct { - state protoimpl.MessageState `protogen:"open.v1"` - Endpoint *pbmodel.Endpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` - Role pbmodel.Role `protobuf:"varint,2,opt,name=role,proto3,enum=model.Role" json:"role,omitempty"` - Certificate []byte `protobuf:"bytes,3,opt,name=certificate,proto3" json:"certificate,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Endpoint *pbmodel.Endpoint `protobuf:"bytes,1,opt,name=endpoint,proto3" json:"endpoint,omitempty"` + Role pbmodel.Role `protobuf:"varint,2,opt,name=role,proto3,enum=model.Role" json:"role,omitempty"` + CertificateKey string `protobuf:"bytes,3,opt,name=certificate_key,json=certificateKey,proto3" json:"certificate_key,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *ClientAuthentication) Reset() { @@ -485,11 +493,11 @@ func (x *ClientAuthentication) GetRole() pbmodel.Role { return pbmodel.Role(0) } -func (x *ClientAuthentication) GetCertificate() []byte { +func (x *ClientAuthentication) GetCertificateKey() string { if x != nil { - return x.Certificate + return x.CertificateKey } - return nil + return "" } type ClientsResp_Change struct { @@ -632,20 +640,21 @@ var File_relay_proto protoreflect.FileDescriptor const file_relay_proto_rawDesc = "" + "\n" + - "\vrelay.proto\x12\x05relay\x1a\verror.proto\x1a\vmodel.proto\"\xef\x01\n" + + "\vrelay.proto\x12\x05relay\x1a\verror.proto\x1a\vmodel.proto\"\xa9\x02\n" + "\x0fAuthenticateReq\x12\x14\n" + "\x05token\x18\x01 \x01(\tR\x05token\x12-\n" + "\taddresses\x18\x05 \x03(\v2\x0f.model.HostPortR\taddresses\x12'\n" + "\x0freconnect_token\x18\x03 \x01(\fR\x0ereconnectToken\x12#\n" + "\rbuild_version\x18\x04 \x01(\tR\fbuildVersion\x12\x1a\n" + "\bmetadata\x18\x06 \x01(\tR\bmetadata\x12-\n" + - "\x12server_certificate\x18\a \x01(\fR\x11serverCertificate\"\xba\x01\n" + + "\x12server_certificate\x18\a \x01(\fR\x11serverCertificate\x128\n" + + "\x18relay_authentication_key\x18\b \x01(\fR\x16relayAuthenticationKey\"\xbc\x01\n" + "\x10AuthenticateResp\x12\"\n" + "\x05error\x18\x01 \x01(\v2\f.error.ErrorR\x05error\x12\x1d\n" + "\n" + "control_id\x18\x02 \x01(\tR\tcontrolId\x12'\n" + - "\x0freconnect_token\x18\x03 \x01(\fR\x0ereconnectToken\x12:\n" + - "\x19authentication_verify_key\x18\x04 \x01(\fR\x17authenticationVerifyKey\"$\n" + + "\x0freconnect_token\x18\x03 \x01(\fR\x0ereconnectToken\x12<\n" + + "\x1acontrol_authentication_key\x18\x04 \x01(\fR\x18controlAuthenticationKey\"$\n" + "\n" + "ClientsReq\x12\x16\n" + "\x06offset\x18\x01 \x01(\x03R\x06offset\"\xc3\x02\n" + @@ -669,11 +678,11 @@ const file_relay_proto_rawDesc = "" + "\x06Change\x12)\n" + "\x06change\x18\x01 \x01(\x0e2\x11.relay.ChangeTypeR\x06change\x12+\n" + "\bendpoint\x18\x02 \x01(\v2\x0f.model.EndpointR\bendpoint\x12-\n" + - "\x12server_certificate\x18\x03 \x01(\fR\x11serverCertificate\"\x86\x01\n" + + "\x12server_certificate\x18\x03 \x01(\fR\x11serverCertificate\"\x8d\x01\n" + "\x14ClientAuthentication\x12+\n" + "\bendpoint\x18\x01 \x01(\v2\x0f.model.EndpointR\bendpoint\x12\x1f\n" + - "\x04role\x18\x02 \x01(\x0e2\v.model.RoleR\x04role\x12 \n" + - "\vcertificate\x18\x03 \x01(\fR\vcertificate*=\n" + + "\x04role\x18\x02 \x01(\x0e2\v.model.RoleR\x04role\x12'\n" + + "\x0fcertificate_key\x18\x03 \x01(\tR\x0ecertificateKey*=\n" + "\n" + "ChangeType\x12\x11\n" + "\rChangeUnknown\x10\x00\x12\r\n" + diff --git a/proto/relay.proto b/proto/relay.proto index 9624870..836aa9e 100644 --- a/proto/relay.proto +++ b/proto/relay.proto @@ -13,13 +13,14 @@ message AuthenticateReq { string build_version = 4; string metadata = 6; bytes server_certificate = 7; + bytes relay_authentication_key = 8; } message AuthenticateResp { error.Error error = 1; string control_id = 2; bytes reconnect_token = 3; - bytes authentication_verify_key = 4; + bytes control_authentication_key = 4; } enum ChangeType { @@ -65,5 +66,5 @@ message ServersResp { message ClientAuthentication { model.Endpoint endpoint = 1; model.Role role = 2; - bytes certificate = 3; + string certificate_key = 3; } diff --git a/relay.go b/relay.go index d708710..cac3327 100644 --- a/relay.go +++ b/relay.go @@ -39,7 +39,7 @@ type relay struct { type relayConfig struct { tls *serverTLSConfig - auth *pbclientrelay.AuthenticateReq + auth []byte } func runRelay(ctx context.Context, local *peer, id relayID, hps []model.HostPort, cfg *relayConfig, logger *slog.Logger) *relay { @@ -165,7 +165,7 @@ func (r *relay) check(ctx context.Context, conn *quic.Conn) error { return nil } -func (r *relay) authenticate(ctx context.Context, conn *quic.Conn, auth *pbclientrelay.AuthenticateReq) error { +func (r *relay) authenticate(ctx context.Context, conn *quic.Conn, auth []byte) error { stream, err := conn.OpenStreamSync(ctx) if err != nil { return err @@ -176,7 +176,11 @@ func (r *relay) authenticate(ctx context.Context, conn *quic.Conn, auth *pbclien } }() - if err := proto.Write(stream, auth); err != nil { + if err := proto.Write(stream, &pbclientrelay.AuthenticateReq{ + Authentication: auth, + Metadata: r.local.metadata, + BuildVersion: model.BuildVersion(), + }); err != nil { return fmt.Errorf("cannot write auth request: %w", err) } diff --git a/server/control/relays.go b/server/control/relays.go index 589d707..a481ed6 100644 --- a/server/control/relays.go +++ b/server/control/relays.go @@ -2,7 +2,6 @@ package control import ( "context" - "crypto/ed25519" "crypto/rand" "crypto/x509" "errors" @@ -25,6 +24,7 @@ import ( "github.com/connet-dev/connet/proto/pberror" "github.com/connet-dev/connet/proto/pbrelay" "github.com/quic-go/quic-go" + "golang.org/x/crypto/nacl/box" protobuf "google.golang.org/protobuf/proto" ) @@ -96,9 +96,9 @@ func newRelayServer( directsCache := map[RelayID]directRelay{} for _, msg := range directsMsgs { directsCache[msg.Key.ID] = directRelay{ - auth: msg.Value.Authentication, - signKey: msg.Value.AuthenticationSignKey, - proto: &pbclient.DirectRelay{ + auth: msg.Value.Authentication, + authSealKey: msg.Value.AuthenticationSealKey, + template: &pbclient.DirectRelay{ Id: msg.Key.ID.string, Addresses: model.PBsFromHostPorts(msg.Value.Hostports), ServerCertificate: msg.Value.Certificate.Raw, @@ -189,9 +189,9 @@ type relayServer struct { } type directRelay struct { - auth RelayAuthentication - signKey ed25519.PrivateKey - proto *pbclient.DirectRelay + auth RelayAuthentication + authSealKey *[32]byte + template *pbclient.DirectRelay } func (s *relayServer) getEndpoint(endpoint model.Endpoint) (map[RelayID]relayCacheValue, int64) { @@ -269,14 +269,19 @@ func (s *relayServer) cachedDirects() (map[RelayID]directRelay, int64) { func (s *relayServer) Directs(ctx context.Context, endpoint model.Endpoint, role model.Role, cert *x509.Certificate, auth ClientAuthentication, notify func(map[RelayID]*pbclient.DirectRelay) error) error { - signatureData, err := protobuf.Marshal(&pbrelay.ClientAuthentication{ - Endpoint: endpoint.PB(), - Role: role.PB(), - Certificate: cert.Raw, + authenticationData, err := protobuf.Marshal(&pbrelay.ClientAuthentication{ + Endpoint: endpoint.PB(), + Role: role.PB(), + CertificateKey: model.NewKey(cert).String(), }) if err != nil { return fmt.Errorf("signature data error: %w", err) } + seal := func(key *[32]byte) []byte { + var nonce [24]byte + rand.Read(nonce[:]) // nolint:errcheck + return box.SealAfterPrecomputation(nonce[:], authenticationData, &nonce, key) + } directRelays, offset := s.cachedDirects() localDirectRelays := map[RelayID]*pbclient.DirectRelay{} @@ -285,10 +290,11 @@ func (s *relayServer) Directs(ctx context.Context, endpoint model.Endpoint, role return fmt.Errorf("auth allow error: %w", err) } else if ok { localDirectRelays[id] = &pbclient.DirectRelay{ - Id: relay.proto.Id, - Addresses: relay.proto.Addresses, - ServerCertificate: relay.proto.ServerCertificate, - AuthenticationSignature: ed25519.Sign(relay.signKey, signatureData), + Id: relay.template.Id, + Addresses: relay.template.Addresses, + ServerCertificate: relay.template.ServerCertificate, + Authentication: seal(relay.authSealKey), + Metadata: relay.template.Metadata, } } } @@ -311,10 +317,11 @@ func (s *relayServer) Directs(ctx context.Context, endpoint model.Endpoint, role return fmt.Errorf("auth allow error: %w", err) } else if ok { localDirectRelays[msg.Key.ID] = &pbclient.DirectRelay{ - Id: msg.Key.ID.string, - Addresses: model.PBsFromHostPorts(msg.Value.Hostports), - ServerCertificate: msg.Value.Certificate.Raw, - AuthenticationSignature: ed25519.Sign(msg.Value.AuthenticationSignKey, signatureData), + Id: msg.Key.ID.string, + Addresses: model.PBsFromHostPorts(msg.Value.Hostports), + ServerCertificate: msg.Value.Certificate.Raw, + Authentication: seal(msg.Value.AuthenticationSealKey), + Metadata: msg.Value.Metadata, } changed = true } @@ -461,12 +468,13 @@ func (s *relayServer) runDirectsCache(ctx context.Context) error { delete(s.directsCache, msg.Key.ID) } else { s.directsCache[msg.Key.ID] = directRelay{ - auth: msg.Value.Authentication, - signKey: msg.Value.AuthenticationSignKey, - proto: &pbclient.DirectRelay{ + auth: msg.Value.Authentication, + authSealKey: msg.Value.AuthenticationSealKey, + template: &pbclient.DirectRelay{ Id: msg.Key.ID.string, Addresses: model.PBsFromHostPorts(msg.Value.Hostports), ServerCertificate: msg.Value.Certificate.Raw, + Metadata: msg.Value.Metadata, }, } } @@ -526,7 +534,7 @@ type relayConnAuth struct { metadata string protocol model.RelayControlNextProto certificate *x509.Certificate - signKey ed25519.PrivateKey + authSignKey *[32]byte } func (c *relayConn) run(ctx context.Context) { @@ -584,7 +592,7 @@ func (c *relayConn) runErr(ctx context.Context) error { if c.protocol == model.RelayControlV03 { key := RelayConnKey{ID: c.id} - value := RelayDirectValue{c.auth, c.hostports, c.metadata, c.certificate, c.signKey} + value := RelayDirectValue{c.auth, c.hostports, c.metadata, c.certificate, c.authSignKey} if err := c.server.directs.Put(key, value); err != nil { return err } @@ -621,22 +629,69 @@ func (c *relayConn) authenticate(ctx context.Context) (*relayConnAuth, error) { return nil, fmt.Errorf("auth read request: %w", err) } - protocol := model.GetRelayControlNextProto(c.conn) + switch model.GetRelayControlNextProto(c.conn) { + case model.RelayControlV02: + return c.authenticateV2(authStream, req) + default: + return c.authenticateV3(authStream, req) + } +} - var cert *x509.Certificate - if protocol != model.RelayControlV02 { - // old relays do not support direct relays, so no certificate - cert, err = x509.ParseCertificate(req.ServerCertificate) - if err != nil { - perr := pberror.GetError(err) - if perr == nil { - perr = pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: %v", err) - } - if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { - return nil, fmt.Errorf("relay auth err write: %w", err) - } - return nil, fmt.Errorf("auth failed: %w", perr) +func (c *relayConn) authenticateV2(authStream *quic.Stream, req *pbrelay.AuthenticateReq) (*relayConnAuth, error) { + auth, err := c.server.auth.Authenticate(RelayAuthenticateRequest{ + Proto: model.RelayControlV02, + Token: req.Token, + Addr: c.conn.RemoteAddr(), + BuildVersion: req.BuildVersion, + }) + if err != nil { + perr := pberror.GetError(err) + if perr == nil { + perr = pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: %v", err) + } + if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { + return nil, fmt.Errorf("relay auth err write: %w", err) } + return nil, fmt.Errorf("auth failed: %w", perr) + } + + var id RelayID + if sid, err := c.server.reconnect.openRelayID(req.ReconnectToken); err != nil { + c.logger.Debug("decode failed", "err", err) + id = NewRelayID() + } else { + id = sid + } + + retoken, err := c.server.reconnect.sealRelayID(id) + if err != nil { + c.logger.Debug("encrypting failed", "err", err) + retoken = nil + } + if err := proto.Write(authStream, &pbrelay.AuthenticateResp{ + ControlId: c.server.id, + ReconnectToken: retoken, + }); err != nil { + return nil, fmt.Errorf("auth write response: %w", err) + } + + c.logger.Debug("authentication completed", "local", c.conn.LocalAddr(), "remote", c.conn.RemoteAddr(), "proto", model.RelayControlV02, "build", req.BuildVersion) + hostports := model.HostPortFromPBs(req.Addresses) + return &relayConnAuth{id, auth, hostports, req.Metadata, model.RelayControlV02, nil, nil}, nil +} + +func (c *relayConn) authenticateV3(authStream *quic.Stream, req *pbrelay.AuthenticateReq) (*relayConnAuth, error) { + protocol := model.RelayControlV03 + cert, err := x509.ParseCertificate(req.ServerCertificate) + if err != nil { + perr := pberror.GetError(err) + if perr == nil { + perr = pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: %v", err) + } + if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { + return nil, fmt.Errorf("relay auth err write: %w", err) + } + return nil, fmt.Errorf("auth failed: %w", perr) } auth, err := c.server.auth.Authenticate(RelayAuthenticateRequest{ @@ -664,20 +719,20 @@ func (c *relayConn) authenticate(ctx context.Context) (*relayConnAuth, error) { id = sid } - var ( - pk ed25519.PublicKey - sk ed25519.PrivateKey - ) - if protocol != model.RelayControlV02 { - // old relays do not support direct relays, so need to generate keys - pk, sk, err = ed25519.GenerateKey(rand.Reader) - if err != nil { - perr := pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: %v", err) - if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { - return nil, fmt.Errorf("relay auth err write: %w", err) - } - return nil, fmt.Errorf("auth failed: %w", perr) + controlPk, controlSk, err := box.GenerateKey(rand.Reader) + if err != nil { + perr := pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: %v", err) + if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { + return nil, fmt.Errorf("relay auth err write: %w", err) + } + return nil, fmt.Errorf("auth failed: %w", perr) + } + if len(req.RelayAuthenticationKey) != 32 { + perr := pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed: invalid key") + if err := proto.Write(authStream, &pbrelay.AuthenticateResp{Error: perr}); err != nil { + return nil, fmt.Errorf("relay auth err write: %w", err) } + return nil, fmt.Errorf("auth failed: %w", perr) } retoken, err := c.server.reconnect.sealRelayID(id) @@ -686,16 +741,20 @@ func (c *relayConn) authenticate(ctx context.Context) (*relayConnAuth, error) { retoken = nil } if err := proto.Write(authStream, &pbrelay.AuthenticateResp{ - ControlId: c.server.id, - ReconnectToken: retoken, - AuthenticationVerifyKey: pk, + ControlId: c.server.id, + ReconnectToken: retoken, + ControlAuthenticationKey: controlPk[:], }); err != nil { return nil, fmt.Errorf("auth write response: %w", err) } + var relayPk = [32]byte(req.RelayAuthenticationKey) + sharedKey := new([32]byte) + box.Precompute(sharedKey, &relayPk, controlSk) + c.logger.Debug("authentication completed", "local", c.conn.LocalAddr(), "remote", c.conn.RemoteAddr(), "proto", protocol, "build", req.BuildVersion) hostports := model.HostPortFromPBs(req.Addresses) - return &relayConnAuth{id, auth, hostports, req.Metadata, protocol, cert, sk}, nil + return &relayConnAuth{id, auth, hostports, req.Metadata, protocol, cert, sharedKey}, nil } func (c *relayConn) runRelayClients(ctx context.Context) error { diff --git a/server/control/server.go b/server/control/server.go index 4474931..ab2abff 100644 --- a/server/control/server.go +++ b/server/control/server.go @@ -80,6 +80,8 @@ func (s *Server) Status(ctx context.Context) (Status, error) { } return Status{ + BuildVersion: model.BuildVersion(), + ClientIngresses: iterc.MapSlice(s.clients.ingresses, StatusIngressFn), Clients: clients, Endpoints: endpoints, @@ -153,6 +155,8 @@ func (s *Server) getRelays() (map[string]StatusRelay, error) { } type Status struct { + BuildVersion string `json:"build-version"` + ClientIngresses []StatusIngress `json:"client-ingresses"` Clients map[string]StatusClient `json:"clients"` Endpoints map[string]StatusEndpoint `json:"endpoints"` diff --git a/server/control/store.go b/server/control/store.go index 501b1fe..b8c96f6 100644 --- a/server/control/store.go +++ b/server/control/store.go @@ -1,7 +1,6 @@ package control import ( - "crypto/ed25519" "crypto/x509" "encoding/json" "path/filepath" @@ -127,7 +126,7 @@ type RelayDirectValue struct { Hostports []model.HostPort `json:"hostports"` Metadata string `json:"metadata"` Certificate *x509.Certificate `json:"certificate"` - AuthenticationSignKey ed25519.PrivateKey `json:"authentication-sign-key"` + AuthenticationSealKey *[32]byte `json:"authentication-seal-key"` } type jsonRelayDirectValue struct { @@ -135,7 +134,7 @@ type jsonRelayDirectValue struct { Hostports []model.HostPort `json:"hostports"` Metadata string `json:"metadata"` Certificate []byte `json:"certificate"` - AuthenticationSignKey []byte `json:"authentication-sign-key"` + AuthenticationSealKey []byte `json:"authentication-seal-key"` } func (v RelayDirectValue) MarshalJSON() ([]byte, error) { @@ -144,7 +143,7 @@ func (v RelayDirectValue) MarshalJSON() ([]byte, error) { Hostports: v.Hostports, Metadata: v.Metadata, Certificate: v.Certificate.Raw, - AuthenticationSignKey: v.AuthenticationSignKey, + AuthenticationSealKey: v.AuthenticationSealKey[:], }) } @@ -159,7 +158,9 @@ func (v *RelayDirectValue) UnmarshalJSON(b []byte) error { return err } - *v = RelayDirectValue{s.Authentication, s.Hostports, s.Metadata, cert, s.AuthenticationSignKey} + var authKey [32]byte + copy(authKey[:], s.AuthenticationSealKey) + *v = RelayDirectValue{s.Authentication, s.Hostports, s.Metadata, cert, &authKey} return nil } diff --git a/server/relay/control.go b/server/relay/control.go index 9519d61..6a6c4a5 100644 --- a/server/relay/control.go +++ b/server/relay/control.go @@ -2,7 +2,7 @@ package relay import ( "context" - "crypto/ed25519" + "crypto/rand" "crypto/tls" "crypto/x509" "errors" @@ -28,6 +28,7 @@ import ( "github.com/connet-dev/connet/proto/pbrelay" "github.com/klev-dev/klevdb" "github.com/quic-go/quic-go" + "golang.org/x/crypto/nacl/box" protobuf "google.golang.org/protobuf/proto" ) @@ -53,8 +54,7 @@ type controlClient struct { clientsStreamOffset int64 clientsLogOffset int64 - directVerifyKey ed25519.PublicKey - directVerifyKeyMu sync.RWMutex + authUnsealKey atomic.Pointer[[32]byte] connStatus atomic.Value logger *slog.Logger @@ -180,27 +180,26 @@ func (s *controlClient) v1Auth(serverName string, certs []*x509.Certificate) *cl } func (s *controlClient) v2Auth(authReq *pbclientrelay.AuthenticateReq, cert *x509.Certificate) (*clientAuth, error) { - signatureData, err := protobuf.Marshal(&pbrelay.ClientAuthentication{ - Endpoint: authReq.Endpoint, - Role: authReq.Role, - Certificate: cert.Raw, - }) - if err != nil { - return nil, fmt.Errorf("signature data error: %w", err) - } - - s.directVerifyKeyMu.RLock() - directVerifyKey := s.directVerifyKey - s.directVerifyKeyMu.RUnlock() - - if directVerifyKey == nil { + authUnsealKey := s.authUnsealKey.Load() + if authUnsealKey == nil { return nil, fmt.Errorf("no control verification key") } - if !ed25519.Verify(directVerifyKey, signatureData, authReq.AuthenticationSignature) { - return nil, pberror.NewError(pberror.Code_AuthenticationFailed, "could not verify authentication") + decryptNonce := [24]byte(authReq.Authentication) + authData, ok := box.OpenAfterPrecomputation(nil, authReq.Authentication[24:], &decryptNonce, authUnsealKey) + if !ok { + return nil, pberror.NewError(pberror.Code_AuthenticationFailed, "invalid authentication") + } + var auth pbrelay.ClientAuthentication + if err := protobuf.Unmarshal(authData, &auth); err != nil { + return nil, pberror.NewError(pberror.Code_AuthenticationFailed, "invalid authentication data") } - return &clientAuth{model.EndpointFromPB(authReq.Endpoint), model.RoleFromPB(authReq.Role), model.NewKey(cert), model.ConnectRelayV02, authReq.Metadata}, nil + certKey := model.NewKey(cert) + if auth.CertificateKey != certKey.String() { + return nil, pberror.NewError(pberror.Code_AuthenticationFailed, "invalid certificate") + } + + return &clientAuth{model.EndpointFromPB(auth.Endpoint), model.RoleFromPB(auth.Role), certKey, model.ConnectRelayV02, authReq.Metadata}, nil } type TransportsFn func(ctx context.Context) ([]*quic.Transport, error) @@ -281,56 +280,116 @@ func (s *controlClient) connectSingle(ctx context.Context, transport *quic.Trans } }() - protocol := model.GetRelayControlNextProto(conn) + switch model.GetRelayControlNextProto(conn) { + case model.RelayControlV02: + err = s.authenticateV2(authStream, reconnConfig) + default: + err = s.authenticate(authStream, reconnConfig) + } + if err != nil { + perr := pberror.GetError(err) + if perr == nil { + perr = pberror.NewError(pberror.Code_AuthenticationFailed, "authentication failed") + } + cerr := conn.CloseWithError(quic.ApplicationErrorCode(perr.Code), perr.Message) + return nil, errors.Join(perr, cerr) + } - var serverCert []byte - if protocol == model.RelayControlV03 { - serverCert = s.direct.Raw() + return conn, nil +} + +func (s *controlClient) authenticateV2(authStream *quic.Stream, reconnConfig ConfigValue) error { + if err := proto.Write(authStream, &pbrelay.AuthenticateReq{ + Token: s.controlToken, + Addresses: model.PBsFromHostPorts(s.hostports), + ReconnectToken: reconnConfig.Bytes, + BuildVersion: model.BuildVersion(), + Metadata: s.metadata, + }); err != nil { + return fmt.Errorf("auth write error: %w", err) + } + + resp := &pbrelay.AuthenticateResp{} + if err := proto.Read(authStream, resp); err != nil { + return fmt.Errorf("auth read error: %w", err) + } + if resp.Error != nil { + return fmt.Errorf("remote error: %w", resp.Error) + } + + controlIDConfig, err := s.config.GetOrDefault(configControlID, ConfigValue{}) + if err != nil { + return fmt.Errorf("server control id get: %w", err) + } + if controlIDConfig.String != "" && controlIDConfig.String != resp.ControlId { + return fmt.Errorf("unexpected server id, has: %s, resp: %s", controlIDConfig.String, resp.ControlId) + } + controlIDConfig.String = resp.ControlId + if err := s.config.Put(configControlID, controlIDConfig); err != nil { + return fmt.Errorf("server control id set: %w", err) + } + + reconnConfig.Bytes = resp.ReconnectToken + if err := s.config.Put(configControlReconnect, reconnConfig); err != nil { + return fmt.Errorf("server reconnect set: %w", err) + } + + return nil +} + +func (s *controlClient) authenticate(authStream *quic.Stream, reconnConfig ConfigValue) error { + relayPk, relaySk, err := box.GenerateKey(rand.Reader) + if err != nil { + return fmt.Errorf("could not create keys: %w", err) } if err := proto.Write(authStream, &pbrelay.AuthenticateReq{ - Token: s.controlToken, - Addresses: model.PBsFromHostPorts(s.hostports), - ReconnectToken: reconnConfig.Bytes, - BuildVersion: model.BuildVersion(), - Metadata: s.metadata, - ServerCertificate: serverCert, + Token: s.controlToken, + Addresses: model.PBsFromHostPorts(s.hostports), + ReconnectToken: reconnConfig.Bytes, + BuildVersion: model.BuildVersion(), + Metadata: s.metadata, + ServerCertificate: s.direct.Raw(), + RelayAuthenticationKey: relayPk[:], }); err != nil { - return nil, fmt.Errorf("auth write error: %w", err) + return fmt.Errorf("auth write error: %w", err) } resp := &pbrelay.AuthenticateResp{} if err := proto.Read(authStream, resp); err != nil { - return nil, fmt.Errorf("auth read error: %w", err) + return fmt.Errorf("auth read error: %w", err) } if resp.Error != nil { - return nil, fmt.Errorf("remote error: %w", resp.Error) + return fmt.Errorf("remote error: %w", resp.Error) } controlIDConfig, err := s.config.GetOrDefault(configControlID, ConfigValue{}) if err != nil { - return nil, fmt.Errorf("server control id get: %w", err) + return fmt.Errorf("server control id get: %w", err) } if controlIDConfig.String != "" && controlIDConfig.String != resp.ControlId { - return nil, fmt.Errorf("unexpected server id, has: %s, resp: %s", controlIDConfig.String, resp.ControlId) + return fmt.Errorf("unexpected server id, has: %s, resp: %s", controlIDConfig.String, resp.ControlId) } controlIDConfig.String = resp.ControlId if err := s.config.Put(configControlID, controlIDConfig); err != nil { - return nil, fmt.Errorf("server control id set: %w", err) + return fmt.Errorf("server control id set: %w", err) } reconnConfig.Bytes = resp.ReconnectToken if err := s.config.Put(configControlReconnect, reconnConfig); err != nil { - return nil, fmt.Errorf("server reconnect set: %w", err) + return fmt.Errorf("server reconnect set: %w", err) } - if protocol == model.RelayControlV03 { - s.directVerifyKeyMu.Lock() - s.directVerifyKey = resp.AuthenticationVerifyKey - s.directVerifyKeyMu.Unlock() + if len(resp.ControlAuthenticationKey) != 32 { + return fmt.Errorf("invalid control auth key length") } - return conn, nil + controlPk := [32]byte(resp.ControlAuthenticationKey) + sharedKey := new([32]byte) + box.Precompute(sharedKey, &controlPk, relaySk) + s.authUnsealKey.Store(sharedKey) + + return nil } func (s *controlClient) reconnect(ctx context.Context, tfn TransportsFn) (*quic.Conn, error) { diff --git a/server/relay/server.go b/server/relay/server.go index eb75574..add3d21 100644 --- a/server/relay/server.go +++ b/server/relay/server.go @@ -128,11 +128,12 @@ func (s *Server) Run(ctx context.Context) error { } type Status struct { - Status statusc.Status `json:"status"` - Hostports []string `json:"hostports"` - ServerAddr string `json:"server-addrress"` - ServerID string `json:"server-id"` - Endpoints map[string]EndpointStatus `json:"endpoints"` + Status statusc.Status `json:"status"` + BuildVersion string `json:"build-version"` + Hostports []string `json:"hostports"` + ServerAddr string `json:"server-addrress"` + ServerID string `json:"server-id"` + Endpoints map[string]EndpointStatus `json:"endpoints"` } type EndpointStatus struct { @@ -157,11 +158,12 @@ func (s *Server) Status(ctx context.Context) (Status, error) { eps := s.getEndpoints() return Status{ - Status: stat, - Hostports: iterc.MapSliceStrings(s.control.hostports), - ServerAddr: s.control.controlAddr.String(), - ServerID: controlID, - Endpoints: eps, + Status: stat, + BuildVersion: model.BuildVersion(), + Hostports: iterc.MapSliceStrings(s.control.hostports), + ServerAddr: s.control.controlAddr.String(), + ServerID: controlID, + Endpoints: eps, }, nil }