From 30d5ac4fff7a20050c8013423af826bfa0565a59 Mon Sep 17 00:00:00 2001 From: Wendel Hime <6754291+WendelHime@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:57:45 -0300 Subject: [PATCH] adding IsReady to dialer interface (#1436) * feat: adding IsReady to BanditDialer and isReady to proxy implementations * feat: only select dialer when ready to dial * fix: adding IsReady to test dialer * fix: loading b64 wasm in background so channel doesn't lock and only wait for download when ready is isn't ready * fix: removing err declaration * fix: renaming channel and waiting dialer to be available before checking at test * feat: if dialer is not ready, try to add to bypass again in 60s * feat: adding log debug and continue when loading dialer in background * fix: adopt suggestions and make IsReady return an error * feat: verifying IsReady error at bandit before checking if it's ready or not * fix: updating bypass function for using and checking context timeout, if it happens it should stop retrying to load the proxy async * feat: adding locker at package lever for avoiding downloading WASM file with data race * feat: add support for water multiplex * fix: typo * fix: checking error at bypass before using ready * fix: canceling context if dialer IsReady returns an error * fix: start proxy if dialer is ready * fix: removing retry atomic bool and replace by select statement * fix: removing if statement for checking if thereadyChain is closed and removing close * fix: removing close calls for finishedToLoad; updated dialer closed message and isReady now returns true based on flag and dialer different than nil * fix: using a sync.Map for handling lockers per WATER transport * fix: making Ready returns a <- chan error * fix: checking if ready chan is nil * fix: adding on success to dialer * fix: deleting bandit package * fix: removing errLoadingWASM var and send it directly to the channel * feat: go mod tidy * fix: replace log by logger * fix: add comment for making explicit IsReady can return a nil value if initialization is not required and verifying at bypass even when loading async * fix: replace sync.Map usage by creating water WASM lock map and a locker for map; also renaming httpClient to waterHTTPClient * fix: set default behavior instead of waiting for dialer to be ready at bandit * fix: make water wasm map * feat: broadcast ready status to all chan listeners * fix: update bandit message * fix: using a buffered channel so we can simplify logic (replacing go routines, context timeouts) * fix: updating types.proto * fix: adding fields to apipib legacy and update water impl connect options * fix: updating test --- apipb/legacy.go | 11 +- apipb/types.pb.go | 294 +++++++++++++++++-------------- apipb/types.proto | 9 +- chained/algeneva_impl.go | 4 + chained/broflake_impl.go | 4 + chained/chained_test.go | 4 + chained/http_impl.go | 4 + chained/https_impl.go | 4 + chained/multipath.go | 4 + chained/multiplexed_impl.go | 4 + chained/preconnecting_dialer.go | 4 + chained/proxy.go | 6 + chained/quic_impl.go | 4 + chained/shadowsocks_impl.go | 8 +- chained/starbridge_impl.go | 4 + chained/tlsmasq_impl.go | 4 + chained/water_impl.go | 172 +++++++++++++----- chained/water_impl_test.go | 33 +++- chained/water_version_control.go | 40 ++++- chained/wss_impl.go | 4 + client/client_test.go | 4 + dialer/bandit.go | 15 ++ dialer/bandit_test.go | 4 + dialer/dialer.go | 5 + dialer/dialer_test.go | 4 + services/bypass.go | 82 +++++++-- 26 files changed, 531 insertions(+), 204 deletions(-) diff --git a/apipb/legacy.go b/apipb/legacy.go index be6002161..986544b87 100644 --- a/apipb/legacy.go +++ b/apipb/legacy.go @@ -14,6 +14,7 @@ import ( "net" "strconv" "strings" + "time" commonconfig "github.com/getlantern/common/config" ) @@ -177,9 +178,15 @@ func ProxyToLegacyConfig(cfg *ProxyConnectConfig) (*commonconfig.ProxyConfig, er } case *ProxyConnectConfig_ConnectCfgWater: legacy.PluggableTransport = "water" + duration, err := time.ParseDuration(pCfg.ConnectCfgWater.DownloadTimeout.String()) + if err != nil { + duration = 5 * time.Minute + } legacy.PluggableTransportSettings = map[string]string{ - "water_wasm": base64.StdEncoding.EncodeToString(pCfg.ConnectCfgWater.Wasm), - "water_transport": pCfg.ConnectCfgWater.Transport, + "water_wasm": base64.StdEncoding.EncodeToString(pCfg.ConnectCfgWater.Wasm), + "water_transport": pCfg.ConnectCfgWater.Transport, + "wasm_available_at": strings.Join(pCfg.ConnectCfgWater.WasmAvailableAt, ","), + "download_timeout": duration.String(), } default: diff --git a/apipb/types.pb.go b/apipb/types.pb.go index 30dbdb76d..d7c5ea7fd 100644 --- a/apipb/types.pb.go +++ b/apipb/types.pb.go @@ -1,15 +1,16 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.33.0 -// protoc v5.28.3 +// protoc v3.12.4 // source: apipb/types.proto package apipb import ( + duration "github.com/golang/protobuf/ptypes/duration" + timestamp "github.com/golang/protobuf/ptypes/timestamp" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -827,8 +828,8 @@ type ConfigRequest_Proxy struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` // list of proxy ids - LastRequest *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=last_request,json=lastRequest,proto3" json:"last_request,omitempty"` // last time client requested proxy config + Names []string `protobuf:"bytes,1,rep,name=names,proto3" json:"names,omitempty"` // list of proxy ids + LastRequest *timestamp.Timestamp `protobuf:"bytes,2,opt,name=last_request,json=lastRequest,proto3" json:"last_request,omitempty"` // last time client requested proxy config } func (x *ConfigRequest_Proxy) Reset() { @@ -870,7 +871,7 @@ func (x *ConfigRequest_Proxy) GetNames() []string { return nil } -func (x *ConfigRequest_Proxy) GetLastRequest() *timestamppb.Timestamp { +func (x *ConfigRequest_Proxy) GetLastRequest() *timestamp.Timestamp { if x != nil { return x.LastRequest } @@ -1445,8 +1446,16 @@ type ProxyConnectConfig_WATERConfig struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // deprecated: use wasm_available_at instead + // + // Deprecated: Marked as deprecated in apipb/types.proto. Wasm []byte `protobuf:"bytes,1,opt,name=wasm,proto3" json:"wasm,omitempty"` Transport string `protobuf:"bytes,2,opt,name=transport,proto3" json:"transport,omitempty"` + // wasm_available_at provide a list of URLs that can be used for fetching the WASM module + // with different methods like a direct URL, a magnet link, etc. + WasmAvailableAt []string `protobuf:"bytes,3,rep,name=wasm_available_at,json=wasmAvailableAt,proto3" json:"wasm_available_at,omitempty"` + // download_timeout specifies the time the HTTP client should wait to retrieve the WASM binary + DownloadTimeout *duration.Duration `protobuf:"bytes,4,opt,name=download_timeout,json=downloadTimeout,proto3" json:"download_timeout,omitempty"` } func (x *ProxyConnectConfig_WATERConfig) Reset() { @@ -1481,6 +1490,7 @@ func (*ProxyConnectConfig_WATERConfig) Descriptor() ([]byte, []int) { return file_apipb_types_proto_rawDescGZIP(), []int{2, 7} } +// Deprecated: Marked as deprecated in apipb/types.proto. func (x *ProxyConnectConfig_WATERConfig) GetWasm() []byte { if x != nil { return x.Wasm @@ -1495,6 +1505,20 @@ func (x *ProxyConnectConfig_WATERConfig) GetTransport() string { return "" } +func (x *ProxyConnectConfig_WATERConfig) GetWasmAvailableAt() []string { + if x != nil { + return x.WasmAvailableAt + } + return nil +} + +func (x *ProxyConnectConfig_WATERConfig) GetDownloadTimeout() *duration.Duration { + if x != nil { + return x.DownloadTimeout + } + return nil +} + // SessionState represents a utls.ClientSessionState. type ProxyConnectConfig_TLSConfig_SessionState struct { state protoimpl.MessageState @@ -1662,6 +1686,8 @@ var file_apipb_types_proto_rawDesc = []byte{ 0x0a, 0x11, 0x61, 0x70, 0x69, 0x70, 0x62, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9a, 0x03, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x3a, 0x0a, 0x0b, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x43, 0x6f, @@ -1700,7 +1726,7 @@ var file_apipb_types_proto_rawDesc = []byte{ 0x79, 0x12, 0x2d, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x07, 0x70, 0x72, 0x6f, 0x78, 0x69, 0x65, 0x73, - 0x22, 0xc8, 0x12, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x22, 0xbf, 0x13, 0x0a, 0x12, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, @@ -1843,121 +1869,129 @@ var file_apipb_types_proto_rawDesc = []byte{ 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x1a, 0x2c, 0x0a, 0x0e, 0x41, 0x6c, 0x67, 0x65, 0x6e, 0x65, 0x76, 0x61, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0x3f, 0x0a, 0x0b, 0x57, 0x41, 0x54, 0x45, 0x52, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x77, 0x61, 0x73, 0x6d, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x77, 0x61, 0x73, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x99, 0x0c, 0x0a, 0x13, - 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, - 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, - 0x0a, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, - 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x50, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, - 0x12, 0x0a, 0x04, 0x62, 0x69, 0x61, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x62, - 0x69, 0x61, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, - 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x12, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x12, 0x76, 0x0a, 0x1c, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, - 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x4c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, - 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x1a, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x1c, 0x0a, 0x09, - 0x45, 0x4e, 0x48, 0x54, 0x54, 0x50, 0x55, 0x52, 0x4c, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x09, 0x45, 0x4e, 0x48, 0x54, 0x54, 0x50, 0x55, 0x52, 0x4c, 0x12, 0x50, 0x0a, 0x25, 0x54, 0x4c, - 0x53, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, - 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x21, 0x54, 0x4c, 0x53, 0x44, 0x65, - 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x69, 0x70, 0x68, - 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x24, - 0x54, 0x4c, 0x53, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, - 0x64, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, 0x75, 0x69, 0x74, 0x65, 0x5f, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x20, 0x54, 0x4c, 0x53, 0x4d, - 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x43, 0x69, 0x70, 0x68, - 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, - 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, - 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, - 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x49, 0x6e, 0x64, - 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3f, 0x0a, 0x1c, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x63, 0x68, - 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x19, 0x54, 0x4c, - 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, - 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x54, 0x4c, 0x53, 0x43, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x49, 0x44, 0x18, 0x0f, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x10, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x6c, - 0x6c, 0x6f, 0x49, 0x44, 0x12, 0x3a, 0x0a, 0x19, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x5f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x5f, 0x73, 0x70, 0x6c, 0x69, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, - 0x12, 0x36, 0x0a, 0x17, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x15, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x3e, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x4c, 0x65, 0x67, - 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, - 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, - 0x69, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x45, - 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x6d, 0x75, 0x6c, 0x74, 0x69, - 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x41, 0x64, - 0x64, 0x72, 0x12, 0x3c, 0x0a, 0x1a, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, - 0x64, 0x5f, 0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, - 0x18, 0x15, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, - 0x78, 0x65, 0x64, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x73, - 0x12, 0x31, 0x0a, 0x14, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, - 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x12, 0x60, 0x0a, 0x14, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, - 0x65, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x17, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2d, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, - 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, - 0x78, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x18, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x72, - 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x19, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x1a, 0x9a, 0x01, 0x0a, 0x0d, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4c, 0x6f, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, - 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, - 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, - 0x1a, 0x4d, 0x0a, 0x1f, 0x50, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, - 0x46, 0x0a, 0x18, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x53, 0x65, - 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x0d, 0x42, 0x79, 0x70, 0x61, 0x73, - 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, - 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x22, 0x28, 0x0a, 0x0e, 0x42, 0x79, 0x70, 0x61, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, 0x6d, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x65, 0x74, 0x6c, 0x61, 0x6e, 0x74, - 0x65, 0x72, 0x6e, 0x2f, 0x66, 0x6c, 0x61, 0x73, 0x68, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x61, - 0x70, 0x69, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x1a, 0xb5, 0x01, 0x0a, 0x0b, 0x57, 0x41, 0x54, 0x45, + 0x52, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x04, 0x77, 0x61, 0x73, 0x6d, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x02, 0x18, 0x01, 0x52, 0x04, 0x77, 0x61, 0x73, 0x6d, 0x12, + 0x1c, 0x0a, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2a, 0x0a, + 0x11, 0x77, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, + 0x61, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x77, 0x61, 0x73, 0x6d, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x41, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x64, 0x6f, 0x77, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0f, + 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x42, + 0x11, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x22, 0x99, 0x0c, 0x0a, 0x13, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x12, + 0x0a, 0x04, 0x61, 0x64, 0x64, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x61, 0x64, + 0x64, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, + 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x74, 0x72, 0x75, 0x73, 0x74, 0x65, 0x64, 0x12, + 0x25, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0d, 0x6d, 0x61, 0x78, 0x50, 0x72, 0x65, 0x63, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x69, 0x61, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x62, 0x69, 0x61, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x70, 0x6c, + 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, + 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, + 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x76, 0x0a, 0x1c, 0x70, + 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, + 0x72, 0x74, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x34, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, 0x6c, + 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x1a, 0x70, 0x6c, 0x75, 0x67, 0x67, 0x61, 0x62, + 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, 0x74, 0x69, + 0x6e, 0x67, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x45, 0x4e, 0x48, 0x54, 0x54, 0x50, 0x55, 0x52, 0x4c, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x45, 0x4e, 0x48, 0x54, 0x54, 0x50, 0x55, 0x52, + 0x4c, 0x12, 0x50, 0x0a, 0x25, 0x54, 0x4c, 0x53, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x5f, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, 0x73, + 0x75, 0x69, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x21, 0x54, 0x4c, 0x53, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x65, 0x64, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x12, 0x4e, 0x0a, 0x24, 0x54, 0x4c, 0x53, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, + 0x5f, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x5f, 0x63, 0x69, 0x70, 0x68, 0x65, 0x72, 0x5f, + 0x73, 0x75, 0x69, 0x74, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x0c, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x20, 0x54, 0x4c, 0x53, 0x4d, 0x6f, 0x62, 0x69, 0x6c, 0x65, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x65, 0x64, 0x43, 0x69, 0x70, 0x68, 0x65, 0x72, 0x53, 0x75, 0x69, 0x74, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x73, 0x12, 0x38, 0x0a, 0x18, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x18, + 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x54, 0x4c, 0x53, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x4e, 0x61, 0x6d, 0x65, 0x49, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3f, 0x0a, + 0x1c, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x5f, 0x63, 0x61, 0x63, 0x68, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x19, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x61, 0x63, 0x68, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x2b, + 0x0a, 0x11, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x6c, 0x6c, + 0x6f, 0x49, 0x44, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, 0x54, 0x4c, 0x53, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x49, 0x44, 0x12, 0x3a, 0x0a, 0x19, 0x54, + 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x5f, 0x73, + 0x70, 0x6c, 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, + 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x70, + 0x6c, 0x69, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x36, 0x0a, 0x17, 0x54, 0x4c, 0x53, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x11, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x54, 0x4c, 0x53, 0x43, 0x6c, 0x69, + 0x65, 0x6e, 0x74, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x3e, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x22, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x50, 0x72, 0x6f, 0x78, 0x79, 0x4c, 0x6f, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x2d, 0x0a, 0x12, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x65, 0x6e, 0x64, + 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x13, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x29, + 0x0a, 0x10, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, 0x61, 0x64, + 0x64, 0x72, 0x18, 0x14, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x78, 0x65, 0x64, 0x41, 0x64, 0x64, 0x72, 0x12, 0x3c, 0x0a, 0x1a, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, 0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, + 0x6c, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x73, 0x18, 0x15, 0x20, 0x01, 0x28, 0x05, 0x52, 0x18, 0x6d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, + 0x61, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x73, 0x12, 0x31, 0x0a, 0x14, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x18, + 0x16, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, + 0x65, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x12, 0x60, 0x0a, 0x14, 0x6d, 0x75, + 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x18, 0x17, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, + 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4d, + 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x78, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x13, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x65, 0x78, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x74, 0x72, 0x61, 0x63, 0x6b, 0x18, 0x18, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x72, 0x61, + 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x18, 0x19, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 0x1a, 0x9a, 0x01, 0x0a, 0x0d, 0x50, + 0x72, 0x6f, 0x78, 0x79, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, + 0x63, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x63, 0x69, 0x74, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x72, 0x79, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x0a, + 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, + 0x08, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6c, 0x6f, 0x6e, + 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x02, 0x52, 0x09, 0x6c, 0x6f, + 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65, 0x1a, 0x4d, 0x0a, 0x1f, 0x50, 0x6c, 0x75, 0x67, 0x67, + 0x61, 0x62, 0x6c, 0x65, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x46, 0x0a, 0x18, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x78, 0x65, 0x64, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, + 0x0a, 0x0d, 0x42, 0x79, 0x70, 0x61, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x2c, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x14, 0x2e, 0x4c, 0x65, 0x67, 0x61, 0x63, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x43, + 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x28, 0x0a, 0x0e, 0x42, 0x79, 0x70, 0x61, 0x73, + 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x61, 0x6e, + 0x64, 0x6f, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x61, 0x6e, 0x64, 0x6f, + 0x6d, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x67, 0x65, 0x74, 0x6c, 0x61, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x2f, 0x66, 0x6c, 0x61, 0x73, 0x68, + 0x6c, 0x69, 0x67, 0x68, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1993,9 +2027,10 @@ var file_apipb_types_proto_goTypes = []interface{}{ (*ProxyConnectConfig_WATERConfig)(nil), // 16: ProxyConnectConfig.WATERConfig (*ProxyConnectConfig_TLSConfig_SessionState)(nil), // 17: ProxyConnectConfig.TLSConfig.SessionState (*LegacyConnectConfig_ProxyLocation)(nil), // 18: LegacyConnectConfig.ProxyLocation - nil, // 19: LegacyConnectConfig.PluggableTransportSettingsEntry - nil, // 20: LegacyConnectConfig.MultiplexedSettingsEntry - (*timestamppb.Timestamp)(nil), // 21: google.protobuf.Timestamp + nil, // 19: LegacyConnectConfig.PluggableTransportSettingsEntry + nil, // 20: LegacyConnectConfig.MultiplexedSettingsEntry + (*timestamp.Timestamp)(nil), // 21: google.protobuf.Timestamp + (*duration.Duration)(nil), // 22: google.protobuf.Duration } var file_apipb_types_proto_depIdxs = []int32{ 6, // 0: ConfigRequest.client_info:type_name -> ConfigRequest.ClientInfo @@ -2016,11 +2051,12 @@ var file_apipb_types_proto_depIdxs = []int32{ 21, // 15: ConfigRequest.Proxy.last_request:type_name -> google.protobuf.Timestamp 2, // 16: ConfigResponse.Proxy.proxies:type_name -> ProxyConnectConfig 17, // 17: ProxyConnectConfig.TLSConfig.session_state:type_name -> ProxyConnectConfig.TLSConfig.SessionState - 18, // [18:18] is the sub-list for method output_type - 18, // [18:18] is the sub-list for method input_type - 18, // [18:18] is the sub-list for extension type_name - 18, // [18:18] is the sub-list for extension extendee - 0, // [0:18] is the sub-list for field type_name + 22, // 18: ProxyConnectConfig.WATERConfig.download_timeout:type_name -> google.protobuf.Duration + 19, // [19:19] is the sub-list for method output_type + 19, // [19:19] is the sub-list for method input_type + 19, // [19:19] is the sub-list for extension type_name + 19, // [19:19] is the sub-list for extension extendee + 0, // [0:19] is the sub-list for field type_name } func init() { file_apipb_types_proto_init() } diff --git a/apipb/types.proto b/apipb/types.proto index 47663e2b6..4d7a78347 100644 --- a/apipb/types.proto +++ b/apipb/types.proto @@ -1,5 +1,6 @@ syntax = "proto3"; import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; option go_package = "github.com/getlantern/flashlight/apipb"; // @@ -134,8 +135,14 @@ message ProxyConnectConfig { } message WATERConfig { - bytes wasm = 1; + // deprecated: use wasm_available_at instead + bytes wasm = 1 [deprecated = true]; string transport = 2; + // wasm_available_at provide a list of URLs that can be used for fetching the WASM module + // with different methods like a direct URL, a magnet link, etc. + repeated string wasm_available_at = 3; + // download_timeout specifies the time the HTTP client should wait to retrieve the WASM binary + google.protobuf.Duration download_timeout = 4; } oneof protocol_config { diff --git a/chained/algeneva_impl.go b/chained/algeneva_impl.go index 0e487218d..f9d0c63a9 100644 --- a/chained/algeneva_impl.go +++ b/chained/algeneva_impl.go @@ -73,6 +73,10 @@ func (a *algenevaImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, er return conn, nil } +func (*algenevaImpl) ready() <-chan error { + return nil +} + // algenevaDialer is a algeneva.Dialer wrapper around a reportDialCore. algeneva accepts an optional // Dialer interface which it will use to dial the server and then wrap the resulting connection. type algenevaDialer struct { diff --git a/chained/broflake_impl.go b/chained/broflake_impl.go index 23ee2f879..031f4f18f 100644 --- a/chained/broflake_impl.go +++ b/chained/broflake_impl.go @@ -55,6 +55,10 @@ func newBroflakeImpl(pc *config.ProxyConfig, reportDialCore reportDialCoreFn) (p }, nil } +func (*broflakeImpl) ready() <-chan error { + return nil +} + func (b *broflakeImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { // TODO: I don't know what to do with 'op' return b.QUICLayer.DialContext(ctx) diff --git a/chained/chained_test.go b/chained/chained_test.go index 9297da6b7..7cb17d7da 100644 --- a/chained/chained_test.go +++ b/chained/chained_test.go @@ -51,6 +51,10 @@ func (impl *testImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, err return impl.d(ctx) } +func (*testImpl) ready() <-chan error { + return nil +} + func newDialer(dialServer func(ctx context.Context) (net.Conn, error)) (func(network, addr string) (net.Conn, error), error) { p, err := newProxy("test", "addr:567", "proto", "netw", &config.ProxyConfig{ AuthToken: "token", diff --git a/chained/http_impl.go b/chained/http_impl.go index b1bd0cf56..7fc13b823 100644 --- a/chained/http_impl.go +++ b/chained/http_impl.go @@ -22,3 +22,7 @@ func newHTTPImpl(addr string, dialCore coreDialer) proxyImpl { func (impl *httpImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { return impl.dialCore(op, ctx, impl.addr) } + +func (*httpImpl) ready() <-chan error { + return nil +} diff --git a/chained/https_impl.go b/chained/https_impl.go index 8c4d1ecca..c3e4d306d 100644 --- a/chained/https_impl.go +++ b/chained/https_impl.go @@ -106,6 +106,10 @@ func (impl *httpsImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, er return result.Conn, nil } +func (*httpsImpl) ready() <-chan error { + return nil +} + func timeoutFor(ctx context.Context) time.Duration { deadline, ok := ctx.Deadline() if ok { diff --git a/chained/multipath.go b/chained/multipath.go index db9e28b6c..bf5499a82 100644 --- a/chained/multipath.go +++ b/chained/multipath.go @@ -49,6 +49,10 @@ func (impl *multipathImpl) FormatStats() []string { return impl.dialer.(multipath.Stats).FormatStats() } +func (*multipathImpl) ready() <-chan error { + return nil +} + func CreateMPDialer(configDir, endpoint string, ss map[string]*config.ProxyConfig, uc common.UserConfig) (dialer.ProxyDialer, error) { if len(ss) < 1 { return nil, errors.New("no dialers") diff --git a/chained/multiplexed_impl.go b/chained/multiplexed_impl.go index 0ca97ac46..46c56dc01 100644 --- a/chained/multiplexed_impl.go +++ b/chained/multiplexed_impl.go @@ -51,6 +51,10 @@ func (impl *multiplexedImpl) dialServer(op *ops.Op, ctx context.Context) (net.Co return impl.multiplexedDial(ctx, "", "") } +func (impl *multiplexedImpl) ready() <-chan error { + return nil +} + // createMultiplexedProtocol configures a cmux multiplexing protocol // according to settings. func createMultiplexedProtocol(s *config.ProxyConfig) (cmux.Protocol, error) { diff --git a/chained/preconnecting_dialer.go b/chained/preconnecting_dialer.go index c51ce9d7a..ca2254b3c 100644 --- a/chained/preconnecting_dialer.go +++ b/chained/preconnecting_dialer.go @@ -73,6 +73,10 @@ func (pd *preconnectingDialer) dialServer(op *ops.Op, ctx context.Context) (conn } } +func (*preconnectingDialer) ready() <-chan error { + return nil +} + func (pd *preconnectingDialer) preconnectIfNecessary(op *ops.Op) { pd.statsMutex.Lock() defer pd.statsMutex.Unlock() diff --git a/chained/proxy.go b/chained/proxy.go index 898dcbcdc..1699f48df 100644 --- a/chained/proxy.go +++ b/chained/proxy.go @@ -67,6 +67,8 @@ type proxyImpl interface { dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) // close releases the resources associated with the implementation, if any. close() + // isReady returns if the implementation is ready to dial + ready() <-chan error } // nopCloser is a mixin to implement a do-nothing close() method of proxyImpl. @@ -584,3 +586,7 @@ func splitClientHello(hello []byte) [][]byte { splits = append(splits, hello[start:]) return splits } + +func (p *proxy) Ready() <-chan error { + return p.impl.ready() +} diff --git a/chained/quic_impl.go b/chained/quic_impl.go index aa83a8071..c598d24eb 100644 --- a/chained/quic_impl.go +++ b/chained/quic_impl.go @@ -71,3 +71,7 @@ func (impl *quicImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, err return conn, err }) } + +func (*quicImpl) ready() <-chan error { + return nil +} diff --git a/chained/shadowsocks_impl.go b/chained/shadowsocks_impl.go index f82854a82..85d132e5c 100644 --- a/chained/shadowsocks_impl.go +++ b/chained/shadowsocks_impl.go @@ -36,6 +36,7 @@ type shadowsocksImpl struct { rng *mrand.Rand rngmx sync.Mutex tlsConfig *tls.Config + nopCloser } type PrefixSaltGen struct { @@ -125,9 +126,6 @@ func newShadowsocksImpl(name, addr string, pc *config.ProxyConfig, reportDialCor }, nil } -func (impl *shadowsocksImpl) close() { -} - func (impl *shadowsocksImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { return impl.reportDialCore(op, func() (net.Conn, error) { conn, err := impl.client.DialStream(ctx, impl.generateUpstream()) @@ -142,6 +140,10 @@ func (impl *shadowsocksImpl) dialServer(op *ops.Op, ctx context.Context) (net.Co }) } +func (*shadowsocksImpl) ready() <-chan error { + return nil +} + // generateUpstream() creates a marker upstream address. This isn't an // acutal upstream that will be dialed, it signals that the upstream // should be determined by other methods. It's just a bit random just to diff --git a/chained/starbridge_impl.go b/chained/starbridge_impl.go index 528e7bb40..2736da74c 100644 --- a/chained/starbridge_impl.go +++ b/chained/starbridge_impl.go @@ -67,6 +67,10 @@ func newStarbridgeImpl(name, addr string, pc *config.ProxyConfig, reportDialCore }, nil } +func (*starbridge) ready() <-chan error { + return nil +} + // Adapted from https://github.com/OperatorFoundation/Starbridge-go/blob/v3.0.12/Starbridge/v3/starbridge.go#L237-L253 func getClientConfig(serverPublicKey string) replicant.ClientConfig { polishClientConfig := polish.DarkStarPolishClientConfig{ diff --git a/chained/tlsmasq_impl.go b/chained/tlsmasq_impl.go index c09647e4e..f23b1c229 100644 --- a/chained/tlsmasq_impl.go +++ b/chained/tlsmasq_impl.go @@ -111,6 +111,10 @@ func newTLSMasqImpl(configDir, name, addr string, pc *config.ProxyConfig, uc com }, nil } +func (*tlsMasqImpl) ready() <-chan error { + return nil +} + func decodeUint16(s string) (uint16, error) { b, err := hex.DecodeString(strings.TrimPrefix(s, "0x")) if err != nil { diff --git a/chained/water_impl.go b/chained/water_impl.go index 11c6dd3b6..df7b8b5ae 100644 --- a/chained/water_impl.go +++ b/chained/water_impl.go @@ -23,85 +23,161 @@ type waterImpl struct { raddr string reportDialCore reportDialCoreFn dialer water.Dialer - wgDownload *sync.WaitGroup - downloadErr error + // readyChannels is a list of channels that are waiting for the dialer to be ready. + readyChannels []chan error + // readyChanLock protects access/modifications to readyChannels, finishedLoading flag and errLoadingWASM. + readyChanLock sync.Locker + finishedLoading bool + errLoadingWASM error nopCloser } -var httpClient *http.Client +var ( + // waterHTTPClient is a shared HTTP client for downloading WebAssembly modules. + waterHTTPClient *http.Client + + // waterWASMLocks is a map of mutexes protecting RW access to the WebAssembly module files for each + // WATER transport. A goroutine must acquire the lock for transport "foo" in order to write the + // foo.wasm file or read the foo.wasm file on disk. + // + // Invariant: entries to this map are never overwritten. + waterWASMLocks = make(map[string]*sync.Mutex) + // wlLock protects access to the waterWASMLocks map. + wlLock = new(sync.Mutex) +) func newWaterImpl(dir, addr string, pc *config.ProxyConfig, reportDialCore reportDialCoreFn) (*waterImpl, error) { ctx := context.Background() - wasmAvailableAt := ptSetting(pc, "water_available_at") + wasmAvailableAt := ptSetting(pc, "wasm_available_at") transport := ptSetting(pc, "water_transport") - wg := new(sync.WaitGroup) d := &waterImpl{ raddr: addr, reportDialCore: reportDialCore, - wgDownload: wg, + readyChannels: make([]chan error, 0), + readyChanLock: new(sync.Mutex), } b64WASM := ptSetting(pc, "water_wasm") if b64WASM != "" { - var err error - wasm, err := base64.StdEncoding.DecodeString(b64WASM) - if err != nil { - return nil, fmt.Errorf("failed to decode water wasm: %w", err) - } + go func() { + wasm, err := base64.StdEncoding.DecodeString(b64WASM) + if err != nil { + d.setErrLoadingWASM(log.Errorf("failed to decode water wasm: %w", err)) + return + } - dialer, err := createDialer(ctx, wasm, transport) - if err != nil { - return nil, log.Errorf("failed to create dialer: %w", err) - } - d.dialer = dialer + d.dialer, err = createDialer(ctx, wasm, transport) + if err != nil { + d.setErrLoadingWASM(log.Errorf("failed to create dialer: %w", err)) + return + } + d.setFinishedLoadingSuccessfully() + }() return d, nil } if wasmAvailableAt != "" { - wg.Add(1) go func() { - defer wg.Done() - log.Debugf("Loading WASM for %q. If not available, it should try to download from the following URLs: %+v. The file should be available here: %s", transport, strings.Split(wasmAvailableAt, ","), dir) - vc := newWaterVersionControl(dir) - cli := httpClient - if cli == nil { - cli = proxied.ChainedThenDirectThenFrontedClient(1*time.Minute, "") - } - downloader, err := newWaterWASMDownloader(strings.Split(wasmAvailableAt, ","), cli) - if err != nil { - d.downloadErr = log.Errorf("failed to create wasm downloader: %w", err) - return - } + log.Debugf("Loading WASM for %q. If not available, it should try to download from the following URLs: %+v. The file should be available at: %q", transport, strings.Split(wasmAvailableAt, ","), dir) - r, err := vc.GetWASM(ctx, transport, downloader) + r, err := d.loadWASM(ctx, transport, dir, wasmAvailableAt) if err != nil { - d.downloadErr = log.Errorf("failed to get wasm: %w", err) + d.setErrLoadingWASM(log.Errorf("failed to read wasm: %w", err)) return } defer r.Close() - b, err := io.ReadAll(r) if err != nil { - d.downloadErr = log.Errorf("failed to read wasm: %w", err) + d.setErrLoadingWASM(log.Errorf("failed to load wasm bytes: %w", err)) return } - if len(b) == 0 { - d.downloadErr = log.Errorf("received empty wasm") - return - } + log.Debugf("received wasm with %d bytes", len(b)) - dialer, err := createDialer(ctx, b, transport) + d.dialer, err = createDialer(ctx, b, transport) if err != nil { - d.downloadErr = log.Errorf("failed to create dialer: %w", err) + d.setErrLoadingWASM(log.Errorf("failed to create dialer: %w", err)) + return } - d.dialer = dialer + d.setFinishedLoadingSuccessfully() }() } return d, nil } +// ready returns a channel that will be closed when the dialer is ready to be used. +// If the dialer is already ready, this will return nil. +// If the dialer failed to load, this will return a channel that will return the error received while loading the WASM file and close immediately. +// If the dialer is still loading, this will return a channel that will be closed when the dialer is ready or failed to load. +func (d *waterImpl) ready() <-chan error { + d.readyChanLock.Lock() + defer d.readyChanLock.Unlock() + + if d.finishedLoading { + return nil + } + + if d.errLoadingWASM != nil { + tempChan := make(chan error, 1) + tempChan <- d.errLoadingWASM + return tempChan + } + + readyChan := make(chan error, 1) + d.readyChannels = append(d.readyChannels, readyChan) + return readyChan +} + +func (d *waterImpl) setFinishedLoadingSuccessfully() { + d.readyChanLock.Lock() + defer d.readyChanLock.Unlock() + d.finishedLoading = true + d.broadcastReadyState(nil) +} + +func (d *waterImpl) setErrLoadingWASM(err error) { + d.readyChanLock.Lock() + defer d.readyChanLock.Unlock() + d.errLoadingWASM = err + d.broadcastReadyState(d.errLoadingWASM) +} + +func (d *waterImpl) broadcastReadyState(err error) { + for _, c := range d.readyChannels { + c <- err + close(c) + } +} + +func (d *waterImpl) loadWASM(ctx context.Context, transport string, dir string, wasmAvailableAt string) (io.ReadCloser, error) { + wlLock.Lock() + m, ok := waterWASMLocks[transport] + if !ok { + m = new(sync.Mutex) + waterWASMLocks[transport] = m + } + wlLock.Unlock() + + m.Lock() + defer m.Unlock() + + vc := newWaterVersionControl(dir) + cli := waterHTTPClient + if cli == nil { + cli = proxied.ChainedThenDirectThenFrontedClient(1*time.Minute, "") + } + downloader, err := newWaterWASMDownloader(strings.Split(wasmAvailableAt, ","), cli) + if err != nil { + return nil, log.Errorf("failed to create wasm downloader: %w", err) + } + r, err := vc.GetWASM(ctx, transport, downloader) + if err != nil { + return nil, log.Errorf("failed to get wasm: %w", err) + } + return r, nil +} + func createDialer(ctx context.Context, wasm []byte, transport string) (water.Dialer, error) { cfg := &water.Config{ TransportModuleBin: wasm, @@ -117,9 +193,19 @@ func createDialer(ctx context.Context, wasm []byte, transport string) (water.Dia func (d *waterImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { return d.reportDialCore(op, func() (net.Conn, error) { - d.wgDownload.Wait() - if d.dialer == nil { - return nil, log.Errorf("dialer not available: %w", d.downloadErr) + // if dialer is not ready, wait until WASM is downloaded or context timeout + isReady := d.ready() + if isReady != nil { + select { + case err := <-isReady: + if err != nil { + return nil, log.Errorf("failed to load dialer: %w", err) + } + + log.Debug("dialer ready!") + case <-ctx.Done(): + return nil, log.Errorf("context completed while waiting for WASM download: %w", ctx.Err()) + } } // TODO: At water 0.7.0 (currently), the library is hanging onto the dial context diff --git a/chained/water_impl_test.go b/chained/water_impl_test.go index 835872541..a8819be24 100644 --- a/chained/water_impl_test.go +++ b/chained/water_impl_test.go @@ -69,11 +69,18 @@ func TestNewWaterImpl(t *testing.T) { assert: func(t *testing.T, actual *waterImpl, err error) { require.NoError(t, err) require.NotNil(t, actual) + readyChan := actual.ready() + assert.NotNil(t, readyChan) + select { + case err, ok := <-readyChan: + assert.True(t, ok) + require.NoError(t, err) + } require.NotNil(t, actual.dialer) assert.NotNil(t, actual.reportDialCore) }, setHTTPClient: func() { - httpClient = &http.Client{ + waterHTTPClient = &http.Client{ Transport: &roundTripFunc{ f: func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -92,8 +99,8 @@ func TestNewWaterImpl(t *testing.T) { raddr: "127.0.0.1", pc: &config.ProxyConfig{ PluggableTransportSettings: map[string]string{ - "water_available_at": "http://example.com/wasm.wasm,http://example2.com/wasm.wasm", - "water_transport": "plain.v1.tinygo.wasm", + "wasm_available_at": "http://example.com/wasm.wasm,http://example2.com/wasm.wasm", + "water_transport": "plain.v1.tinygo.wasm", }, }, reportDialCore: func(op *ops.Op, dialCore func() (net.Conn, error)) (net.Conn, error) { @@ -101,15 +108,21 @@ func TestNewWaterImpl(t *testing.T) { }, }, assert: func(t *testing.T, actual *waterImpl, err error) { + defer actual.close() require.NoError(t, err) require.NotNil(t, actual) - actual.wgDownload.Wait() - assert.NoError(t, actual.downloadErr) + readyChan := actual.ready() + assert.NotNil(t, readyChan) + select { + case err, ok := <-readyChan: + assert.True(t, ok) + require.NoError(t, err) + } assert.NotNil(t, actual.dialer) assert.NotNil(t, actual.reportDialCore) }, setHTTPClient: func() { - httpClient = &http.Client{ + waterHTTPClient = &http.Client{ Transport: &roundTripFunc{ f: func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -145,7 +158,10 @@ func TestWaterDialServer(t *testing.T) { b64WASM := base64.StdEncoding.EncodeToString(wasm) - pc := &config.ProxyConfig{PluggableTransportSettings: map[string]string{"water_wasm": b64WASM}} + pc := &config.ProxyConfig{PluggableTransportSettings: map[string]string{ + "water_wasm": b64WASM, + }} + testOp := ops.Begin("test") defer testOp.End() @@ -180,7 +196,7 @@ func TestWaterDialServer(t *testing.T) { }, givenAddr: "127.0.0.1:8080", setHTTPClient: func() { - httpClient = &http.Client{ + waterHTTPClient = &http.Client{ Transport: &roundTripFunc{ f: func(req *http.Request) (*http.Response, error) { return &http.Response{ @@ -198,6 +214,7 @@ func TestWaterDialServer(t *testing.T) { tt.setHTTPClient() waterImpl, err := newWaterImpl(tt.givenConfigDir, tt.givenAddr, pc, tt.givenReportDialCore) require.NoError(t, err) + defer waterImpl.close() conn, err := waterImpl.dialServer(tt.givenOp, tt.givenCtx) tt.assert(t, conn, err) }) diff --git a/chained/water_version_control.go b/chained/water_version_control.go index 32151baec..342c0a478 100644 --- a/chained/water_version_control.go +++ b/chained/water_version_control.go @@ -31,8 +31,13 @@ func newWaterVersionControl(dir string) *waterVersionControl { // GetWASM returns the WASM file for the given transport // Remember to Close the io.ReadCloser after using it func (vc *waterVersionControl) GetWASM(ctx context.Context, transport string, downloader waterWASMDownloader) (io.ReadCloser, error) { + // This function implements the following steps: + // 1. Check if the WASM file exists + // 2. If it does not exist, download it + // 3. If it exists, check if it was loaded correctly by checking if the last-loaded file existis + // 4. If it was not loaded correctly, download it again + // 5. If it was loaded correctly, return the file and mark the file as loaded path := filepath.Join(vc.dir, transport+".wasm") - var f io.ReadCloser log.Debugf("trying to load file %q", path) f, err := os.Open(path) if err != nil && !os.IsNotExist(err) { @@ -40,17 +45,40 @@ func (vc *waterVersionControl) GetWASM(ctx context.Context, transport string, do } if errors.Is(err, fs.ErrNotExist) || f == nil { - f, err = vc.downloadWASM(ctx, transport, downloader) + if f != nil { + f.Close() + } + response, err := vc.downloadWASM(ctx, transport, downloader) + if err != nil { + return nil, log.Errorf("failed to download WASM file: %w", err) + } + + return response, nil + } + + lastLoaded, err := os.Open(filepath.Join(vc.dir, transport+".last-loaded")) + if err != nil && !os.IsNotExist(err) { + return nil, log.Errorf("failed to open file %s: %w", transport+".last-loaded", err) + } + + if errors.Is(err, fs.ErrNotExist) { + log.Debugf("%q WASM file exists but never loaded correctly, downloading again", transport) + response, err := vc.downloadWASM(ctx, transport, downloader) if err != nil { return nil, log.Errorf("failed to download WASM file: %w", err) } + return response, nil + } + defer lastLoaded.Close() + + _, err = f.Seek(0, 0) + if err != nil { + return nil, log.Errorf("failed to seek file at the beginning: %w", err) } if err = vc.markUsed(transport); err != nil { return nil, log.Errorf("failed to update WASM history: %w", err) } - log.Debugf("WASM file loaded") - return f, nil } @@ -142,5 +170,9 @@ func (vc *waterVersionControl) downloadWASM(ctx context.Context, transport strin return nil, log.Errorf("failed to seek to the beginning of the file: %w", err) } + if err = vc.markUsed(transport); err != nil { + return nil, log.Errorf("failed to update WASM history: %w", err) + } + return f, nil } diff --git a/chained/wss_impl.go b/chained/wss_impl.go index cbae1a4bc..9aaf9586a 100644 --- a/chained/wss_impl.go +++ b/chained/wss_impl.go @@ -63,6 +63,10 @@ func (impl *wssImpl) close() { impl.dialer.Close() } +func (*wssImpl) ready() <-chan error { + return nil +} + func (impl *wssImpl) dialServer(op *ops.Op, ctx context.Context) (net.Conn, error) { return impl.reportDialCore(op, func() (net.Conn, error) { return impl.dialer.DialContext(ctx) diff --git a/client/client_test.go b/client/client_test.go index b2013bac2..01d26c417 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -442,6 +442,10 @@ func (d *testDialer) DialProxy(ctx context.Context) (net.Conn, error) { return &net.TCPConn{}, nil } +func (d *testDialer) Ready() <-chan error { + return nil +} + // Name returns the name for this Dialer func (d *testDialer) Name() string { return d.name diff --git a/dialer/bandit.go b/dialer/bandit.go index fe170760a..5f826c963 100644 --- a/dialer/bandit.go +++ b/dialer/bandit.go @@ -114,6 +114,21 @@ func (o *BanditDialer) chooseDialerForDomain(network, addr string) (ProxyDialer, notAllFailing := hasNotFailing(o.dialers) for i := 0; i < (len(o.dialers) * 2); i++ { d = o.dialers[chosenArm] + readyChan := d.Ready() + if readyChan != nil { + select { + case err := <-readyChan: + if err != nil { + log.Errorf("dialer %q failed to initialize with error %w, chossing different arm", d.Name(), err) + chosenArm = differentArm(chosenArm, len(o.dialers)) + continue + } + default: + log.Debugf("dialer %q is not ready, chossing different arm", d.Name()) + chosenArm = differentArm(chosenArm, len(o.dialers)) + continue + } + } if (d.ConsecFailures() > 0 && notAllFailing) || !d.SupportsAddr(network, addr) { // If the chosen dialer has consecutive failures and there are other // dialers that are succeeding, we should choose a different dialer. diff --git a/dialer/bandit_test.go b/dialer/bandit_test.go index b00e227ef..867fb19ce 100644 --- a/dialer/bandit_test.go +++ b/dialer/bandit_test.go @@ -301,6 +301,10 @@ type tcpConnDialer struct { server net.Conn } +func (*tcpConnDialer) Ready() <-chan error { + return nil +} + // DialProxy implements Dialer. func (t *tcpConnDialer) DialProxy(ctx context.Context) (net.Conn, error) { if t.shouldFail { diff --git a/dialer/dialer.go b/dialer/dialer.go index 2a4643e39..877d99738 100644 --- a/dialer/dialer.go +++ b/dialer/dialer.go @@ -212,6 +212,11 @@ type ProxyDialer interface { // connections created via this dialer. DataRecv() uint64 + // Ready returns a channel which will have a value on it only when initialization + // of the dialer is complete. If initialization failed, the channel will have a non-nil + // error value. If initialization is not required, this will return nil channel value. + Ready() <-chan error + // Stop stops background processing for this Dialer. Stop() diff --git a/dialer/dialer_test.go b/dialer/dialer_test.go index 218262e98..fcd7b3224 100644 --- a/dialer/dialer_test.go +++ b/dialer/dialer_test.go @@ -137,6 +137,10 @@ func (m *mockProxyDialer) DataRecv() uint64 { return 0 } +func (m *mockProxyDialer) Ready() <-chan error { + return nil +} + func (m *mockProxyDialer) Stop() {} func (m *mockProxyDialer) WriteStats(w io.Writer) {} diff --git a/services/bypass.go b/services/bypass.go index f160bd4e1..5165ea847 100644 --- a/services/bypass.go +++ b/services/bypass.go @@ -92,9 +92,6 @@ func (b *bypassService) onProxies( return // bypassService was stopped } - b.mxProxies.Lock() - defer b.mxProxies.Unlock() - // Some pluggable transports don't support bypass, filter these out here. supportedInfos := make(map[string]*commonconfig.ProxyConfig, len(infos)) @@ -105,21 +102,78 @@ func (b *bypassService) onProxies( } dialers := chained.CreateDialersMap(configDir, supportedInfos, userConfig) - for k, v := range supportedInfos { - dialer := dialers[k] + for name, config := range supportedInfos { + dialer := dialers[name] if dialer == nil { - logger.Errorf("bypass: no dialer for %v", k) + logger.Errorf("No dialer for %v", name) continue } - pc := chained.CopyConfig(v) - // Set the name in the info since we know it here. - pc.Name = k - // Kill the cert to avoid it taking up unnecessary space. - pc.Cert = "" - p := newProxy(k, pc, configDir, userConfig, dialer) - b.proxies = append(b.proxies, p) - go p.start(b.done) + readyCh := dialer.Ready() + if readyCh != nil { + go b.loadProxyAsync(name, config, configDir, userConfig, dialer) + continue + } + b.startProxy(name, config, configDir, userConfig, dialer) + } +} + +func (b *bypassService) loadProxyAsync(proxyName string, config *commonconfig.ProxyConfig, configDir string, userConfig common.UserConfig, dialer dialer.ProxyDialer) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + readyChan := make(chan struct{}) + go func() { + dialerReady := dialer.Ready() + if dialerReady == nil { + b.startProxy(proxyName, config, configDir, userConfig, dialer) + readyChan <- struct{}{} + return + } + select { + case err := <-dialerReady: + if err != nil { + logger.Errorf("dialer %q initialization failed: %w", proxyName, err) + cancel() + return + } + b.startProxy(proxyName, config, configDir, userConfig, dialer) + readyChan <- struct{}{} + return + case <-ctx.Done(): + logger.Errorf("proxy %q took to long to start: %w", proxyName, ctx.Err()) + return + } + }() + select { + case <-readyChan: + logger.Debugf("proxy ready!") + case <-ctx.Done(): + logger.Errorf("proxy %q took to long to start: %w", proxyName, ctx.Err()) + } +} + +func (b *bypassService) startProxy(proxyName string, config *commonconfig.ProxyConfig, configDir string, userConfig common.UserConfig, dialer dialer.ProxyDialer) { + b.mxProxies.Lock() + defer b.mxProxies.Unlock() + pc := chained.CopyConfig(config) + // Set the name in the info since we know it here. + pc.Name = proxyName + // Kill the cert to avoid it taking up unnecessary space. + pc.Cert = "" + p := b.newProxy(proxyName, pc, configDir, userConfig, dialer) + b.proxies = append(b.proxies, p) + go p.start(b.done) +} + +func (b *bypassService) newProxy(name string, pc *commonconfig.ProxyConfig, configDir string, userConfig common.UserConfig, dialer dialer.ProxyDialer) *proxy { + return &proxy{ + ProxyConfig: pc, + name: name, + proxyRoundTripper: newProxyRoundTripper(name, pc, userConfig, dialer), + dfRoundTripper: proxied.Fronted("bypass_fronted_roundtrip", 0), + sender: &sender{}, + toggle: atomic.NewBool(mrand.Float32() < 0.5), + userConfig: userConfig, } }