]> Sergey Matveev's repositories - vors.git/commitdiff
Noising
authorSergey Matveev <stargrave@stargrave.org>
Thu, 11 Apr 2024 15:39:26 +0000 (18:39 +0300)
committerSergey Matveev <stargrave@stargrave.org>
Thu, 11 Apr 2024 15:39:26 +0000 (18:39 +0300)
19 files changed:
cmd/client/main.go
cmd/keygen/main.go
cmd/server/main.go
cmd/server/peer.go [new file with mode: 0644]
cmd/server/stats.go
cmd/server/x509.go [deleted file]
doc/index.texi
doc/install.texi
doc/proto.texi
doc/usage.texi
doc/vad.texi
go.mod
go.sum
internal/audio.go [new file with mode: 0644]
internal/cookie.go [new file with mode: 0644]
internal/noise.go [new file with mode: 0644]
internal/resolve.go [new file with mode: 0644]
internal/var.go
internal/x509.go [deleted file]

index bad619faed3490fe9f5d1de277d770194152d61c263e260e6b8b93683a36ba39..bd7fba6963122a32a4995bcf10b6f41b6af7ae82c99f40cd039934f416af11c6 100644 (file)
 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
                        }
index dfbea59f96349000d62f843b5ff8893d0c74ed2c408b1b2e5e668b3d2da997e2..03c3b6368e06a1cb723fe19d13f599201d24708b5360339902bc5de16c65cfa0 100644 (file)
@@ -1,71 +1,35 @@
-// VoRS -- Vo(IP) Really Simple
-// Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
 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[:])
 }
index 094d10cda724d76cbabcf5e02c7efe8ef6efeabe1a54e120e6c278bebebafa8f..17c9ce3daecaf66a17ffd27dc99ed892e04e60262e1f76e2e71f89ed98af2980 100644 (file)
@@ -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 (file)
index 0000000..ef0693b
--- /dev/null
@@ -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()
+}
index b4c324ba158fb1ac838174d87a1e3f0d7deb1bb3e89a4e4c8aac87bf362f93a1..86c010e61dae851da0c8f5b7142e3039ada609d70023cbf5d13d778910d9ee97 100644 (file)
@@ -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 (file)
index f202b60..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-// VoRS -- Vo(IP) Really Simple
-// Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
-//
-// 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 <http://www.gnu.org/licenses/>.
-
-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
-}
index 8dfdb6dcad588d467e2e968aa97434d9b2c4b4d6ff18376b86a8b2f4b9d9b166..f576f9db0e03ec2bca7b4d6b1c88e26e52d1acfcb1a1aef61a3a6d8e5d636735 100644 (file)
@@ -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
index e22ccd0348e7f7f479bf0f9c6c3c24e30362fcddef864cc35a7396c683ba52a2..b01529f9bb1729cd3596bde595b4c53cb48054ff3ee6a64effa15a4f02aac6da 100644 (file)
@@ -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
index 6ebaca70c9a9119ba408a490d33e844d3329e61fb856fda1e16eac1123652521..f3c1209edf6ca8d0370b38d3cbc33917f2e40f7164326ab22bf9fc6172c1a4d6 100644 (file)
@@ -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.
index 252a947c20fceccd7b46308e21236df2c63bfb446707906f65f60eae1c1cf48e..396685ce1d6ff798b1f2fa5db6576eb3cc22d322e2f6f0319ed2f1de6bf1813d 100644 (file)
@@ -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
index d5510ae613538976c08785dc1ee029c4ec04beed839726310cfef06344b7a8ea..b45a30bcf923f1fad25b01d772d86af64287c1def73267154a4dbd263ef4de16 100644 (file)
@@ -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 70232b4aa5095267406d62a3ad222fac33682fb4b7d8be07bad8ca43022f94ad..969f3284f1372564716dc3e16c3995a142318ad2796c98e59933196b87d2d96a 100644 (file)
--- 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 4c3107ad3afea8b0168bf5b07cdbf6e999f4299193fe03806eaa60c4e4251d98..82f502d0bfa4839985da01677584c891bbbdb08c7a4c9da081cd3bec54907f4f 100644 (file)
--- 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 (file)
index 0000000..bc5f725
--- /dev/null
@@ -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 (file)
index 0000000..57bff24
--- /dev/null
@@ -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 (file)
index 0000000..5e4b883
--- /dev/null
@@ -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 (file)
index 0000000..ceffc33
--- /dev/null
@@ -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
+}
index fbc5544345198995567663ce3ab766aa03c684a2cd1b4ea14398e36b72799c1b..b4cb3ff551c66779fcb787a259d7a09c0377167c4497a2fa08391e93723349ca 100644 (file)
@@ -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 (file)
index c04e978..0000000
+++ /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[:])
-}