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/>.
39 "github.com/jroimartin/gocui"
40 vors "go.stargrave.org/vors/internal"
41 "golang.org/x/crypto/blake2s"
42 "golang.org/x/crypto/chacha20"
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 keyOur, err := tlsState.ExportKeyingMaterial(cols[1], nil, chacha20.KeySize)
197 LoggerReady := make(chan struct{})
198 GUI, err = gocui.NewGui(gocui.OutputNormal)
203 GUI.SelFgColor = gocui.ColorCyan
205 GUI.SetManagerFunc(guiLayout)
206 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
209 if err := GUI.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, mute); err != nil {
215 v, err := GUI.View("logs")
220 log.Println("connected")
223 time.Sleep(vors.ScreenRefresh)
224 GUI.Update(func(gui *gocui.Gui) error {
233 time.Sleep(100 * time.Millisecond)
240 time.Sleep(vors.PingTime)
241 if _, err = ctrl.Write([]byte(vors.CmdPing + "\n")); err != nil {
242 log.Println("ping:", err)
249 go func(seen *time.Time) {
254 if t == vors.CmdPong {
259 cols := strings.Fields(t)
262 sidRaw, name, keyHex := cols[1], cols[2], cols[3]
263 log.Println("add", name)
264 sid := parseSID(sidRaw)
265 key, err := hex.DecodeString(keyHex)
271 in: make(chan []byte, 1<<10),
272 stats: &Stats{dead: make(chan struct{})},
275 dec, err := opus.NewDecoder(vors.Rate, 1)
280 var player io.WriteCloser
283 cmd = makeCmd(*playCmd)
284 player, err = cmd.StdinPipe()
294 var ciph *chacha20.Cipher
296 var mac *poly1305.MAC
297 tag := make([]byte, poly1305.TagSize)
299 pcm := make([]int16, vors.FrameLen)
300 pcmbuf := make([]byte, 2*vors.FrameLen)
301 nonce := make([]byte, 12)
305 for buf := range stream.in {
306 copy(nonce[len(nonce)-4:], buf)
307 ciph, err = chacha20.NewUnauthenticatedCipher(key, nonce)
312 ciph.XORKeyStream(macKey[:], macKey[:])
314 mac = poly1305.New(&macKey)
315 if _, err = mac.Write(buf[4 : len(buf)-vors.TagLen]); err != nil {
319 if subtle.ConstantTimeCompare(
321 buf[len(buf)-vors.TagLen:],
323 log.Println("decrypt:", stream.name, "tag differs")
326 pkt = buf[4 : len(buf)-vors.TagLen]
327 ciph.XORKeyStream(pkt, pkt)
329 ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
331 // ignore the very first packet in the stream
334 lost = int(ctr - (stream.ctr + 1))
337 stream.stats.lost += int64(lost)
338 if lost > vors.MaxLost {
341 for ; lost > 0; lost-- {
342 lastDur, err = dec.LastPacketDuration()
344 log.Println("PLC:", err)
347 err = dec.DecodePLC(pcm[:lastDur])
349 log.Println("PLC:", err)
352 stream.stats.AddRMS(pcm)
356 pcmConv(pcmbuf, pcm[:lastDur])
357 if _, err = io.Copy(player, bytes.NewReader(
358 pcmbuf[:2*lastDur])); err != nil {
359 log.Println("play:", err)
362 _, err = dec.Decode(pkt, pcm)
364 log.Println("decode:", err)
367 stream.stats.AddRMS(pcm)
368 stream.stats.last = time.Now()
373 if _, err = io.Copy(player,
374 bytes.NewReader(pcmbuf)); err != nil {
375 log.Println("play:", err)
382 go statsDrawer(stream.stats, stream.name)
383 Streams[sid] = stream
385 sid := parseSID(cols[1])
388 log.Println("unknown sid:", sid)
394 log.Println("del", s.name)
396 log.Fatal("unknown cmd:", cols[0])
399 if scanner.Err() != nil {
400 log.Print("scanner:", err)
405 go func(seen *time.Time) {
406 for now := range time.Tick(vors.PingTime) {
407 if seen.Add(2 * vors.PingTime).Before(now) {
408 log.Println("timeout:", seen)
418 var from *net.UDPAddr
423 buf := make([]byte, 2*vors.FrameLen)
424 n, from, err = ln.ReadFromUDP(buf)
426 log.Println("recvfrom:", err)
430 if from.Port != addrUDP.Port || !from.IP.Equal(addrUDP.IP) {
431 log.Println("wrong addr:", from)
434 if n <= 4+vors.TagLen {
435 log.Println("too small:", n)
438 stream = Streams[buf[0]]
440 log.Println("unknown stream:", buf[0])
444 stream.stats.bytes += uint64(n)
445 ctr = binary.BigEndian.Uint32(buf)
446 if ctr <= stream.ctr {
447 stream.stats.reorder++
454 go statsDrawer(OurStats, *Name)
460 if _, err = ln.WriteTo([]byte{sid}, addrUDP); err != nil {
461 log.Println("send:", err)
464 time.Sleep(time.Second)
472 var ciph *chacha20.Cipher
474 var mac *poly1305.MAC
475 tag := make([]byte, poly1305.TagSize)
476 buf := make([]byte, 2*vors.FrameLen)
477 pcm := make([]int16, vors.FrameLen)
478 nonce := make([]byte, 12)
479 nonce[len(nonce)-4] = sid
483 _, err = io.ReadFull(mic, buf)
485 log.Println("mic:", err)
491 for i = 0; i < vors.FrameLen; i++ {
492 pcm[i] = int16(uint16(buf[i*2+0]) | (uint16(buf[i*2+1]) << 8))
494 if vad != 0 && vors.RMS(pcm) < vad {
497 n, err = opusEnc.Encode(pcm, buf[4:])
502 incr(nonce[len(nonce)-3:])
503 copy(buf, nonce[len(nonce)-4:])
504 ciph, err = chacha20.NewUnauthenticatedCipher(keyOur, nonce)
509 ciph.XORKeyStream(macKey[:], macKey[:])
511 ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
512 mac = poly1305.New(&macKey)
513 if _, err = mac.Write(buf[4 : 4+n]); err != nil {
517 copy(buf[4+n:], tag[:vors.TagLen])
518 pkt = buf[:4+n+vors.TagLen]
521 OurStats.bytes += uint64(len(pkt))
522 OurStats.last = time.Now()
524 if _, err = ln.WriteTo(pkt, addrUDP); err != nil {
525 log.Println("send:", err)
532 if err != nil && err != gocui.ErrQuit {