1 // VoRS -- Vo(IP) Really Simple
2 // Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, version 3 of the License.
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
38 "github.com/jroimartin/gocui"
39 vors "go.stargrave.org/vors/internal"
40 "golang.org/x/crypto/blake2s"
41 "golang.org/x/crypto/chacha20"
42 "golang.org/x/crypto/chacha20poly1305"
43 "golang.org/x/crypto/poly1305"
44 "gopkg.in/hraban/opus.v2"
55 Streams = map[byte]*Stream{}
56 Finish = make(chan struct{})
57 OurStats = &Stats{dead: make(chan struct{})}
58 Name = flag.String("name", "test", "Username")
62 func parseSID(s string) byte {
63 n, err := strconv.Atoi(s)
68 log.Fatal("too big stream num")
73 func makeCmd(cmd string) *exec.Cmd {
74 args := strings.Fields(cmd)
76 return exec.Command(args[0])
78 return exec.Command(args[0], args[1:]...)
81 func incr(data []byte) {
82 for i := len(data) - 1; i >= 0; i-- {
92 vadRaw := flag.Uint("vad", 0, "VAD threshold")
93 hostport := flag.String("srv", "[::1]:12345", "TCP/UDP port to connect to")
94 spkiHash := flag.String("spki", "FILL-ME", "SHA256 hash of server's certificate SPKI")
95 passwd := flag.String("passwd", "", "Password")
96 recCmd := flag.String("rec", "rec --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "rec command")
97 playCmd := flag.String("play", "play --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "play command")
99 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
101 vad := uint64(*vadRaw)
102 opusEnc := newOpusEnc()
103 var mic io.ReadCloser
106 cmd := makeCmd(*recCmd)
107 mic, err = cmd.StdoutPipe()
117 addrTCP, err := net.ResolveTCPAddr("tcp", *hostport)
121 addrUDP, err := net.ResolveUDPAddr("udp", *hostport)
125 ctrlRaw, err := net.DialTCP("tcp", nil, addrTCP)
127 log.Fatalln("dial server:", err)
129 defer ctrlRaw.Close()
130 ourAddr := net.UDPAddrFromAddrPort(
131 netip.MustParseAddrPort(ctrlRaw.LocalAddr().String()))
132 ln, err := net.ListenUDP("udp", ourAddr)
136 ctrl := tls.Client(ctrlRaw, &tls.Config{
137 MinVersion: tls.VersionTLS13,
138 CurvePreferences: []tls.CurveID{tls.X25519},
140 InsecureSkipVerify: true,
141 VerifyPeerCertificate: func(
142 rawCerts [][]byte, verifiedChains [][]*x509.Certificate,
144 cer, err := x509.ParseCertificate(rawCerts[0])
148 if *spkiHash != vors.SPKIHash(cer) {
149 return errors.New("server certificate's SPKI hash mismatch")
154 err = ctrl.Handshake()
156 log.Println("TLS handshake:", err)
161 scanner := bufio.NewScanner(ctrl)
163 log.Println("read challenge:", scanner.Err())
167 h, err := blake2s.New256([]byte(*passwd))
171 h.Write(scanner.Bytes())
172 if _, err = io.Copy(ctrl, strings.NewReader(fmt.Sprintf(
173 "%s %s\n", hex.EncodeToString(h.Sum(nil)), *Name))); err != nil {
174 log.Println("write password:", err)
179 log.Println("auth", scanner.Err())
182 cols := strings.Fields(scanner.Text())
184 log.Println("auth failed:", scanner.Text())
187 sid := parseSID(cols[1])
188 Streams[sid] = &Stream{name: *Name, stats: OurStats}
190 tlsState := ctrl.ConnectionState()
191 key, err := tlsState.ExportKeyingMaterial(
192 cols[1], nil, chacha20poly1305.KeySize)
196 ciph, err := chacha20poly1305.New(key)
202 LoggerReady := make(chan struct{})
203 GUI, err = gocui.NewGui(gocui.OutputNormal)
208 GUI.SelFgColor = gocui.ColorCyan
210 GUI.SetManagerFunc(guiLayout)
211 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
214 if err := GUI.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, mute); err != nil {
220 v, err := GUI.View("logs")
225 log.Println("connected")
228 time.Sleep(vors.ScreenRefresh)
229 GUI.Update(func(gui *gocui.Gui) error {
238 time.Sleep(100 * time.Millisecond)
245 time.Sleep(vors.PingTime)
246 if _, err = ctrl.Write([]byte(vors.CmdPing + "\n")); err != nil {
247 log.Println("ping:", err)
254 go func(seen *time.Time) {
259 if t == vors.CmdPong {
264 cols := strings.Fields(t)
267 sidRaw, name, keyHex := cols[1], cols[2], cols[3]
268 log.Println("add", name)
269 sid := parseSID(sidRaw)
270 key, err := hex.DecodeString(keyHex)
276 in: make(chan []byte, 1<<10),
277 stats: &Stats{dead: make(chan struct{})},
280 ciph, err := chacha20poly1305.New(key)
284 dec, err := opus.NewDecoder(vors.Rate, 1)
289 var player io.WriteCloser
292 cmd = makeCmd(*playCmd)
293 player, err = cmd.StdinPipe()
303 ctr := uint32(sid) << 24
304 pcm := make([]int16, vors.FrameLen)
305 pcmbuf := make([]byte, 2*vors.FrameLen)
306 decbuf := make([]byte, 2*vors.FrameLen)
307 nonce := make([]byte, chacha20.NonceSize)
308 ctrBuf := nonce[len(nonce)-4:]
312 for buf := range stream.in {
313 ctr = binary.BigEndian.Uint32(buf)
315 pkt, err = ciph.Open(
316 decbuf[:0], nonce, buf[4:], []byte{buf[0]})
318 log.Println("decrypt:", stream.name, err)
322 // ignore the very first packet in the stream
325 lost = int(ctr - (stream.ctr + 1))
328 stream.stats.lost += int64(lost)
329 if lost > vors.MaxLost {
332 for ; lost > 0; lost-- {
333 lastDur, err = dec.LastPacketDuration()
335 log.Println("PLC:", err)
338 err = dec.DecodePLC(pcm[:lastDur])
340 log.Println("PLC:", err)
343 stream.stats.AddRMS(pcm)
347 pcmConv(pcmbuf, pcm[:lastDur])
348 if _, err = io.Copy(player, bytes.NewReader(
349 pcmbuf[:2*lastDur])); err != nil {
350 log.Println("play:", err)
353 _, err = dec.Decode(pkt, pcm)
355 log.Println("decode:", err)
358 stream.stats.AddRMS(pcm)
359 stream.stats.last = time.Now()
364 if _, err = io.Copy(player,
365 bytes.NewReader(pcmbuf)); err != nil {
366 log.Println("play:", err)
373 go statsDrawer(stream.stats, stream.name)
374 Streams[sid] = stream
376 sid := parseSID(cols[1])
379 log.Println("unknown sid:", sid)
385 log.Println("del", s.name)
387 log.Fatal("unknown cmd:", cols[0])
390 if scanner.Err() != nil {
391 log.Print("scanner:", err)
396 go func(seen *time.Time) {
397 for now := range time.Tick(vors.PingTime) {
398 if seen.Add(2 * vors.PingTime).Before(now) {
399 log.Println("timeout:", seen)
409 var from *net.UDPAddr
414 buf := make([]byte, 2*vors.FrameLen)
415 n, from, err = ln.ReadFromUDP(buf)
417 log.Println("recvfrom:", err)
421 if from.Port != addrUDP.Port || !from.IP.Equal(addrUDP.IP) {
422 log.Println("wrong addr:", from)
425 if n <= 1+4+poly1305.TagSize {
426 log.Println("too small:", n)
429 stream = Streams[buf[0]]
431 log.Println("unknown stream:", buf[0])
435 stream.stats.bytes += uint64(n)
436 ctr = binary.BigEndian.Uint32(buf)
437 if ctr <= stream.ctr {
438 stream.stats.reorder++
445 go statsDrawer(OurStats, *Name)
451 if _, err = ln.WriteTo([]byte{sid}, addrUDP); err != nil {
452 log.Println("send:", err)
455 time.Sleep(time.Second)
463 buf := make([]byte, 2*vors.FrameLen)
464 pcm := make([]int16, vors.FrameLen)
465 nonce := make([]byte, ciph.NonceSize())
466 nonce[len(nonce)-4] = sid
467 ctr := nonce[len(nonce)-3:]
468 sidAndCtr := nonce[len(nonce)-4:]
472 _, err = io.ReadFull(mic, buf)
474 log.Println("mic:", err)
480 for i = 0; i < vors.FrameLen; i++ {
481 pcm[i] = int16(uint16(buf[i*2+0]) | (uint16(buf[i*2+1]) << 8))
483 if vad != 0 && vors.RMS(pcm) < vad {
488 n, err = opusEnc.Encode(pcm, buf[4:])
492 pkt = ciph.Seal(buf[:4], nonce, buf[4:4+n], []byte{sid})
494 OurStats.bytes += uint64(len(pkt))
495 OurStats.last = time.Now()
497 if _, err = ln.WriteTo(pkt, addrUDP); err != nil {
498 log.Println("send:", err)
505 if err != nil && err != gocui.ErrQuit {