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"
57 Streams = map[byte]*Stream{}
58 Finish = make(chan struct{})
59 OurStats = &Stats{dead: make(chan struct{})}
60 Name = flag.String("name", "test", "Username")
64 func parseSID(s string) byte {
65 n, err := strconv.Atoi(s)
70 log.Fatal("too big stream num")
75 func makeCmd(cmd string) *exec.Cmd {
76 args := strings.Fields(cmd)
78 return exec.Command(args[0])
80 return exec.Command(args[0], args[1:]...)
83 func incr(data []byte) {
84 for i := len(data) - 1; i >= 0; i-- {
94 vadRaw := flag.Uint("vad", 0, "VAD threshold")
95 hostport := flag.String("srv", "[::1]:12345", "TCP/UDP port to connect to")
96 spkiHash := flag.String("spki", "FILL-ME", "SHA256 hash of server's certificate SPKI")
97 passwd := flag.String("passwd", "", "Password")
98 recCmd := flag.String("rec", "rec --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "rec command")
99 playCmd := flag.String("play", "play --no-show-progress --buffer 1920 --channels 1 --endian little --encoding signed --rate 48000 --bits 16 --type raw -", "play command")
101 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
103 vad := uint64(*vadRaw)
104 opusEnc := newOpusEnc()
105 var mic io.ReadCloser
108 cmd := makeCmd(*recCmd)
109 mic, err = cmd.StdoutPipe()
119 addrTCP, err := net.ResolveTCPAddr("tcp", *hostport)
123 addrUDP, err := net.ResolveUDPAddr("udp", *hostport)
127 ctrlRaw, err := net.DialTCP("tcp", nil, addrTCP)
129 log.Fatalln("dial server:", err)
131 defer ctrlRaw.Close()
132 ourAddr := net.UDPAddrFromAddrPort(
133 netip.MustParseAddrPort(ctrlRaw.LocalAddr().String()))
134 ln, err := net.ListenUDP("udp", ourAddr)
138 ctrl := tls.Client(ctrlRaw, &tls.Config{
139 MinVersion: tls.VersionTLS13,
140 CurvePreferences: []tls.CurveID{tls.X25519},
142 InsecureSkipVerify: true,
143 VerifyPeerCertificate: func(
144 rawCerts [][]byte, verifiedChains [][]*x509.Certificate,
146 cer, err := x509.ParseCertificate(rawCerts[0])
150 if *spkiHash != vors.SPKIHash(cer) {
151 return errors.New("server certificate's SPKI hash mismatch")
156 err = ctrl.Handshake()
158 log.Println("TLS handshake:", err)
163 scanner := bufio.NewScanner(ctrl)
165 log.Println("read challenge:", scanner.Err())
169 h, err := blake2s.New256([]byte(*passwd))
173 h.Write(scanner.Bytes())
174 if _, err = io.Copy(ctrl, strings.NewReader(fmt.Sprintf(
175 "%s %s\n", hex.EncodeToString(h.Sum(nil)), *Name))); err != nil {
176 log.Println("write password:", err)
181 log.Println("auth", scanner.Err())
184 cols := strings.Fields(scanner.Text())
186 log.Println("auth failed:", scanner.Text())
189 sid := parseSID(cols[1])
190 Streams[sid] = &Stream{name: *Name, stats: OurStats}
192 tlsState := ctrl.ConnectionState()
193 keyOur, err := tlsState.ExportKeyingMaterial(cols[1], nil, chacha20.KeySize)
199 LoggerReady := make(chan struct{})
200 GUI, err = gocui.NewGui(gocui.OutputNormal)
205 GUI.SelFgColor = gocui.ColorCyan
207 GUI.SetManagerFunc(guiLayout)
208 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
211 if err := GUI.SetKeybinding("", gocui.KeyEnter, gocui.ModNone, mute); err != nil {
217 v, err := GUI.View("logs")
222 log.Println("connected")
225 time.Sleep(vors.ScreenRefresh)
226 GUI.Update(func(gui *gocui.Gui) error {
235 time.Sleep(100 * time.Millisecond)
242 time.Sleep(vors.PingTime)
243 if _, err = ctrl.Write([]byte(vors.CmdPing + "\n")); err != nil {
244 log.Println("ping:", err)
251 go func(seen *time.Time) {
256 if t == vors.CmdPong {
261 cols := strings.Fields(t)
264 sidRaw, name, keyHex := cols[1], cols[2], cols[3]
265 log.Println("add", name)
266 sid := parseSID(sidRaw)
267 key, err := hex.DecodeString(keyHex)
273 in: make(chan []byte, 1<<10),
274 stats: &Stats{dead: make(chan struct{})},
277 dec, err := opus.NewDecoder(vors.Rate, 1)
282 var player io.WriteCloser
285 cmd = makeCmd(*playCmd)
286 player, err = cmd.StdinPipe()
296 var ciph *chacha20.Cipher
298 var mac *poly1305.MAC
299 tag := make([]byte, poly1305.TagSize)
301 pcm := make([]int16, vors.FrameLen)
302 pcmbuf := make([]byte, 2*vors.FrameLen)
303 nonce := make([]byte, 12)
307 for buf := range stream.in {
308 copy(nonce[len(nonce)-4:], buf)
309 ciph, err = chacha20.NewUnauthenticatedCipher(key, nonce)
314 ciph.XORKeyStream(macKey[:], macKey[:])
316 mac = poly1305.New(&macKey)
317 if _, err = mac.Write(buf[4 : len(buf)-TagLen]); err != nil {
321 if subtle.ConstantTimeCompare(tag[:TagLen], buf[len(buf)-TagLen:]) != 1 {
322 log.Println("decrypt:", stream.name, "tag differs")
325 pkt = buf[4 : len(buf)-TagLen]
326 ciph.XORKeyStream(pkt, pkt)
328 ctr = binary.BigEndian.Uint32(nonce[len(nonce)-4:])
330 // ignore the very first packet in the stream
333 lost = int(ctr - (stream.ctr + 1))
336 stream.stats.lost += int64(lost)
337 if lost > vors.MaxLost {
340 for ; lost > 0; lost-- {
341 lastDur, err = dec.LastPacketDuration()
343 log.Println("PLC:", err)
346 err = dec.DecodePLC(pcm[:lastDur])
348 log.Println("PLC:", err)
351 stream.stats.AddRMS(pcm)
355 pcmConv(pcmbuf, pcm[:lastDur])
356 if _, err = io.Copy(player, bytes.NewReader(
357 pcmbuf[:2*lastDur])); err != nil {
358 log.Println("play:", err)
361 _, err = dec.Decode(pkt, pcm)
363 log.Println("decode:", err)
366 stream.stats.AddRMS(pcm)
367 stream.stats.last = time.Now()
372 if _, err = io.Copy(player,
373 bytes.NewReader(pcmbuf)); err != nil {
374 log.Println("play:", err)
381 go statsDrawer(stream.stats, stream.name)
382 Streams[sid] = stream
384 sid := parseSID(cols[1])
387 log.Println("unknown sid:", sid)
393 log.Println("del", s.name)
395 log.Fatal("unknown cmd:", cols[0])
398 if scanner.Err() != nil {
399 log.Print("scanner:", err)
404 go func(seen *time.Time) {
405 for now := range time.Tick(vors.PingTime) {
406 if seen.Add(2 * vors.PingTime).Before(now) {
407 log.Println("timeout:", seen)
417 var from *net.UDPAddr
422 buf := make([]byte, 2*vors.FrameLen)
423 n, from, err = ln.ReadFromUDP(buf)
425 log.Println("recvfrom:", err)
429 if from.Port != addrUDP.Port || !from.IP.Equal(addrUDP.IP) {
430 log.Println("wrong addr:", from)
433 if n <= 1+4+poly1305.TagSize {
434 log.Println("too small:", n)
437 stream = Streams[buf[0]]
439 log.Println("unknown stream:", buf[0])
443 stream.stats.bytes += uint64(n)
444 ctr = binary.BigEndian.Uint32(buf)
445 if ctr <= stream.ctr {
446 stream.stats.reorder++
453 go statsDrawer(OurStats, *Name)
459 if _, err = ln.WriteTo([]byte{sid}, addrUDP); err != nil {
460 log.Println("send:", err)
463 time.Sleep(time.Second)
471 var ciph *chacha20.Cipher
473 var mac *poly1305.MAC
474 tag := make([]byte, poly1305.TagSize)
475 buf := make([]byte, 2*vors.FrameLen)
476 pcm := make([]int16, vors.FrameLen)
477 nonce := make([]byte, 12)
478 nonce[len(nonce)-4] = sid
482 _, err = io.ReadFull(mic, buf)
484 log.Println("mic:", err)
490 for i = 0; i < vors.FrameLen; i++ {
491 pcm[i] = int16(uint16(buf[i*2+0]) | (uint16(buf[i*2+1]) << 8))
493 if vad != 0 && vors.RMS(pcm) < vad {
496 n, err = opusEnc.Encode(pcm, buf[4:])
501 incr(nonce[len(nonce)-3:])
502 copy(buf, nonce[len(nonce)-4:])
503 ciph, err = chacha20.NewUnauthenticatedCipher(keyOur, nonce)
508 ciph.XORKeyStream(macKey[:], macKey[:])
510 ciph.XORKeyStream(buf[4:4+n], buf[4:4+n])
511 mac = poly1305.New(&macKey)
512 if _, err = mac.Write(buf[4 : 4+n]); err != nil {
516 copy(buf[4+n:], tag[:TagLen])
517 pkt = buf[:4+n+TagLen]
520 OurStats.bytes += uint64(len(pkt))
521 OurStats.last = time.Now()
523 if _, err = ln.WriteTo(pkt, addrUDP); err != nil {
524 log.Println("send:", err)
531 if err != nil && err != gocui.ErrQuit {