From 97f11626206c489e2a8dadc7efa3336030388ec8f00d6050133e02b38f7848c5 Mon Sep 17 00:00:00 2001 From: Sergey Matveev Date: Thu, 11 Apr 2024 18:39:26 +0300 Subject: [PATCH] Noising --- cmd/client/main.go | 217 +++++++++++++---------- cmd/keygen/main.go | 74 ++------ cmd/server/main.go | 410 +++++++++++++++++++++++++------------------- cmd/server/peer.go | 70 ++++++++ cmd/server/stats.go | 2 +- cmd/server/x509.go | 57 ------ doc/index.texi | 2 - doc/install.texi | 3 +- doc/proto.texi | 69 ++++---- doc/usage.texi | 53 +++--- doc/vad.texi | 2 + go.mod | 1 + go.sum | 15 ++ internal/audio.go | 9 + internal/cookie.go | 11 ++ internal/noise.go | 36 ++++ internal/resolve.go | 31 ++++ internal/var.go | 9 +- internal/x509.go | 13 -- 19 files changed, 616 insertions(+), 468 deletions(-) create mode 100644 cmd/server/peer.go delete mode 100644 cmd/server/x509.go create mode 100644 internal/audio.go create mode 100644 internal/cookie.go create mode 100644 internal/noise.go create mode 100644 internal/resolve.go delete mode 100644 internal/x509.go diff --git a/cmd/client/main.go b/cmd/client/main.go index bad619f..bd7fba6 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -16,26 +16,21 @@ package main import ( - "bufio" "bytes" "crypto/subtle" - "crypto/tls" - "crypto/x509" "encoding/binary" "encoding/hex" - "errors" "flag" - "fmt" "io" "log" "net" - "net/netip" "os" "os/exec" "strconv" "strings" "time" + "github.com/flynn/noise" "github.com/jroimartin/gocui" vors "go.stargrave.org/vors/internal" "golang.org/x/crypto/blake2s" @@ -88,20 +83,26 @@ func incr(data []byte) { panic("overflow") } +const soxParams = "--no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -" + func main() { + srvAddr := flag.String("srv", "vors.home.arpa:"+strconv.Itoa(vors.DefaultPort), + "Host:TCP/UDP port to connect to") + srvPubHex := flag.String("pub", "", "Server's public key, hex") + recCmd := flag.String("rec", "rec "+soxParams, "rec command") + playCmd := flag.String("play", "play "+soxParams, "play command") vadRaw := flag.Uint("vad", 0, "VAD threshold") - hostport := flag.String("srv", "[::1]:12345", "TCP/UDP port to connect to") - spkiHash := flag.String("spki", "FILL-ME", "SHA256 hash of server's certificate SPKI") - passwd := flag.String("passwd", "", "Password") - recCmd := flag.String("rec", "rec --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "rec command") - playCmd := flag.String("play", "play --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "play command") flag.Parse() log.SetFlags(log.Lmicroseconds | log.Lshortfile) + srvPub, err := hex.DecodeString(*srvPubHex) + if err != nil { + log.Fatal(err) + } + vad := uint64(*vadRaw) opusEnc := newOpusEnc() var mic io.ReadCloser - var err error if *recCmd != "" { cmd := makeCmd(*recCmd) mic, err = cmd.StdoutPipe() @@ -114,84 +115,122 @@ func main() { } } - addrTCP, err := net.ResolveTCPAddr("tcp", *hostport) + ctrl, err := net.DialTCP("tcp", nil, vors.MustResolveTCP(*srvAddr)) if err != nil { - log.Fatal(err) + log.Fatalln("dial server:", err) } - addrUDP, err := net.ResolveUDPAddr("udp", *hostport) + defer ctrl.Close() + if err = ctrl.SetNoDelay(true); err != nil { + log.Fatalln("nodelay:", err) + } + if _, err = io.Copy(ctrl, strings.NewReader(vors.NoisePrologue)); err != nil { + log.Fatalln("handshake: write prologue", err) + return + } + + hs, err := noise.NewHandshakeState(noise.Config{ + CipherSuite: vors.NoiseCipherSuite, + Pattern: noise.HandshakeNK, + Initiator: true, + PeerStatic: srvPub, + Prologue: []byte(vors.NoisePrologue), + }) if err != nil { - log.Fatal(err) + log.Fatalln("noise.NewHandshakeState:", err) } - ctrlRaw, err := net.DialTCP("tcp", nil, addrTCP) + buf, _, _, err := hs.WriteMessage(nil, []byte(*Name)) if err != nil { - log.Fatalln("dial server:", err) + log.Fatalln("handshake encrypt:", err) } - defer ctrlRaw.Close() - ourAddr := net.UDPAddrFromAddrPort( - netip.MustParseAddrPort(ctrlRaw.LocalAddr().String())) - ln, err := net.ListenUDP("udp", ourAddr) + if err = vors.PktWrite(ctrl, buf); err != nil { + log.Fatalln("write handshake:", err) + return + } + buf, err = vors.PktRead(ctrl) if err != nil { - log.Fatal(err) + log.Fatalln("read handshake:", err) } - ctrl := tls.Client(ctrlRaw, &tls.Config{ - MinVersion: tls.VersionTLS13, - CurvePreferences: []tls.CurveID{tls.X25519}, - ServerName: vors.CN, - InsecureSkipVerify: true, - VerifyPeerCertificate: func( - rawCerts [][]byte, verifiedChains [][]*x509.Certificate, - ) error { - cer, err := x509.ParseCertificate(rawCerts[0]) + buf, txCS, rxCS, err := hs.ReadMessage(nil, buf) + if err != nil { + log.Fatalln("handshake decrypt:", err) + } + + rx := make(chan []byte) + go func() { + for { + buf, err := vors.PktRead(ctrl) if err != nil { - return err + log.Fatalln("rx", err) } - if *spkiHash != vors.SPKIHash(cer) { - return errors.New("server certificate's SPKI hash mismatch") + buf, err = rxCS.Decrypt(buf[:0], nil, buf) + if err != nil { + log.Fatalln("rx decrypt", err) } - return nil - }, - }) - err = ctrl.Handshake() - if err != nil { - log.Println("TLS handshake:", err) - return - } - defer ctrl.Close() + rx <- buf + } + }() - scanner := bufio.NewScanner(ctrl) - if !scanner.Scan() { - log.Println("read challenge:", scanner.Err()) - return + srvAddrUDP := vors.MustResolveUDP(*srvAddr) + conn, err := net.DialUDP("udp", nil, srvAddrUDP) + if err != nil { + log.Fatalln("connect:", err) } + var sid byte { - h, err := blake2s.New256([]byte(*passwd)) + cols := strings.Fields(string(buf)) + if cols[0] != "OK" || len(cols) != 2 { + log.Fatalln("handshake failed:", cols) + } + var cookie vors.Cookie + cookieRaw, err := hex.DecodeString(cols[1]) if err != nil { log.Fatal(err) } - h.Write(scanner.Bytes()) - if _, err = io.Copy(ctrl, strings.NewReader(fmt.Sprintf( - "%s %s\n", hex.EncodeToString(h.Sum(nil)), *Name))); err != nil { - log.Println("write password:", err) - return + copy(cookie[:], cookieRaw) + timeout := time.NewTimer(vors.PingTime) + defer func() { + if !timeout.Stop() { + <-timeout.C + } + }() + ticker := time.NewTicker(time.Second) + if _, err = conn.Write(cookie[:]); err != nil { + log.Fatalln("write:", err) + } + WaitForCookieAcceptance: + for { + select { + case <-timeout.C: + log.Fatalln("cookie acceptance timeout") + case <-ticker.C: + if _, err = conn.Write(cookie[:]); err != nil { + log.Fatalln("write:", err) + } + case buf = <-rx: + cols = strings.Fields(string(buf)) + if cols[0] != "SID" || len(cols) != 2 { + log.Fatalln("cookie acceptance failed:", string(buf)) + } + sid = parseSID(cols[1]) + Streams[sid] = &Stream{name: *Name, stats: OurStats} + break WaitForCookieAcceptance + } + } + if !timeout.Stop() { + <-timeout.C } } - if !scanner.Scan() { - log.Println("auth", scanner.Err()) - return - } - cols := strings.Fields(scanner.Text()) - if cols[0] != "OK" { - log.Println("auth failed:", scanner.Text()) - return - } - sid := parseSID(cols[1]) - Streams[sid] = &Stream{name: *Name, stats: OurStats} - tlsState := ctrl.ConnectionState() - keyOur, err := tlsState.ExportKeyingMaterial(cols[1], nil, chacha20.KeySize) - if err != nil { - log.Fatal(err) + var keyOur []byte + { + h, err := blake2s.New256(hs.ChannelBinding()) + if err != nil { + log.Fatalln(err) + } + h.Write([]byte(vors.NoisePrologue)) + keyOur = h.Sum(nil) } + seen := time.Now() LoggerReady := make(chan struct{}) @@ -217,7 +256,8 @@ func main() { log.Fatal(err) } log.SetOutput(v) - log.Println("connected") + log.Println("connected", "sid:", sid, + "addr:", conn.LocalAddr().String()) close(LoggerReady) for { time.Sleep(vors.ScreenRefresh) @@ -235,32 +275,31 @@ func main() { }() go func() { - var err error for { time.Sleep(vors.PingTime) - if _, err = ctrl.Write([]byte(vors.CmdPing + "\n")); err != nil { - log.Println("ping:", err) - Finish <- struct{}{} - break + buf, err := txCS.Encrypt(nil, nil, []byte(vors.CmdPing)) + if err != nil { + log.Fatalln("tx encrypt:", err) + } + if err = vors.PktWrite(ctrl, buf); err != nil { + log.Fatalln("tx:", err) } } }() go func(seen *time.Time) { - var t string var now time.Time - for scanner.Scan() { - t = scanner.Text() - if t == vors.CmdPong { + for buf := range rx { + if string(buf) == vors.CmdPong { now = time.Now() *seen = now continue } - cols := strings.Fields(t) + cols := strings.Fields(string(buf)) switch cols[0] { case vors.CmdAdd: sidRaw, name, keyHex := cols[1], cols[2], cols[3] - log.Println("add", name) + log.Println("add", name, "sid:", sidRaw) sid := parseSID(sidRaw) key, err := hex.DecodeString(keyHex) if err != nil { @@ -388,18 +427,14 @@ func main() { log.Println("unknown sid:", sid) continue } + log.Println("del", s.name, "sid:", cols[1]) delete(Streams, sid) close(s.in) close(s.stats.dead) - log.Println("del", s.name) default: log.Fatal("unknown cmd:", cols[0]) } } - if scanner.Err() != nil { - log.Print("scanner:", err) - Finish <- struct{}{} - } }(&seen) go func(seen *time.Time) { @@ -421,13 +456,13 @@ func main() { var ctr uint32 for { buf := make([]byte, 2*vors.FrameLen) - n, from, err = ln.ReadFromUDP(buf) + n, from, err = conn.ReadFromUDP(buf) if err != nil { log.Println("recvfrom:", err) Finish <- struct{}{} break } - if from.Port != addrUDP.Port || !from.IP.Equal(addrUDP.IP) { + if from.Port != srvAddrUDP.Port || !from.IP.Equal(srvAddrUDP.IP) { log.Println("wrong addr:", from) continue } @@ -437,7 +472,7 @@ func main() { } stream = Streams[buf[0]] if stream == nil { - log.Println("unknown stream:", buf[0]) + // log.Println("unknown stream:", buf[0]) continue } stream.stats.pkts++ @@ -457,7 +492,7 @@ func main() { for { OurStats.pkts++ OurStats.bytes += 1 - if _, err = ln.WriteTo([]byte{sid}, addrUDP); err != nil { + if _, err = conn.Write([]byte{sid}); err != nil { log.Println("send:", err) Finish <- struct{}{} } @@ -521,7 +556,7 @@ func main() { OurStats.bytes += uint64(len(pkt)) OurStats.last = time.Now() OurStats.AddRMS(pcm) - if _, err = ln.WriteTo(pkt, addrUDP); err != nil { + if _, err = conn.Write(pkt); err != nil { log.Println("send:", err) break } diff --git a/cmd/keygen/main.go b/cmd/keygen/main.go index dfbea59..03c3b63 100644 --- a/cmd/keygen/main.go +++ b/cmd/keygen/main.go @@ -1,71 +1,35 @@ -// VoRS -- Vo(IP) Really Simple -// Copyright (C) 2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - package main import ( - "crypto/ed25519" "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" + "encoding/hex" + "flag" "fmt" + "io" "log" - "math/big" "os" - "time" - vors "go.stargrave.org/vors/internal" + "github.com/flynn/noise" ) func main() { - log.SetFlags(log.Lmicroseconds | log.Lshortfile) - pub, prv, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - log.Fatal(err) - } - notBefore := time.Now() - tmpl := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{CommonName: vors.CN}, - NotBefore: notBefore, - NotAfter: notBefore.Add(365 * 24 * time.Hour), - KeyUsage: x509.KeyUsageDigitalSignature, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - DNSNames: []string{vors.CN}, - } - der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, pub, prv) - if err != nil { - log.Fatal(err) - } - cer, err := x509.ParseCertificate(der) - if err != nil { - log.Fatal(err) - } - fmt.Fprintln(os.Stderr, "SPKI hash:", vors.SPKIHash(cer)) - key, err := x509.MarshalPKCS8PrivateKey(prv) - if err != nil { - log.Fatal(err) - } - err = pem.Encode(os.Stdout, &pem.Block{Type: "PRIVATE KEY", Bytes: key}) - if err != nil { - log.Fatal(err) + pub := flag.Bool("pub", false, "Print hexadecimal public key") + flag.Parse() + if *pub { + data, err := io.ReadAll(os.Stdin) + if err != nil { + log.Fatal(err) + } + if len(data) != 2*32 { + log.Fatal("wrong length") + } + fmt.Printf("%s\n", hex.EncodeToString(data[32:])) + return } - err = pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE", Bytes: der}) + kp, err := noise.DH25519.GenerateKeypair(rand.Reader) if err != nil { log.Fatal(err) } + os.Stdout.Write(kp.Private[:]) + os.Stdout.Write(kp.Public[:]) } diff --git a/cmd/server/main.go b/cmd/server/main.go index 094d10c..17c9ce3 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -16,7 +16,6 @@ package main import ( - "bufio" "crypto/rand" "crypto/subtle" "crypto/tls" @@ -30,16 +29,15 @@ import ( "net/netip" "os" "strconv" - "strings" "sync" "time" "github.com/dustin/go-humanize" + "github.com/flynn/noise" "github.com/jroimartin/gocui" vors "go.stargrave.org/vors/internal" "golang.org/x/crypto/blake2s" "golang.org/x/crypto/chacha20" - "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/poly1305" ) @@ -48,228 +46,265 @@ var ( MinVersion: tls.VersionTLS13, CurvePreferences: []tls.CurveID{tls.X25519}, } - SPKI string - Passwd = flag.String("passwd", "", "Shared password") - Peers = map[byte]*Peer{} - PeersM sync.Mutex + Peers = map[byte]*Peer{} + PeersM sync.Mutex + Prv, Pub []byte + Cookies = map[vors.Cookie]chan *net.UDPAddr{} ) -type Peer struct { - name string - sid byte - addr *net.UDPAddr - conn net.Conn - key []byte - stats *Stats -} - -func newPeer(connRaw net.Conn) { - logger := slog.With("remote", connRaw.RemoteAddr().String()) +func newPeer(conn *net.TCPConn) { + logger := slog.With("remote", conn.RemoteAddr().String()) logger.Info("connected") - defer connRaw.Close() - if len(Peers) == 256 { + defer conn.Close() + if len(Peers) == 1<<8 { logger.Error("too many peers") return } - conn := tls.Server(connRaw, TLSCfg) - err := conn.Handshake() + err := conn.SetNoDelay(true) if err != nil { - logger.Error("handshake:", "err", err) + log.Fatalln("nodelay:", err) + } + buf := make([]byte, len(vors.NoisePrologue)) + + if _, err = io.ReadFull(conn, buf); err != nil { + logger.Error("handshake: read prologue", "err", err) + return + } + if string(buf) != vors.NoisePrologue { + logger.Error("handshake: wrong prologue", "err", err) return } - defer conn.Close() - scanner := bufio.NewScanner(conn) - peer := Peer{conn: conn, stats: &Stats{dead: make(chan struct{})}} - peer.addr = net.UDPAddrFromAddrPort( - netip.MustParseAddrPort(conn.RemoteAddr().String())) + hs, err := noise.NewHandshakeState(noise.Config{ + CipherSuite: vors.NoiseCipherSuite, + Pattern: noise.HandshakeNK, + Initiator: false, + StaticKeypair: noise.DHKey{Private: Prv, Public: Pub}, + Prologue: []byte(vors.NoisePrologue), + }) if err != nil { - log.Fatal(err) + log.Fatalln("noise.NewHandshakeState:", err) + } + buf, err = vors.PktRead(conn) + if err != nil { + logger.Error("read handshake", "err", err) + return + } + peer := Peer{ + logger: logger, + conn: conn, + stats: &Stats{alive: make(chan struct{})}, + rx: make(chan []byte), + tx: make(chan []byte, 10), + alive: make(chan struct{}), } { - chlng := make([]byte, 16) - if _, err = io.ReadFull(rand.Reader, chlng); err != nil { - log.Fatal(err) + name, _, _, err := hs.ReadMessage(nil, buf) + if err != nil { + logger.Error("handshake: decrypt", "err", err) return } - chlngHex := hex.EncodeToString(chlng) - if _, err = io.Copy(conn, strings.NewReader(chlngHex+"\n")); err != nil { - logger.Error("write challenge:", "err", err) - return + peer.name = string(name) + } + logger = logger.With("name", peer.name) + + for _, p := range Peers { + if p.name != peer.name { + continue } - h, err := blake2s.New256([]byte(*Passwd)) + logger.Error("name already taken") + buf, _, _, err = hs.WriteMessage(nil, []byte("name already taken")) if err != nil { log.Fatal(err) } - h.Write([]byte(chlngHex)) - if !scanner.Scan() { - logger.Error("read password:", "err", scanner.Err()) - return - } - cols := strings.Fields(scanner.Text()) - if len(cols) == 1 { - logger.Error("no name") - io.Copy(conn, strings.NewReader("no name\n")) - return - } - peer.name = cols[1] - if peer.name == "myself" { - logger.Error("reserved name") - io.Copy(conn, strings.NewReader("reserved name\n")) - return - } - logger = logger.With("name", cols[1]) - if hex.EncodeToString(h.Sum(nil)) != cols[0] { - logger.Error("wrong password") - io.Copy(conn, strings.NewReader("wrong password\n")) - return - } - for _, p := range Peers { - if p.name == peer.name { - logger.Error("name already taken") - io.Copy(conn, strings.NewReader("name already taken\n")) - return - } - } + vors.PktWrite(conn, buf) + return + } + + { var i byte var ok bool + var found bool PeersM.Lock() - for i = 0; i <= 255; i++ { + for i = 0; i <= (1<<8)-1; i++ { if _, ok = Peers[i]; !ok { peer.sid = i + found = true break } } - Peers[peer.sid] = &peer + if found { + Peers[peer.sid] = &peer + go peer.Tx() + } PeersM.Unlock() - logger = logger.With("sid", peer.sid) - logger.Info("authenticated") - defer func() { - logger.Info("removing") - PeersM.Lock() - delete(Peers, peer.sid) - close(peer.stats.dead) - s := fmt.Sprintf("%s %d\n", vors.CmdDel, peer.sid) - for _, p := range Peers { - go io.Copy(p.conn, strings.NewReader(s)) + if !found { + buf, _, _, err = hs.WriteMessage(nil, []byte("too many users")) + if err != nil { + log.Fatal(err) } - PeersM.Unlock() - }() - if _, err = io.Copy(conn, strings.NewReader( - fmt.Sprintf("OK %d\n", peer.sid))); err != nil { - logger.Error("write ok:", "err", err) + vors.PktWrite(conn, buf) return } + } + logger = logger.With("sid", peer.sid) + logger.Info("logged in") + + defer func() { + logger.Info("removing") + PeersM.Lock() + delete(Peers, peer.sid) + PeersM.Unlock() + close(peer.stats.alive) + s := []byte(fmt.Sprintf("%s %d", vors.CmdDel, peer.sid)) for _, p := range Peers { - if p.sid == peer.sid { - continue - } - if _, err = io.Copy(conn, strings.NewReader(fmt.Sprintf( - "%s %d %s %s\n", vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key), - ))); err != nil { - logger.Error("write ADD:", "err", err) - return - } + go func(tx chan []byte) { tx <- s }(p.tx) + } + }() + + { + var cookie vors.Cookie + if _, err = io.ReadFull(rand.Reader, cookie[:]); err != nil { + log.Fatalln("cookie:", err) + } + gotCookie := make(chan *net.UDPAddr) + Cookies[cookie] = gotCookie + + var txCS, rxCS *noise.CipherState + buf, txCS, rxCS, err := hs.WriteMessage(nil, + []byte(fmt.Sprintf("OK %s", hex.EncodeToString(cookie[:])))) + if err = vors.PktWrite(conn, buf); err != nil { + logger.Error("handshake write", "err", err) + delete(Cookies, cookie) + return + } + peer.rxCS, peer.txCS = txCS, rxCS + + timeout := time.NewTimer(vors.PingTime) + select { + case peer.addr = <-gotCookie: + case <-timeout.C: + logger.Error("cookie timeout") + delete(Cookies, cookie) + return } - tlsState := conn.ConnectionState() - peer.key, err = tlsState.ExportKeyingMaterial( - strconv.Itoa(int(peer.sid)), nil, chacha20poly1305.KeySize) + delete(Cookies, cookie) + logger.Info("got cookie", "addr", peer.addr) + if !timeout.Stop() { + <-timeout.C + } + } + go peer.Rx() + peer.tx <- []byte(fmt.Sprintf("SID %d", peer.sid)) + + for _, p := range Peers { + if p.sid == peer.sid { + continue + } + peer.tx <- []byte(fmt.Sprintf("%s %d %s %s", + vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key))) + } + + { + h, err := blake2s.New256(hs.ChannelBinding()) if err != nil { - log.Fatal(err) + log.Fatalln(err) } - { - // assume atomic write - s := fmt.Sprintf("%s %d %s %s\n", - vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key)) - for _, p := range Peers { - if p.sid == peer.sid { - continue - } - go io.Copy(p.conn, strings.NewReader(s)) + h.Write([]byte(vors.NoisePrologue)) + peer.key = h.Sum(nil) + } + + { + s := []byte(fmt.Sprintf("%s %d %s %s", + vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key))) + for _, p := range Peers { + if p.sid != peer.sid { + p.tx <- s } } - seen := time.Now() - go func(seen *time.Time) { - for now := range time.Tick(vors.PingTime) { + } + + seen := time.Now() + go func(seen *time.Time) { + ticker := time.Tick(vors.PingTime) + var now time.Time + for { + select { + case now = <-ticker: if seen.Add(2 * vors.PingTime).Before(now) { logger.Error("timeout:", "seen", seen) - conn.Close() - break + peer.Close() + return } - } - }(&seen) - go func(stats *Stats) { - if *NoGUI { + case <-peer.alive: return } - tick := time.Tick(vors.ScreenRefresh) - var now time.Time - var v *gocui.View - for { - select { - case <-stats.dead: - GUI.DeleteView(peer.name) - return - case now = <-tick: - s := fmt.Sprintf( - "Rx/Tx: %s / %s | %s / %s", - humanize.Comma(stats.pktsRx), - humanize.Comma(stats.pktsTx), - humanize.IBytes(stats.bytesRx), - humanize.IBytes(stats.bytesTx), - ) - if stats.last.Add(vors.ScreenRefresh).After(now) { - s += " | " + vors.CGreen + "TALK" + vors.CReset - } - v, err = GUI.View(peer.name) - if err == nil { - v.Clear() - v.Write([]byte(s)) - } + } + }(&seen) + + go func(stats *Stats) { + if *NoGUI { + return + } + tick := time.Tick(vors.ScreenRefresh) + var now time.Time + var v *gocui.View + for { + select { + case <-stats.alive: + GUI.DeleteView(peer.name) + return + case now = <-tick: + s := fmt.Sprintf( + "%s | Rx/Tx: %s / %s | %s / %s", + peer.addr, + humanize.Comma(stats.pktsRx), + humanize.Comma(stats.pktsTx), + humanize.IBytes(stats.bytesRx), + humanize.IBytes(stats.bytesTx), + ) + if stats.last.Add(vors.ScreenRefresh).After(now) { + s += " | " + vors.CGreen + "TALK" + vors.CReset } - } - }(peer.stats) - for scanner.Scan() { - if scanner.Text() == vors.CmdPing { - if _, err = io.Copy(conn, - strings.NewReader(vors.CmdPong+"\n")); err != nil { - logger.Error("write ok:", "err", err) - return + v, err = GUI.View(peer.name) + if err == nil { + v.Clear() + v.Write([]byte(s)) } - seen = time.Now() } } - if scanner.Err() != nil { - logger.Error(scanner.Err().Error()) + }(peer.stats) + + for buf := range peer.rx { + if string(buf) == vors.CmdPing { + seen = time.Now() + peer.tx <- []byte(vors.CmdPong) } } } func main() { - bind := flag.String("bind", "[::1]:12345", "TCP/UDP port to listen on") - pemFile := flag.String("pem", "keypair.pem", "PEM with keypair") + bind := flag.String("bind", "[::1]:"+strconv.Itoa(vors.DefaultPort), + "Host:TCP/UDP port to listen on") + kpFile := flag.String("key", "key", "Path to keypair file") flag.Parse() log.SetFlags(log.Lmicroseconds | log.Lshortfile) - if *Passwd == "" { - log.Fatal("no -passwd specified") - } - if err := parsePEM(*pemFile); err != nil { - log.Fatal(err) - } - addrTCP, err := net.ResolveTCPAddr("tcp", *bind) - if err != nil { - log.Fatal(err) - } - addrUDP, err := net.ResolveUDPAddr("udp", *bind) - if err != nil { - log.Fatal(err) + { + data, err := os.ReadFile(*kpFile) + if err != nil { + log.Fatal(err) + } + Prv, Pub = data[:len(data)/2], data[len(data)/2:] } - lnTCP, err := net.ListenTCP("tcp", addrTCP) + + lnTCP, err := net.ListenTCP("tcp", + net.TCPAddrFromAddrPort(netip.MustParseAddrPort(*bind))) if err != nil { log.Fatal(err) } - lnUDP, err := net.ListenUDP("udp", addrUDP) + lnUDP, err := net.ListenUDP("udp", + net.UDPAddrFromAddrPort(netip.MustParseAddrPort(*bind))) if err != nil { log.Fatal(err) } @@ -325,26 +360,41 @@ func main() { if err != nil { log.Fatalln("recvfrom:", err) } + + if n == vors.CookieLen { + var cookie vors.Cookie + copy(cookie[:], buf) + if c, ok := Cookies[cookie]; ok { + c <- from + close(c) + } else { + slog.Info("unknown cookie", "cookie", cookie) + } + continue + } + sid = buf[0] peer = Peers[sid] if peer == nil { - slog.Info("unknown:", "sid", sid, "from", from) + slog.Info("unknown", "sid", sid, "from", from) continue } + if from.Port != peer.addr.Port || !from.IP.Equal(peer.addr.IP) { - slog.Info("wrong addr:", + slog.Info("wrong addr", "peer", peer.name, "our", peer.addr, "got", from) continue } + peer.stats.pktsRx++ peer.stats.bytesRx += uint64(n) if n == 1 { continue } if n <= 4+vors.TagLen { - slog.Info("too small:", "peer", peer.name, "len", n) + slog.Info("too small", "peer", peer.name, "len", n) continue } @@ -366,7 +416,7 @@ func main() { buf[n-vors.TagLen:n], ) != 1 { log.Println("decrypt:", peer.name, "tag differs") - slog.Info("MAC failed:", "peer", peer.name, "len", n) + slog.Info("MAC failed", "peer", peer.name, "len", n) continue } @@ -378,7 +428,7 @@ func main() { p.stats.pktsTx++ p.stats.bytesTx += uint64(n) if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil { - slog.Warn("sendto:", "peer", peer.name, "err", err) + slog.Warn("sendto", "peer", peer.name, "err", err) } } } @@ -386,9 +436,9 @@ func main() { go func() { <-LoggerReady - slog.Info("listening", "bind", *bind, "spki", SPKI) + slog.Info("listening", "bind", *bind, "pub", hex.EncodeToString(Pub)) for { - conn, err := lnTCP.Accept() + conn, err := lnTCP.AcceptTCP() if err != nil { log.Fatalln("accept:", err) } @@ -397,12 +447,10 @@ func main() { }() if *NoGUI { - dummy := make(chan struct{}) - <-dummy - } else { - err = GUI.MainLoop() - if err != nil && err != gocui.ErrQuit { - log.Fatal(err) - } + <-make(chan struct{}) + } + err = GUI.MainLoop() + if err != nil && err != gocui.ErrQuit { + log.Fatal(err) } } diff --git a/cmd/server/peer.go b/cmd/server/peer.go new file mode 100644 index 0000000..ef0693b --- /dev/null +++ b/cmd/server/peer.go @@ -0,0 +1,70 @@ +package main + +import ( + "log/slog" + "net" + "sync" + + "github.com/flynn/noise" + vors "go.stargrave.org/vors/internal" +) + +type Peer struct { + name string + sid byte + addr *net.UDPAddr + key []byte + stats *Stats + + logger *slog.Logger + conn net.Conn + rx, tx chan []byte + rxCS, txCS *noise.CipherState + alive chan struct{} + aliveOnce sync.Once +} + +func (peer *Peer) Close() { + peer.aliveOnce.Do(func() { + close(peer.rx) + close(peer.tx) + close(peer.alive) + peer.conn.Close() + }) +} + +func (peer *Peer) Rx() { + for { + buf, err := vors.PktRead(peer.conn) + if err != nil { + peer.logger.Error("rx", "err", err) + break + } + buf, err = peer.rxCS.Decrypt(buf[:0], nil, buf) + if err != nil { + peer.logger.Error("rx decrypt", "err", err) + break + } + peer.rx <- buf + } + peer.Close() +} + +func (peer *Peer) Tx() { + for buf := range peer.tx { + if peer.txCS == nil { + continue + } + buf, err := peer.txCS.Encrypt(buf[:0], nil, buf) + if err != nil { + peer.logger.Error("tx encrypt", "err", err) + break + } + err = vors.PktWrite(peer.conn, buf) + if err != nil { + peer.logger.Error("tx", "err", err) + break + } + } + peer.Close() +} diff --git a/cmd/server/stats.go b/cmd/server/stats.go index b4c324b..86c010e 100644 --- a/cmd/server/stats.go +++ b/cmd/server/stats.go @@ -8,5 +8,5 @@ type Stats struct { bytesRx uint64 bytesTx uint64 last time.Time - dead chan struct{} + alive chan struct{} } diff --git a/cmd/server/x509.go b/cmd/server/x509.go deleted file mode 100644 index f202b60..0000000 --- a/cmd/server/x509.go +++ /dev/null @@ -1,57 +0,0 @@ -// VoRS -- Vo(IP) Really Simple -// Copyright (C) 2024 Sergey Matveev -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, version 3 of the License. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -package main - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "os" - - vors "go.stargrave.org/vors/internal" -) - -func parsePEM(pth string) error { - data, err := os.ReadFile(pth) - if err != nil { - return err - } - cert := tls.Certificate{} - var b *pem.Block - for len(data) > 0 { - b, data = pem.Decode(data) - if b == nil { - continue - } - switch b.Type { - case "CERTIFICATE": - cert.Certificate = append(cert.Certificate, b.Bytes) - cer, err := x509.ParseCertificate(b.Bytes) - if err != nil { - return err - } - SPKI = vors.SPKIHash(cer) - case "PRIVATE KEY": - prv, err := x509.ParsePKCS8PrivateKey(b.Bytes) - if err != nil { - return err - } - cert.PrivateKey = prv - } - } - TLSCfg.Certificates = append(TLSCfg.Certificates, cert) - return nil -} diff --git a/doc/index.texi b/doc/index.texi index 8dfdb6d..f576f9d 100644 --- a/doc/index.texi +++ b/doc/index.texi @@ -53,8 +53,6 @@ appropriate and satisfiable as fast and secure encryption solution. @end itemize -TODO: Look at latest Opus'es neural network abilities. - @include install.texi @include usage.texi @include vad.texi diff --git a/doc/install.texi b/doc/install.texi index e22ccd0..b01529f 100644 --- a/doc/install.texi +++ b/doc/install.texi @@ -3,7 +3,8 @@ VoRS is written on @url{https://go.dev/, Go}, but depends on @url{https://github.com/hraban/opus, gopkg.in/hraban/opus.v2} -library, that links it with C-written @code{libopus} library. +library, that links it with C-written +@url{https://opus-codec.org/, libopus} library. So you will need its development headers. @example diff --git a/doc/proto.texi b/doc/proto.texi index 6ebaca7..f3c1209 100644 --- a/doc/proto.texi +++ b/doc/proto.texi @@ -3,7 +3,7 @@ VoRS uses Opus codec with 20ms frames with 48kHz 1ch 16-bit S-LE sound. It uses native @code{libopus}'es Packet Loss Concealment (PLC) feature -if number of lost frame does not excess 32 count. +if number of lost frame does not exceed 32 count. Each frame has single byte stream identifier (unique identifier of the participant) and 24-bit big-endian packet counter. Reordered packets are @@ -19,45 +19,56 @@ IPv6 header, +8B of Poly1305 authentication tag, +4B of stream identifier with the counter, +8B of UDP header for 50pps means also 24Kbps of bandwidth only for overhead transmission. -Each client handshakes with the server over TCP protocol using TLS 1.3 -with curve25519 key-agreement protocol. @command{vors-keygen} generates -ed25519-based certificates -- so everything here is nearly completely -NIST-free. +Each client handshakes with the server over TCP connection using +@url{http://noiseprotocol.org/, Noise}-NK protocol pattern with +curve25519, ChaCha20-Poly1305 and BLAKE2s algorithms. -After TLS session is established, simple text-based protocol is run: +@itemize -@example -TLS 1.3: - S <- C : ClientHello - S -> C : ServerHello+ServerFinished - S <- C : ClientFinished +@item Client sends @code{VoRS v1} to the socket. Just a magic number. + +@item All next messages are prepended with 16-bit big-endian length. + +@item Client sends initial Noise handshake message with his username as +a payload. + +@item Server answers with final Noise handshake message with the payload +of @code{OK HEX(COOKIE)}, or any other failure message. It may reject +client if there are too many peers or its name is already taken. + +@item That 128-bit cookie is sent by client over UDP to the server every +second. If UDP packets are lost, then no connection is possible and +after timeout server drop TCP connection. + +@item Otherwise it replies with @code{SID XXX}, where XXX is ASCII +decimal stream number client must use. -S -> C : HEX(128-bit random CHALLENGE) -S <- C : HEX(BLAKE2s-256(PASSWORD, CHALLENGE)) USERNAME -S -> C : OK SID +@item @code{PING} and @code{PONG} messages are then sent every ten +seconds as a heartbeat. -S <- C : PING -S -> C : PONG +@end itemize + +@example +S <- C : e, es, "username" +S -> C : e, ee, "OK COOKIE" +S <- C : UDP(COOKIE) +S -> C : "SID XXX" + +S <- C : "PING" +S -> C : "PONG" S <> C : ... -S -> C : ADD SID USERNAME HEX(KEY) +S -> C : "ADD SID USERNAME HEX(KEY)" S -> C : ... -S -> C : DEL SID +S -> C : "DEL SID" S -> C : ... @end example -Client is authenticated by hashing the challenge with keyed hash. Every -ten seconds it PINGs server, awaiting for PONG in return. Server may -acknowledge client about new peer appearing, sending its SID (stream -identifier) in ASCII decimal form, username and encryption key. Also it -may notify about peer disappearing. - -If client did not get @code{OK SID} reply, then it disconnects. -@code{SID} is our stream identifier. When we are successfully -authenticated, we both derive our encryption key for UDP packets by -"exporting keying material" (EKM) from TLS session context. - Every second client sends UDP packet with his single-byte stream identifier, even if it is muted. That may help punching holes in stateful firewalls. + +Clients are notified about new peers appearance with @code{ADD} +commands, telling their SIDs, usernames and keys. @code{DEL} notifies +about leaving peers. diff --git a/doc/usage.texi b/doc/usage.texi index 252a947..396685c 100644 --- a/doc/usage.texi +++ b/doc/usage.texi @@ -1,45 +1,38 @@ @node Usage @unnumbered Usage -Server is required to authenticate clients, give them unique stream -numbers and relay their voice traffic. Except for address to bind to, it -requires only password and keypair specification. Clients authenticate -server by its X.509 certificate's SubjectPublicKeyInfo's SHA2-256 hash. -Clients are authenticate by challenge-response protocol based on -provided password. +@itemize -Generate server's keypair with @command{vors-keygen} and run the server. -Its SPKI hash will also be printed in the logs. +@item + Generate server's keypair. And share its public key among users. + Fact of server's public key knowledge means ability to connect to it. @example -$ umask 077 -$ vors-keygen > keypair.pem -$ vors-server -bind "[2001:db8::1234]:12345" -passwd PASSWORD -pem keypair.pem +$ vors-keygen | tee key | vors-keygen -pub | read pub +$ vors-server -key key -bind [2001:db8::1]:12978 @end example -Client uses external commands for reading from microphone and playing it -back. By default it uses SoX'es @command{rec} and @command{play} -commands. Pay attention that VoRS expects @strong{ONLY} one channel, -48kHz, 16-bit signed little-endian audio format. Empty strings in -@option{-rec}/@option{-play} options mean no recording/playback attempts. +@item + Client uses external commands for reading from microphone and + playing it back. By default it uses SoX'es @command{rec} and + @command{play} commands. -@command{-play} command is spawned for each participant. Your OS should -mix their output together. + Pay attention that VoRS expects @strong{ONLY} one channel, 48kHz, + 16-bit signed little-endian audio format. Empty strings in + @option{-rec}/@option{-play} options mean no recording/playback + attempts. -Why no audio libraries solutions? OpenAL, PulseAudio, PortAudio, -PipeWire, OSS, sndio, libao, JACK. Too much to choose from. None of them -present by default in every distribution. All of them have problems, -issues, and libao offers only playback capability for example. And pay -attention that we have to use them from Go. Luckily SoX can use any of -OS'es backend and we can use it transparently. And we do not have to -create complex interface to configure in/out audio resources. + @command{-play} command is spawned for each participant. Your OS + should mix their output together. -Start the client, providing server's SPKI hash, password and our username: +@item + Start the client, providing server's public key and our username: @example -$ vors-client -spki SPKI -passwd PASSWORD -name NAME \ - -srv "[2001:db8::1234]:12345" +$ vors-client -srv "[2001:db8::1]:12978" -pub $pub -name NAME @end example -Pressing F10 in server/client TUIs means quitting. Pressing Enter in -client means "mute" toggling. + Pressing F10 in server/client TUIs means quitting. Pressing Enter in + client means "mute" toggling. + +@end itemize diff --git a/doc/vad.texi b/doc/vad.texi index d5510ae..b45a30b 100644 --- a/doc/vad.texi +++ b/doc/vad.texi @@ -13,7 +13,9 @@ desired @option{THRES} value to @option{-vad} option of $ rec [...] | vors-vad 100 [talk and see if threshold is low/high enough] [it is too sensible, let's try higher one] + $ rec [...] | vors-vad 200 [perfect!] + $ vors-client -vad 200 [...] @end example diff --git a/go.mod b/go.mod index 70232b4..969f328 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.22.2 require ( github.com/dustin/go-humanize v1.0.1 + github.com/flynn/noise v1.1.0 github.com/jroimartin/gocui v0.5.0 golang.org/x/term v0.19.0 gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 diff --git a/go.sum b/go.sum index 4c3107a..82f502d 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,31 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= +github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= github.com/jroimartin/gocui v0.5.0 h1:DCZc97zY9dMnHXJSJLLmx9VqiEnAj0yh0eTNpuEtG/4= github.com/jroimartin/gocui v0.5.0/go.mod h1:l7Hz8DoYoL6NoYnlnaX6XCNR62G7J5FfSW5jEogzaxE= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY= github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM= gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= diff --git a/internal/audio.go b/internal/audio.go new file mode 100644 index 0000000..bc5f725 --- /dev/null +++ b/internal/audio.go @@ -0,0 +1,9 @@ +package internal + +const ( + Rate = 48000 + FrameMs = 20 + Bitrate = 32000 + FrameLen = FrameMs * Rate / 1000 + MaxLost = 32 +) diff --git a/internal/cookie.go b/internal/cookie.go new file mode 100644 index 0000000..57bff24 --- /dev/null +++ b/internal/cookie.go @@ -0,0 +1,11 @@ +package internal + +import "encoding/hex" + +const CookieLen = 16 + +type Cookie [CookieLen]byte + +func (c Cookie) String() string { + return hex.EncodeToString(c[:]) +} diff --git a/internal/noise.go b/internal/noise.go new file mode 100644 index 0000000..5e4b883 --- /dev/null +++ b/internal/noise.go @@ -0,0 +1,36 @@ +package internal + +import ( + "bytes" + "io" + "net" + + "github.com/flynn/noise" +) + +const NoisePrologue = "VoRS v1" + +var NoiseCipherSuite = noise.NewCipherSuite( + noise.DH25519, + noise.CipherChaChaPoly, + noise.HashBLAKE2s, +) + +func PktRead(conn net.Conn) (buf []byte, err error) { + buf = make([]byte, 2) + _, err = io.ReadFull(conn, buf[:2]) + if err != nil { + return + } + buf = make([]byte, int(buf[0])<<8|int(buf[1])) + _, err = io.ReadFull(conn, buf) + return +} + +func PktWrite(conn net.Conn, buf []byte) (err error) { + _, err = io.Copy(conn, bytes.NewReader(append([]byte{ + byte((len(buf) & 0xFF00) >> 8), + byte((len(buf) & 0x00FF) >> 0), + }, buf...))) + return +} diff --git a/internal/resolve.go b/internal/resolve.go new file mode 100644 index 0000000..ceffc33 --- /dev/null +++ b/internal/resolve.go @@ -0,0 +1,31 @@ +package internal + +import "net" + +const DefaultPort = 12978 + +func MustResolveTCP(s string) (addr *net.TCPAddr) { + var err error + addr, err = net.ResolveTCPAddr("tcp6", s) + if err == nil { + return addr + } + addr, err = net.ResolveTCPAddr("tcp4", s) + if err != nil { + panic(err) + } + return +} + +func MustResolveUDP(s string) (addr *net.UDPAddr) { + var err error + addr, err = net.ResolveUDPAddr("udp6", s) + if err == nil { + return addr + } + addr, err = net.ResolveUDPAddr("udp4", s) + if err != nil { + panic(err) + } + return +} diff --git a/internal/var.go b/internal/var.go index fbc5544..b4cb3ff 100644 --- a/internal/var.go +++ b/internal/var.go @@ -3,14 +3,7 @@ package internal import "time" const ( - Rate = 48000 - FrameMs = 20 - Bitrate = 32000 - FrameLen = FrameMs * Rate / 1000 - - CN = "vors" - MaxLost = 32 - TagLen = 8 + TagLen = 8 CmdPing = "PING" CmdPong = "PONG" diff --git a/internal/x509.go b/internal/x509.go deleted file mode 100644 index c04e978..0000000 --- a/internal/x509.go +++ /dev/null @@ -1,13 +0,0 @@ -package internal - -import ( - "crypto/sha256" - "crypto/x509" - "encoding/hex" -) - -func SPKIHash(c *x509.Certificate) string { - spki := c.RawSubjectPublicKeyInfo - hsh := sha256.Sum256(spki) - return hex.EncodeToString(hsh[:]) -} -- 2.44.0