refactor: remove WASM E2EE proxy from relay server and adjust client timing
- Update default ALPN flag from "h1" to "http/1.1" for standard HTTP/1.1 identifier in relay-server - Remove embedded WASM filesystem, /api/relay-info endpoint, /pkg/ and service worker handlers, and registration script to streamline server and eliminate E2EE proxy features - Increase lease listen worker retry delay from 100ms to 500ms and add delay on stream accept errors in relay client for improved reliability and reduced CPU usage during reconnections
lemon-mint committed
Oct 29, 2025 at 15:16 UTC
e32a07f2097ccc54669e7c92003cfa1c4f0321ee
7 files changed
+19
-487
cmd/relay-server/main.go
+1
-1
@@ -31,7 +31,7 @@ var (
31
func init() {
32
flags := rootCmd.PersistentFlags()
33
flags.StringArrayVar(&flagBootstraps, "bootstraps", nil, "bootstrap addresses")
34
- flags.StringVar(&flagALPN, "alpn", "h1", "ALPN identifier for this service")
34
+ flags.StringVar(&flagALPN, "alpn", "http/1.1", "ALPN identifier for this service")
35
flags.IntVar(&flagPort, "port", 4017, "admin UI and HTTP proxy port")
36
}
37
cmd/relay-server/view.go
-75
@@ -2,11 +2,9 @@ package main
2
3
import (
4
"context"
5
- "embed"
5
"encoding/json"
6
"fmt"
7
"html/template"
9
- "io/fs"
8
"net"
9
"net/http"
10
"net/http/httputil"
@@ -23,11 +21,6 @@ import (
21
"github.com/gosuda/relaydns/sdk"
22
)
23
26
-// Paths are relative to this file's directory
27
-//
28
-//go:embed wasm
29
-var wasmFS embed.FS
30
-
24
// serveHTTP builds the HTTP mux and returns the server.
25
func serveHTTP(ctx context.Context, addr string, serv *relaydns.RelayServer, nodeID string, bootstraps []string, alpn string, cancel context.CancelFunc) *http.Server {
26
if addr == "" {
@@ -193,33 +186,6 @@ func serveHTTP(ctx context.Context, addr string, serv *relaydns.RelayServer, nod
186
}
187
})
188
196
- // API: Get relay server info for E2EE WebSocket
197
- mux.HandleFunc("/api/relay-info", func(w http.ResponseWriter, r *http.Request) {
198
- // Build relay WebSocket URL from current request
199
- scheme := "ws"
200
- if r.TLS != nil {
201
- scheme = "wss"
202
- }
203
- // Check X-Forwarded-Proto header (for reverse proxy)
204
- if proto := r.Header.Get("X-Forwarded-Proto"); proto == "https" {
205
- scheme = "wss"
206
- }
207
-
208
- relayUrl := fmt.Sprintf("%s://%s/relay", scheme, r.Host)
209
-
210
- type relayInfo struct {
211
- RelayUrl string `json:"relayUrl"`
212
- NodeID string `json:"nodeId"`
213
- }
214
- resp := relayInfo{
215
- RelayUrl: relayUrl,
216
- NodeID: nodeID,
217
- }
218
- w.Header().Set("Content-Type", "application/json")
219
- w.Header().Set("Access-Control-Allow-Origin", "*")
220
- _ = json.NewEncoder(w).Encode(resp)
221
- })
222
-
189
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
190
type info struct {
191
Status string `json:"status"`
@@ -229,39 +195,6 @@ func serveHTTP(ctx context.Context, addr string, serv *relaydns.RelayServer, nod
195
_ = json.NewEncoder(w).Encode(resp)
196
})
197
232
- // Serve embedded WASM files
233
- if sub, err := fs.Sub(wasmFS, "wasm"); err != nil {
234
- log.Error().Err(err).Msg("[server] failed to init embedded wasm FS")
235
- } else {
236
- // Serve WASM binaries at /pkg/
237
- mux.Handle("/pkg/", http.StripPrefix("/pkg/", http.FileServer(http.FS(sub))))
238
-
239
- // Serve Service Worker files from embed
240
- mux.HandleFunc("/sw-proxy.js", func(w http.ResponseWriter, r *http.Request) {
241
- w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
242
- w.Header().Set("Service-Worker-Allowed", "/")
243
- data, err := fs.ReadFile(sub, "sw-proxy.js")
244
- if err != nil {
245
- log.Error().Err(err).Msg("[server] failed to read sw-proxy.js")
246
- http.Error(w, "Service Worker not found", http.StatusNotFound)
247
- return
248
- }
249
- w.Write(data)
250
- })
251
-
252
- mux.HandleFunc("/sw.js", func(w http.ResponseWriter, r *http.Request) {
253
- w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
254
- w.Header().Set("Service-Worker-Allowed", "/")
255
- data, err := fs.ReadFile(sub, "sw.js")
256
- if err != nil {
257
- log.Error().Err(err).Msg("[server] failed to read sw.js")
258
- http.Error(w, "Service Worker not found", http.StatusNotFound)
259
- return
260
- }
261
- w.Write(data)
262
- })
263
- }
264
-
198
srv := &http.Server{
199
Addr: addr,
200
Handler: mux,
@@ -456,13 +389,5 @@ var serverTmpl = template.Must(template.New("admin-index").Parse(`<!doctype html
389
{{end}}
390
</main>
391
</div>
459
- <script>
460
- // Register E2EE Proxy Service Worker
461
- if ('serviceWorker' in navigator) {
462
- navigator.serviceWorker.register('/sw-proxy.js')
463
- .then(reg => console.log('[Admin] Service Worker registered:', reg.scope))
464
- .catch(err => console.error('[Admin] Service Worker registration failed:', err));
465
- }
466
- </script>
392
</body>
393
</html>`))
relaydns/client.go
+2
-3
@@ -170,7 +170,7 @@ func (g *RelayClient) leaseListenWorker() {
170
default:
171
if g.sess == nil {
172
// Session not initialized, wait a bit and retry
173
- time.Sleep(100 * time.Millisecond)
173
+ time.Sleep(500 * time.Millisecond)
174
continue
175
}
176
@@ -182,8 +182,7 @@ func (g *RelayClient) leaseListenWorker() {
182
return
183
default:
184
log.Debug().Err(err).Msg("[RelayClient] Error accepting stream, retrying")
185
- // Continue trying to accept streams
186
- continue
185
+ time.Sleep(500 * time.Millisecond) // waiting for reconnection
186
}
187
}
188
log.Debug().Uint32("stream_id", stream.StreamID()).Msg("[RelayClient] Accepted incoming stream")
relaydns/core/cryptoops/sig.go
+2
-1
@@ -10,11 +10,12 @@ import (
10
)
11
12
var _id_magic = []byte("RDVERB_PROTOCOL_VER_01_SHA256_ID")
13
+var _base32_encoding = base32.NewEncoding("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567").WithPadding(base32.NoPadding)
14
15
func DeriveID(publickey ed25519.PublicKey) string {
16
h := hmac.New(sha256.New, _id_magic)
17
h.Write(publickey)
17
- return base32.StdEncoding.EncodeToString(h.Sum(nil))
18
+ return _base32_encoding.EncodeToString(h.Sum(nil))
19
}
20
21
type Credential struct {
relaydns/e2ee_websocket.go
deleted
-402
@@ -1,402 +0,0 @@
1
-package relaydns
2
-
3
-import (
4
- "errors"
5
- "fmt"
6
- "io"
7
- "net/url"
8
-
9
- "github.com/gorilla/websocket"
10
- "github.com/gosuda/relaydns/relaydns/core/cryptoops"
11
- "github.com/gosuda/relaydns/relaydns/utils/wsstream"
12
- "github.com/rs/zerolog/log"
13
-)
14
-
15
-var (
16
- ErrInvalidURL = errors.New("invalid WebSocket URL")
17
- ErrUnsupportedALPN = errors.New("unsupported ALPN protocol")
18
-)
19
-
20
-// SecureWebSocketClient provides E2EE WebSocket connections through RelayDNS
21
-type SecureWebSocketClient struct {
22
- relayClient *RelayClient
23
- credential *cryptoops.Credential
24
-}
25
-
26
-// NewSecureWebSocketClient creates a new E2EE WebSocket client
27
-//
28
-// Parameters:
29
-// - relayConn: Connection to relay server (usually ws://relay-server/relay)
30
-// - credential: Client credential for E2EE authentication
31
-//
32
-// Example:
33
-//
34
-// // Connect to relay server
35
-// relayConn, _, err := websocket.DefaultDialer.Dial("ws://localhost:4017/relay", nil)
36
-// if err != nil {
37
-// log.Fatal(err)
38
-// }
39
-//
40
-// // Create credential
41
-// cred, err := cryptoops.NewRandomCredential()
42
-// if err != nil {
43
-// log.Fatal(err)
44
-// }
45
-//
46
-// // Create E2EE WebSocket client
47
-// client := NewSecureWebSocketClient(relayConn, cred)
48
-func NewSecureWebSocketClient(relayConn io.ReadWriteCloser, credential *cryptoops.Credential) *SecureWebSocketClient {
49
- relayClient := NewRelayClient(relayConn)
50
-
51
- return &SecureWebSocketClient{
52
- relayClient: relayClient,
53
- credential: credential,
54
- }
55
-}
56
-
57
-// Dial establishes an E2EE WebSocket connection to a target peer
58
-//
59
-// The target URL should use the "peer://" scheme:
60
-// - peer://peer-id/path - Connect to peer via E2EE tunnel
61
-//
62
-// Parameters:
63
-// - targetURL: Target WebSocket URL (peer://peer-id/path)
64
-// - alpn: Application-Layer Protocol Negotiation (e.g., "websocket", "http")
65
-//
66
-// Returns:
67
-// - *SecureWebSocketConn: Established E2EE connection
68
-// - error: Error if connection fails
69
-//
70
-// Example:
71
-//
72
-// conn, err := client.Dial("peer://abc123/chat", "websocket")
73
-// if err != nil {
74
-// log.Fatal(err)
75
-// }
76
-// defer conn.Close()
77
-//
78
-// // Send message
79
-// err = conn.WriteMessage(websocket.TextMessage, []byte("Hello!"))
80
-//
81
-// // Receive message
82
-// messageType, data, err := conn.ReadMessage()
83
-func (c *SecureWebSocketClient) Dial(targetURL, alpn string) (*SecureWebSocketConn, error) {
84
- log.Debug().
85
- Str("target_url", targetURL).
86
- Str("alpn", alpn).
87
- Msg("[SecureWebSocket] Dialing E2EE WebSocket")
88
-
89
- // Parse target URL
90
- parsedURL, err := url.Parse(targetURL)
91
- if err != nil {
92
- return nil, fmt.Errorf("%w: %v", ErrInvalidURL, err)
93
- }
94
-
95
- // Extract peer ID from URL
96
- // peer://peer-id/path -> peer-id is the lease ID
97
- peerID := parsedURL.Host
98
- if peerID == "" {
99
- return nil, fmt.Errorf("%w: missing peer ID", ErrInvalidURL)
100
- }
101
-
102
- log.Debug().
103
- Str("peer_id", peerID).
104
- Str("alpn", alpn).
105
- Msg("[SecureWebSocket] Requesting connection to peer")
106
-
107
- // Request connection through relay
108
- code, secConn, err := c.relayClient.RequestConnection(peerID, alpn, c.credential)
109
- if err != nil {
110
- return nil, fmt.Errorf("connection request failed: %w", err)
111
- }
112
-
113
- if secConn == nil {
114
- return nil, fmt.Errorf("connection rejected with code: %v", code)
115
- }
116
-
117
- log.Debug().
118
- Str("peer_id", peerID).
119
- Str("local_id", secConn.LocalID()).
120
- Str("remote_id", secConn.RemoteID()).
121
- Msg("[SecureWebSocket] E2EE connection established")
122
-
123
- return &SecureWebSocketConn{
124
- conn: secConn,
125
- peerID: peerID,
126
- alpn: alpn,
127
- isClosed: false,
128
- }, nil
129
-}
130
-
131
-// RegisterService registers this client as a WebSocket service provider
132
-//
133
-// After registration, other clients can connect to this service using:
134
-// - peer://your-peer-id/path
135
-//
136
-// Parameters:
137
-// - name: Service name (for human readability)
138
-// - alpns: Supported ALPN protocols (e.g., []string{"websocket", "http"})
139
-//
140
-// Returns incoming connections via IncomingConnections() channel
141
-//
142
-// Example:
143
-//
144
-// err := client.RegisterService("chat-server", []string{"websocket"})
145
-// if err != nil {
146
-// log.Fatal(err)
147
-// }
148
-//
149
-// log.Printf("Service registered with ID: %s", client.PeerID())
150
-//
151
-// for conn := range client.IncomingConnections() {
152
-// go handleConnection(conn)
153
-// }
154
-func (c *SecureWebSocketClient) RegisterService(name string, alpns []string) error {
155
- log.Debug().
156
- Str("name", name).
157
- Strs("alpns", alpns).
158
- Msg("[SecureWebSocket] Registering service")
159
-
160
- err := c.relayClient.RegisterLease(c.credential, name, alpns)
161
- if err != nil {
162
- return fmt.Errorf("failed to register service: %w", err)
163
- }
164
-
165
- log.Info().
166
- Str("peer_id", c.credential.ID()).
167
- Str("name", name).
168
- Strs("alpns", alpns).
169
- Msg("[SecureWebSocket] Service registered successfully")
170
-
171
- return nil
172
-}
173
-
174
-// DeregisterService removes this client from the relay server
175
-func (c *SecureWebSocketClient) DeregisterService() error {
176
- log.Debug().Msg("[SecureWebSocket] Deregistering service")
177
-
178
- err := c.relayClient.DeregisterLease(c.credential)
179
- if err != nil {
180
- return fmt.Errorf("failed to deregister service: %w", err)
181
- }
182
-
183
- log.Info().Str("peer_id", c.credential.ID()).Msg("[SecureWebSocket] Service deregistered")
184
- return nil
185
-}
186
-
187
-// IncomingConnections returns a channel that receives incoming E2EE connections
188
-//
189
-// This channel delivers connections from other peers connecting to this service.
190
-// Each connection is already authenticated and encrypted.
191
-//
192
-// Example:
193
-//
194
-// for conn := range client.IncomingConnections() {
195
-// log.Printf("New connection from: %s", conn.RemoteID())
196
-// go handleConnection(conn)
197
-// }
198
-func (c *SecureWebSocketClient) IncomingConnections() <-chan *SecureWebSocketConn {
199
- ch := make(chan *SecureWebSocketConn)
200
-
201
- go func() {
202
- for incomingConn := range c.relayClient.IncommingConnection() {
203
- log.Debug().
204
- Str("lease_id", incomingConn.LeaseID()).
205
- Str("remote_id", incomingConn.RemoteID()).
206
- Msg("[SecureWebSocket] Incoming E2EE connection")
207
-
208
- ch <- &SecureWebSocketConn{
209
- conn: incomingConn.SecureConnection,
210
- peerID: incomingConn.RemoteID(),
211
- alpn: "", // ALPN is already negotiated
212
- isClosed: false,
213
- }
214
- }
215
- close(ch)
216
- }()
217
-
218
- return ch
219
-}
220
-
221
-// PeerID returns this client's peer ID
222
-//
223
-// This is the identifier that other clients use to connect to this service.
224
-// Example: peer://your-peer-id/path
225
-func (c *SecureWebSocketClient) PeerID() string {
226
- return c.credential.ID()
227
-}
228
-
229
-// Close closes the relay connection and stops the client
230
-func (c *SecureWebSocketClient) Close() error {
231
- log.Debug().Msg("[SecureWebSocket] Closing client")
232
- return c.relayClient.Close()
233
-}
234
-
235
-// SecureWebSocketConn represents an E2EE WebSocket connection
236
-type SecureWebSocketConn struct {
237
- conn *cryptoops.SecureConnection
238
- peerID string
239
- alpn string
240
- isClosed bool
241
-}
242
-
243
-// ReadMessage reads the next message from the connection
244
-//
245
-// Compatible with gorilla/websocket ReadMessage interface.
246
-// Always returns BinaryMessage type (E2EE encrypts everything as binary).
247
-//
248
-// Returns:
249
-// - messageType: Always websocket.BinaryMessage for E2EE
250
-// - data: Decrypted message data
251
-// - error: Error if read fails
252
-func (c *SecureWebSocketConn) ReadMessage() (messageType int, data []byte, err error) {
253
- if c.isClosed {
254
- return 0, nil, io.EOF
255
- }
256
-
257
- // Read encrypted data
258
- buf := make([]byte, 65536) // 64KB buffer
259
- n, err := c.conn.Read(buf)
260
- if err != nil {
261
- if err == io.EOF {
262
- c.isClosed = true
263
- }
264
- return 0, nil, err
265
- }
266
-
267
- // Return decrypted data as binary message
268
- return websocket.BinaryMessage, buf[:n], nil
269
-}
270
-
271
-// WriteMessage writes a message to the connection
272
-//
273
-// Compatible with gorilla/websocket WriteMessage interface.
274
-// Message type is ignored as E2EE encrypts everything as binary.
275
-//
276
-// Parameters:
277
-// - messageType: Ignored (can be TextMessage or BinaryMessage)
278
-// - data: Message data to send (will be encrypted)
279
-//
280
-// Returns:
281
-// - error: Error if write fails
282
-func (c *SecureWebSocketConn) WriteMessage(messageType int, data []byte) error {
283
- if c.isClosed {
284
- return io.ErrClosedPipe
285
- }
286
-
287
- // Write encrypted data
288
- _, err := c.conn.Write(data)
289
- return err
290
-}
291
-
292
-// Read implements io.Reader
293
-func (c *SecureWebSocketConn) Read(p []byte) (n int, err error) {
294
- if c.isClosed {
295
- return 0, io.EOF
296
- }
297
- return c.conn.Read(p)
298
-}
299
-
300
-// Write implements io.Writer
301
-func (c *SecureWebSocketConn) Write(p []byte) (n int, err error) {
302
- if c.isClosed {
303
- return 0, io.ErrClosedPipe
304
- }
305
- return c.conn.Write(p)
306
-}
307
-
308
-// Close closes the connection
309
-func (c *SecureWebSocketConn) Close() error {
310
- if c.isClosed {
311
- return nil
312
- }
313
-
314
- c.isClosed = true
315
- return c.conn.Close()
316
-}
317
-
318
-// RemoteID returns the peer's ID (authenticated identity)
319
-func (c *SecureWebSocketConn) RemoteID() string {
320
- return c.conn.RemoteID()
321
-}
322
-
323
-// LocalID returns this client's ID
324
-func (c *SecureWebSocketConn) LocalID() string {
325
- return c.conn.LocalID()
326
-}
327
-
328
-// PeerID returns the target peer ID
329
-func (c *SecureWebSocketConn) PeerID() string {
330
- return c.peerID
331
-}
332
-
333
-// ALPN returns the negotiated application-layer protocol
334
-func (c *SecureWebSocketConn) ALPN() string {
335
- return c.alpn
336
-}
337
-
338
-// DialWebSocketSecure is a convenience function to establish E2EE WebSocket through relay
339
-//
340
-// This is a high-level helper that:
341
-// 1. Connects to relay server
342
-// 2. Creates E2EE client
343
-// 3. Dials target peer
344
-//
345
-// Parameters:
346
-// - relayURL: Relay server WebSocket URL (e.g., "ws://localhost:4017/relay")
347
-// - targetPeerID: Target peer ID to connect to
348
-// - alpn: Application protocol (e.g., "websocket")
349
-// - credential: Client credential (or nil to generate random)
350
-//
351
-// Returns:
352
-// - *SecureWebSocketConn: Established E2EE connection
353
-// - *SecureWebSocketClient: Client instance (keep alive for connection)
354
-// - error: Error if connection fails
355
-//
356
-// Example:
357
-//
358
-// conn, client, err := DialWebSocketSecure(
359
-// "ws://localhost:4017/relay",
360
-// "target-peer-id",
361
-// "websocket",
362
-// nil, // auto-generate credential
363
-// )
364
-// if err != nil {
365
-// log.Fatal(err)
366
-// }
367
-// defer conn.Close()
368
-// defer client.Close()
369
-//
370
-// conn.WriteMessage(websocket.TextMessage, []byte("Hello!"))
371
-func DialWebSocketSecure(relayURL, targetPeerID, alpn string, credential *cryptoops.Credential) (*SecureWebSocketConn, *SecureWebSocketClient, error) {
372
- // Generate credential if not provided
373
- if credential == nil {
374
- var err error
375
- credential, err = cryptoops.NewCredential()
376
- if err != nil {
377
- return nil, nil, fmt.Errorf("failed to generate credential: %w", err)
378
- }
379
- }
380
-
381
- // Connect to relay server
382
- relayConn, _, err := websocket.DefaultDialer.Dial(relayURL, nil)
383
- if err != nil {
384
- return nil, nil, fmt.Errorf("failed to connect to relay: %w", err)
385
- }
386
-
387
- // Wrap WebSocket connection
388
- relayStream := &wsstream.WsStream{Conn: relayConn}
389
-
390
- // Create E2EE client
391
- client := NewSecureWebSocketClient(relayStream, credential)
392
-
393
- // Dial target peer
394
- targetURL := fmt.Sprintf("peer://%s/", targetPeerID)
395
- conn, err := client.Dial(targetURL, alpn)
396
- if err != nil {
397
- client.Close()
398
- return nil, nil, fmt.Errorf("failed to dial peer: %w", err)
399
- }
400
-
401
- return conn, client, nil
402
-}
relaydns/utils/wsstream/wsstream.go
+9
@@ -2,6 +2,7 @@ package wsstream
2
3
import (
4
"io"
5
+ "strings"
6
7
"github.com/gorilla/websocket"
8
)
@@ -26,14 +27,22 @@ func (g *WsStream) Read(p []byte) (n int, err error) {
27
err = nil
28
}
29
30
+ if err != nil && strings.HasPrefix(err.Error(), "websocket: close ") {
31
+ return 0, io.EOF
32
+ }
33
+
34
return n, err
35
}
36
37
func (g *WsStream) Write(p []byte) (n int, err error) {
38
err = g.Conn.WriteMessage(websocket.BinaryMessage, p)
39
if err != nil {
40
+ if strings.HasPrefix(err.Error(), "websocket: close ") {
41
+ return 0, io.EOF
42
+ }
43
return 0, err
44
}
45
+
46
return len(p), nil
47
}
48
sdk/sdk_e2e_test.go
+5
-5
@@ -102,7 +102,7 @@ func TestE2E_ClientToAppThroughRelay(t *testing.T) {
102
103
// 5. Register listener on app side
104
log.Info().Msg("[TEST] Step 5: Registering app listener")
105
- appListener, err := appClient.Listen(appCred, "test-app", []string{"h1"})
105
+ appListener, err := appClient.Listen(appCred, "test-app", []string{"http/1.1"})
106
if err != nil {
107
t.Fatalf("Failed to create app listener: %v", err)
108
}
@@ -155,7 +155,7 @@ func TestE2E_ClientToAppThroughRelay(t *testing.T) {
155
156
// 10. Dial to app through relay
157
log.Info().Msg("[TEST] Step 10: Client dialing to app through relay")
158
- conn, err := clientSDK.Dial(clientCred, appCred.ID(), "h1")
158
+ conn, err := clientSDK.Dial(clientCred, appCred.ID(), "http/1.1")
159
if err != nil {
160
t.Fatalf("Failed to dial to app: %v", err)
161
}
@@ -276,7 +276,7 @@ func TestE2E_MultipleConnections(t *testing.T) {
276
}
277
defer appClient.Close()
278
279
- appListener, err := appClient.Listen(appCred, "multi-test-app", []string{"h1"})
279
+ appListener, err := appClient.Listen(appCred, "multi-test-app", []string{"http/1.1"})
280
if err != nil {
281
t.Fatalf("Failed to create app listener: %v", err)
282
}
@@ -320,7 +320,7 @@ func TestE2E_MultipleConnections(t *testing.T) {
320
go func() {
321
log.Debug().Int("conn_num", i).Msg("[TEST] Starting connection")
322
323
- conn, err := clientSDK.Dial(clientCred, appCred.ID(), "h1")
323
+ conn, err := clientSDK.Dial(clientCred, appCred.ID(), "http/1.1")
324
if err != nil {
325
t.Errorf("Connection %d failed to dial: %v", i, err)
326
return
@@ -432,7 +432,7 @@ func TestE2E_ConnectionTimeout(t *testing.T) {
432
433
// Try to dial to non-existent lease
434
log.Info().Msg("[TEST] Attempting to dial non-existent lease")
435
- _, err = clientSDK.Dial(clientCred, "non-existent-lease-id", "h1")
435
+ _, err = clientSDK.Dial(clientCred, "non-existent-lease-id", "http/1.1")
436
if err == nil {
437
t.Error("Expected error when dialing non-existent lease")
438
} else {