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 {