Mult Protocol
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dragontcp/internal/xorchunk"
|
||||
)
|
||||
|
||||
// DragonTCP speaks two wires that are not interchangeable:
|
||||
//
|
||||
// b — compact binary records (29/5-byte headers, SHA-256 keystream mask)
|
||||
// x — legacy UP/OK framing with XOR 0xAD over ASCII chunk commands
|
||||
//
|
||||
// Networks differ in which they pass, so the client can be pinned to either or
|
||||
// left on auto, which decides by actually fetching a URL through each wire and
|
||||
// keeping the first that answers.
|
||||
const (
|
||||
WireBinary = "b"
|
||||
WireXOR = "x"
|
||||
WireAuto = "auto"
|
||||
)
|
||||
|
||||
// probeTarget is fetched through a candidate wire to decide whether it works.
|
||||
// A plain HTTP host is used deliberately: it exercises OPEN, upload and
|
||||
// download in one go, and a valid status line proves bytes survived intact.
|
||||
const (
|
||||
probeHost = "ip.dr2.site"
|
||||
probePort = 80
|
||||
probeTimeout = 8 * time.Second
|
||||
)
|
||||
|
||||
type wireSelector struct {
|
||||
mu sync.Mutex
|
||||
configured string // b, x or auto
|
||||
resolved string // b or x once decided
|
||||
serverAddr string
|
||||
token string
|
||||
binOpts chunkClientOptions
|
||||
xorOpts xorchunk.Options
|
||||
}
|
||||
|
||||
func newWireSelector(configured, serverAddr, token string, binOpts chunkClientOptions, xorOpts xorchunk.Options) *wireSelector {
|
||||
s := &wireSelector{
|
||||
configured: configured,
|
||||
serverAddr: serverAddr,
|
||||
token: token,
|
||||
binOpts: binOpts,
|
||||
xorOpts: xorOpts,
|
||||
}
|
||||
if configured != WireAuto {
|
||||
s.resolved = configured
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// dial opens a tunnel over the active wire, resolving the wire first if needed.
|
||||
func (s *wireSelector) dial(host string, port int) (net.Conn, error) {
|
||||
mode := s.mode()
|
||||
if mode == WireXOR {
|
||||
return xorchunk.Open(s.serverAddr, s.token, host, port, s.xorOpts)
|
||||
}
|
||||
return openChunkTunnel(s.serverAddr, s.token, host, port, s.binOpts)
|
||||
}
|
||||
|
||||
// mode returns the wire to use, running detection once if configured as auto.
|
||||
// Detection failure is not cached, so a client that starts before the network
|
||||
// is usable retries on the next connection instead of latching a bad guess.
|
||||
func (s *wireSelector) mode() string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.resolved != "" {
|
||||
return s.resolved
|
||||
}
|
||||
if picked, ok := s.detectLocked(); ok {
|
||||
s.resolved = picked
|
||||
return picked
|
||||
}
|
||||
// Undecided: use the binary wire for this attempt without caching it.
|
||||
return WireBinary
|
||||
}
|
||||
|
||||
func (s *wireSelector) detectLocked() (string, bool) {
|
||||
for _, candidate := range []string{WireBinary, WireXOR} {
|
||||
if s.probe(candidate) {
|
||||
fmt.Printf("wire probe: %s selected via %s\n", candidate, probeHost)
|
||||
return candidate, true
|
||||
}
|
||||
fmt.Printf("wire probe: %s failed\n", candidate)
|
||||
}
|
||||
fmt.Printf("wire probe: neither wire reached %s; retrying later\n", probeHost)
|
||||
return "", false
|
||||
}
|
||||
|
||||
// probe fetches probeHost through one wire and reports whether a well-formed
|
||||
// HTTP status line came back.
|
||||
func (s *wireSelector) probe(mode string) bool {
|
||||
type result struct{ ok bool }
|
||||
done := make(chan result, 1)
|
||||
|
||||
go func() {
|
||||
var (
|
||||
conn net.Conn
|
||||
err error
|
||||
)
|
||||
if mode == WireXOR {
|
||||
conn, err = xorchunk.Open(s.serverAddr, s.token, probeHost, probePort, s.xorOpts)
|
||||
} else {
|
||||
conn, err = openChunkTunnel(s.serverAddr, s.token, probeHost, probePort, s.binOpts)
|
||||
}
|
||||
if err != nil {
|
||||
done <- result{false}
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
request := "GET / HTTP/1.1\r\nHost: " + probeHost + "\r\nUser-Agent: dragontcp\r\nConnection: close\r\n\r\n"
|
||||
if _, err := conn.Write([]byte(request)); err != nil {
|
||||
done <- result{false}
|
||||
return
|
||||
}
|
||||
buf := make([]byte, 64)
|
||||
n, err := conn.Read(buf)
|
||||
if n <= 0 || (err != nil && n == 0) {
|
||||
done <- result{false}
|
||||
return
|
||||
}
|
||||
done <- result{strings.HasPrefix(string(buf[:n]), "HTTP/")}
|
||||
}()
|
||||
|
||||
select {
|
||||
case r := <-done:
|
||||
return r.ok
|
||||
case <-time.After(probeTimeout):
|
||||
// The tunnel goroutine is left to unwind on its own; the wire simply
|
||||
// did not answer in time, which is all the caller needs to know.
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user