diff options
Diffstat (limited to 'relay/websocket')
| -rw-r--r-- | relay/websocket/websocket.go | 75 |
1 files changed, 75 insertions, 0 deletions
diff --git a/relay/websocket/websocket.go b/relay/websocket/websocket.go index cfc3289..2ae0dec 100644 --- a/relay/websocket/websocket.go +++ b/relay/websocket/websocket.go | |||
| @@ -7,12 +7,14 @@ import ( | |||
| 7 | "context" | 7 | "context" |
| 8 | "crypto/rand" | 8 | "crypto/rand" |
| 9 | "crypto/sha1" | 9 | "crypto/sha1" |
| 10 | "crypto/tls" | ||
| 10 | "encoding/base64" | 11 | "encoding/base64" |
| 11 | "encoding/binary" | 12 | "encoding/binary" |
| 12 | "fmt" | 13 | "fmt" |
| 13 | "io" | 14 | "io" |
| 14 | "net" | 15 | "net" |
| 15 | "net/http" | 16 | "net/http" |
| 17 | "net/url" | ||
| 16 | "strings" | 18 | "strings" |
| 17 | "sync" | 19 | "sync" |
| 18 | "time" | 20 | "time" |
| @@ -207,6 +209,79 @@ func acceptKey(key string) string { | |||
| 207 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) | 209 | return base64.StdEncoding.EncodeToString(h.Sum(nil)) |
| 208 | } | 210 | } |
| 209 | 211 | ||
| 212 | // Dial connects to a WebSocket server at rawURL and performs the client-side | ||
| 213 | // RFC 6455 handshake. Supports ws:// and wss:// schemes. | ||
| 214 | func Dial(rawURL string) (*Conn, error) { | ||
| 215 | u, err := url.Parse(rawURL) | ||
| 216 | if err != nil { | ||
| 217 | return nil, fmt.Errorf("websocket: parse url: %w", err) | ||
| 218 | } | ||
| 219 | |||
| 220 | host := u.Host | ||
| 221 | var netConn net.Conn | ||
| 222 | switch u.Scheme { | ||
| 223 | case "ws": | ||
| 224 | if !strings.Contains(host, ":") { | ||
| 225 | host += ":80" | ||
| 226 | } | ||
| 227 | netConn, err = net.Dial("tcp", host) | ||
| 228 | case "wss": | ||
| 229 | if !strings.Contains(host, ":") { | ||
| 230 | host += ":443" | ||
| 231 | } | ||
| 232 | netConn, err = tls.Dial("tcp", host, &tls.Config{ServerName: u.Hostname()}) | ||
| 233 | default: | ||
| 234 | return nil, fmt.Errorf("websocket: unsupported scheme %q (use ws:// or wss://)", u.Scheme) | ||
| 235 | } | ||
| 236 | if err != nil { | ||
| 237 | return nil, fmt.Errorf("websocket: dial %s: %w", host, err) | ||
| 238 | } | ||
| 239 | |||
| 240 | // Generate a random 16-byte key and base64-encode it. | ||
| 241 | var keyBytes [16]byte | ||
| 242 | if _, err := rand.Read(keyBytes[:]); err != nil { | ||
| 243 | netConn.Close() | ||
| 244 | return nil, fmt.Errorf("websocket: generate key: %w", err) | ||
| 245 | } | ||
| 246 | key := base64.StdEncoding.EncodeToString(keyBytes[:]) | ||
| 247 | |||
| 248 | path := u.RequestURI() | ||
| 249 | if path == "" { | ||
| 250 | path = "/" | ||
| 251 | } | ||
| 252 | |||
| 253 | req := "GET " + path + " HTTP/1.1\r\n" + | ||
| 254 | "Host: " + u.Host + "\r\n" + | ||
| 255 | "Upgrade: websocket\r\n" + | ||
| 256 | "Connection: Upgrade\r\n" + | ||
| 257 | "Sec-WebSocket-Key: " + key + "\r\n" + | ||
| 258 | "Sec-WebSocket-Version: 13\r\n\r\n" | ||
| 259 | |||
| 260 | if _, err := netConn.Write([]byte(req)); err != nil { | ||
| 261 | netConn.Close() | ||
| 262 | return nil, fmt.Errorf("websocket: send handshake: %w", err) | ||
| 263 | } | ||
| 264 | |||
| 265 | br := bufio.NewReader(netConn) | ||
| 266 | resp, err := http.ReadResponse(br, nil) | ||
| 267 | if err != nil { | ||
| 268 | netConn.Close() | ||
| 269 | return nil, fmt.Errorf("websocket: read handshake response: %w", err) | ||
| 270 | } | ||
| 271 | resp.Body.Close() | ||
| 272 | |||
| 273 | if resp.StatusCode != 101 { | ||
| 274 | netConn.Close() | ||
| 275 | return nil, fmt.Errorf("websocket: server returned status %d, want 101", resp.StatusCode) | ||
| 276 | } | ||
| 277 | if resp.Header.Get("Sec-WebSocket-Accept") != acceptKey(key) { | ||
| 278 | netConn.Close() | ||
| 279 | return nil, fmt.Errorf("websocket: bad Sec-WebSocket-Accept header") | ||
| 280 | } | ||
| 281 | |||
| 282 | return &Conn{rwc: netConn, br: br, client: true}, nil | ||
| 283 | } | ||
| 284 | |||
| 210 | // Accept performs the server-side WebSocket handshake, hijacking the HTTP | 285 | // Accept performs the server-side WebSocket handshake, hijacking the HTTP |
| 211 | // connection and returning a Conn ready for framed I/O. | 286 | // connection and returning a Conn ready for framed I/O. |
| 212 | func Accept(w http.ResponseWriter, r *http.Request) (*Conn, error) { | 287 | func Accept(w http.ResponseWriter, r *http.Request) (*Conn, error) { |
