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 Affero General Public License for more details.
13 // You should have received a copy of the GNU Affero General Public License
14 // along with this program. If not, see <http://www.gnu.org/licenses/>.
34 "github.com/dchest/siphash"
35 "github.com/flynn/noise"
36 "github.com/jroimartin/gocui"
37 vors "go.stargrave.org/vors/v3/internal"
38 "golang.org/x/crypto/blake2s"
39 "golang.org/x/crypto/chacha20"
44 Cookies = map[vors.Cookie]chan *net.UDPAddr{}
47 func newPeer(conn *net.TCPConn) {
48 logger := slog.With("remote", conn.RemoteAddr().String())
49 logger.Info("connected")
51 err := conn.SetNoDelay(true)
53 log.Fatalln("nodelay:", err)
55 buf := make([]byte, len(vors.NoisePrologue))
57 if _, err = io.ReadFull(conn, buf); err != nil {
58 logger.Error("handshake: read prologue", "err", err)
61 if string(buf) != vors.NoisePrologue {
62 logger.Error("handshake: wrong prologue", "err", err)
66 hs, err := noise.NewHandshakeState(noise.Config{
67 CipherSuite: vors.NoiseCipherSuite,
68 Pattern: noise.HandshakeNK,
70 StaticKeypair: noise.DHKey{Private: Prv, Public: Pub},
71 Prologue: []byte(vors.NoisePrologue),
74 log.Fatalln("noise.NewHandshakeState:", err)
76 nsConn := vors.NewNSConn(conn)
79 logger.Error("read handshake", "err", nsConn.Err)
86 rx: make(chan []byte),
87 tx: make(chan []byte, 10),
88 alive: make(chan struct{}),
92 argsRaw, _, _, err := hs.ReadMessage(nil, buf)
94 logger.Error("handshake: decrypt", "err", err)
97 args, err := vors.ArgsDecode(argsRaw)
99 logger.Error("handshake: decode args", "err", err)
102 peer.name = string(args[0])
103 roomName := string(args[1])
104 key := string(args[2])
105 logger = logger.With("name", peer.name, "room", roomName)
107 room = Rooms[roomName]
112 peers: make(map[byte]*Peer),
113 alive: make(chan struct{}),
115 Rooms[roomName] = room
121 tick := time.Tick(vors.ScreenRefresh)
127 GUI.DeleteView(room.name)
130 v, err = GUI.View(room.name)
133 v.Write([]byte(strings.Join(room.Stats(now), "\n")))
142 logger.Error("wrong password")
143 buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
144 []byte(vors.CmdErr), []byte("wrong password"),
156 for _, p := range room.peers {
157 if p.name != peer.name {
160 logger.Error("name already taken")
161 buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
162 []byte(vors.CmdErr), []byte("name already taken"),
167 room.peersM.RUnlock()
171 room.peersM.RUnlock()
178 for i = 0; i <= (1<<8)-1; i++ {
179 if _, ok = Peers[i]; !ok {
186 Peers[peer.sid] = peer
191 buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
192 []byte(vors.CmdErr), []byte("too many users"),
201 logger = logger.With("sid", peer.sid)
203 room.peers[peer.sid] = peer
205 logger.Info("logged in")
208 logger.Info("removing")
210 delete(Peers, peer.sid)
212 delete(room.peers, peer.sid)
215 s := vors.ArgsEncode([]byte(vors.CmdDel), []byte{peer.sid})
217 for _, p := range room.peers {
220 room.peersM.RUnlock()
224 var cookie vors.Cookie
225 if _, err = io.ReadFull(rand.Reader, cookie[:]); err != nil {
226 log.Fatalln("cookie:", err)
228 gotCookie := make(chan *net.UDPAddr)
229 Cookies[cookie] = gotCookie
231 var txCS, rxCS *noise.CipherState
232 buf, txCS, rxCS, err := hs.WriteMessage(nil,
233 vors.ArgsEncode([]byte(vors.CmdCookie), cookie[:]))
235 log.Fatalln("hs.WriteMessage:", err)
237 if err = nsConn.Tx(buf); err != nil {
238 logger.Error("handshake write", "err", err)
239 delete(Cookies, cookie)
242 peer.rxCS, peer.txCS = txCS, rxCS
244 timeout := time.NewTimer(vors.PingTime)
246 case peer.addr = <-gotCookie:
248 logger.Error("cookie timeout")
249 delete(Cookies, cookie)
252 delete(Cookies, cookie)
258 peer.tx <- vors.ArgsEncode([]byte(vors.CmdSID), []byte{peer.sid})
261 for _, p := range room.peers {
262 if p.sid == peer.sid {
265 peer.tx <- vors.ArgsEncode(
266 []byte(vors.CmdAdd), []byte{p.sid}, []byte(p.name), p.key)
268 room.peersM.RUnlock()
271 xof, err := blake2s.NewXOF(chacha20.KeySize+16, nil)
275 xof.Write([]byte(vors.NoisePrologue))
276 xof.Write(hs.ChannelBinding())
277 peer.key = make([]byte, chacha20.KeySize+16)
278 if _, err = io.ReadFull(xof, peer.key); err != nil {
281 peer.mac = siphash.New(peer.key[chacha20.KeySize:])
285 s := vors.ArgsEncode(
286 []byte(vors.CmdAdd), []byte{peer.sid}, []byte(peer.name), peer.key)
288 for _, p := range room.peers {
289 if p.sid != peer.sid {
293 room.peersM.RUnlock()
297 go func(seen *time.Time) {
298 ticker := time.Tick(vors.PingTime)
303 if seen.Add(2 * vors.PingTime).Before(now) {
304 logger.Error("timeout", "seen", seen)
314 for buf := range peer.rx {
315 args, err := vors.ArgsDecode(buf)
317 logger.Error("decode args", "err", err)
321 logger.Error("empty args")
325 switch cmd := string(args[0]); cmd {
327 peer.tx <- vors.ArgsEncode([]byte(vors.CmdPong))
330 s := vors.ArgsEncode([]byte(vors.CmdMuted), []byte{peer.sid})
332 for _, p := range room.peers {
333 if p.sid != peer.sid {
337 room.peersM.RUnlock()
338 case vors.CmdUnmuted:
340 s := vors.ArgsEncode([]byte(vors.CmdUnmuted), []byte{peer.sid})
342 for _, p := range room.peers {
343 if p.sid != peer.sid {
347 room.peersM.RUnlock()
350 logger.Error("wrong len(args)")
353 s := vors.ArgsEncode([]byte(vors.CmdChat), []byte{peer.sid}, args[1])
355 for _, p := range room.peers {
356 if p.sid != peer.sid {
360 room.peersM.RUnlock()
362 logger.Error("unknown", "cmd", cmd)
368 bind := flag.String("bind", "[::1]:"+strconv.Itoa(vors.DefaultPort),
369 "host:TCP/UDP port to listen on")
370 kpFile := flag.String("key", "key", "path to keypair file")
371 prefer4 := flag.Bool("4", false,
372 "Prefer obsolete legacy IPv4 address during name resolution")
373 version := flag.Bool("version", false, "print version")
374 warranty := flag.Bool("warranty", false, "print warranty information")
375 flag.Usage = func() {
376 fmt.Fprintln(os.Stderr, "Usage: vors-server [opts] -bind HOST:PORT -key PATH -srv HOST:PORT")
378 fmt.Fprintln(os.Stderr, `
379 List of known rooms is shown by default. If room requires password
380 authentication, then "protected" is written nearby. Each room's member
381 username and IP address is shown, together with various statistics:
382 number of received, transmitted packets, number of bad packets (failed
383 authentication), amount of traffic. "TALK" means that recently an audio
384 packet was received. "MUTE" means that peer is in muted mode.
388 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
391 fmt.Println(vors.Warranty)
395 fmt.Println(vors.GetVersion())
400 data, err := os.ReadFile(*kpFile)
404 Prv, Pub = data[:len(data)/2], data[len(data)/2:]
407 vors.PreferIPv4 = *prefer4
408 lnTCP, err := net.ListenTCP("tcp",
409 net.TCPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
413 lnUDP, err := net.ListenUDP("udp",
414 net.UDPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
419 LoggerReady := make(chan struct{})
422 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
425 GUI, err = gocui.NewGui(gocui.OutputNormal)
430 GUI.SetManagerFunc(guiLayout)
431 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone,
432 func(g *gocui.Gui, v *gocui.View) error {
434 time.Sleep(100 * time.Millisecond)
444 v, err := GUI.View("logs")
448 slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
451 time.Sleep(vors.ScreenRefresh)
452 GUI.Update(func(gui *gocui.Gui) error {
461 buf := make([]byte, 2*vors.FrameLen)
463 var from *net.UDPAddr
467 tag := make([]byte, siphash.Size)
469 n, from, err = lnUDP.ReadFromUDP(buf)
471 log.Fatalln("recvfrom:", err)
474 if n == vors.CookieLen {
475 var cookie vors.Cookie
477 if c, ok := Cookies[cookie]; ok {
487 slog.Info("unknown", "sid", sid, "from", from)
491 if peer.addr == nil ||
492 from.Port != peer.addr.Port ||
493 !from.IP.Equal(peer.addr.IP) {
494 slog.Info("wrong addr",
502 peer.stats.bytesRx += vors.IPHdrLen(from.IP) + 8 + uint64(n)
506 if n <= 4+siphash.Size {
512 if _, err = peer.mac.Write(buf[:n-siphash.Size]); err != nil {
515 peer.mac.Sum(tag[:0])
516 if subtle.ConstantTimeCompare(
518 buf[n-siphash.Size:n],
524 peer.stats.last = time.Now()
525 peer.room.peersM.RLock()
526 for _, p := range peer.room.peers {
527 if p.sid == sid || p.addr == nil {
531 p.stats.bytesTx += vors.IPHdrLen(p.addr.IP) + 8 + uint64(n)
532 if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil {
533 slog.Warn("sendto", "peer", peer.name, "err", err)
536 peer.room.peersM.RUnlock()
542 slog.Info("listening",
544 "pub", base64.RawURLEncoding.EncodeToString(Pub))
546 conn, err := lnTCP.AcceptTCP()
548 log.Fatalln("accept:", err)
555 <-make(chan struct{})
558 if err != nil && err != gocui.ErrQuit {