release: 1.4.4 — channel «Облако» (WebSocket via KeenDNS cloud to own Keenetic): wsgw gateway, Android 1.4.4, iOS, docs/cloud.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
147
wsgw/main.go
Normal file
147
wsgw/main.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// wsgw: WebSocket -> UDP gateway for the PCA Phobos "Keenetic cloud" transport.
|
||||
// Keenetic `ip http proxy` (KeenDNS cloud, TLS on the router) forwards wss://<sub>/ here as plain ws.
|
||||
// One binary WS message = one obfuscated UDP packet; each WS connection gets its own UDP socket to -t.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const wsGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
|
||||
func main() {
|
||||
listen := flag.String("l", "0.0.0.0:18090", "listen addr (http)")
|
||||
target := flag.String("t", "", "UDP target host:port (server obfuscator)")
|
||||
flag.Parse()
|
||||
if *target == "" {
|
||||
log.Fatal("-t host:port required")
|
||||
}
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.EqualFold(r.Header.Get("Upgrade"), "websocket") {
|
||||
http.Error(w, "ok", http.StatusUpgradeRequired)
|
||||
return
|
||||
}
|
||||
h, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
conn, rw, err := h.Hijack()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sum := sha1.Sum([]byte(r.Header.Get("Sec-WebSocket-Key") + wsGUID))
|
||||
rw.WriteString("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " +
|
||||
base64.StdEncoding.EncodeToString(sum[:]) + "\r\n\r\n")
|
||||
rw.Flush()
|
||||
go serve(conn, rw.Reader, *target)
|
||||
})
|
||||
log.Printf("wsgw %s -> udp %s", *listen, *target)
|
||||
log.Fatal(http.ListenAndServe(*listen, nil))
|
||||
}
|
||||
|
||||
func serve(c net.Conn, br *bufio.Reader, target string) {
|
||||
defer c.Close()
|
||||
u, err := net.Dial("udp", target)
|
||||
if err != nil {
|
||||
log.Printf("udp %s: %v", target, err)
|
||||
return
|
||||
}
|
||||
defer u.Close()
|
||||
go func() { // udp -> ws
|
||||
buf := make([]byte, 65535)
|
||||
for {
|
||||
u.SetReadDeadline(time.Now().Add(5 * time.Minute))
|
||||
n, err := u.Read(buf)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return
|
||||
}
|
||||
if writeFrame(c, 2, buf[:n]) != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
for { // ws -> udp
|
||||
op, p, err := readFrame(br)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
switch op {
|
||||
case 2, 0:
|
||||
u.Write(p)
|
||||
case 8:
|
||||
return
|
||||
case 9:
|
||||
writeFrame(c, 10, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// readFrame reads one client frame (masked). Fragmented messages are not used by our clients.
|
||||
func readFrame(r *bufio.Reader) (byte, []byte, error) {
|
||||
var h [2]byte
|
||||
if _, err := io.ReadFull(r, h[:]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
n := uint64(h[1] & 0x7f)
|
||||
switch n {
|
||||
case 126:
|
||||
var e [2]byte
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
n = uint64(binary.BigEndian.Uint16(e[:]))
|
||||
case 127:
|
||||
var e [8]byte
|
||||
if _, err := io.ReadFull(r, e[:]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
n = binary.BigEndian.Uint64(e[:])
|
||||
}
|
||||
if n > 65535 {
|
||||
return 0, nil, io.ErrUnexpectedEOF
|
||||
}
|
||||
var mask [4]byte
|
||||
masked := h[1]&0x80 != 0
|
||||
if masked {
|
||||
if _, err := io.ReadFull(r, mask[:]); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
}
|
||||
p := make([]byte, n)
|
||||
if _, err := io.ReadFull(r, p); err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
if masked {
|
||||
for i := range p {
|
||||
p[i] ^= mask[i&3]
|
||||
}
|
||||
}
|
||||
return h[0] & 0x0f, p, nil
|
||||
}
|
||||
|
||||
func writeFrame(w io.Writer, op byte, p []byte) error {
|
||||
hdr := []byte{0x80 | op, 0}
|
||||
switch n := len(p); {
|
||||
case n < 126:
|
||||
hdr[1] = byte(n)
|
||||
case n < 65536:
|
||||
hdr[1] = 126
|
||||
hdr = binary.BigEndian.AppendUint16(hdr, uint16(n))
|
||||
default:
|
||||
hdr[1] = 127
|
||||
hdr = binary.BigEndian.AppendUint64(hdr, uint64(n))
|
||||
}
|
||||
_, err := w.Write(append(hdr, p...))
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user