]> Sergey Matveev's repositories - vors.git/blob - cmd/server/main.go
Usage information
[vors.git] / cmd / server / main.go
1 // VoRS -- Vo(IP) Really Simple
2 // Copyright (C) 2024 Sergey Matveev <stargrave@stargrave.org>
3 //
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.
7 //
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.
12 //
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/>.
15
16 package main
17
18 import (
19         "crypto/rand"
20         "crypto/subtle"
21         "encoding/base64"
22         "flag"
23         "fmt"
24         "io"
25         "log"
26         "log/slog"
27         "net"
28         "net/netip"
29         "os"
30         "strconv"
31         "strings"
32         "time"
33
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"
40 )
41
42 var (
43         Prv, Pub []byte
44         Cookies  = map[vors.Cookie]chan *net.UDPAddr{}
45 )
46
47 func newPeer(conn *net.TCPConn) {
48         logger := slog.With("remote", conn.RemoteAddr().String())
49         logger.Info("connected")
50         defer conn.Close()
51         err := conn.SetNoDelay(true)
52         if err != nil {
53                 log.Fatalln("nodelay:", err)
54         }
55         buf := make([]byte, len(vors.NoisePrologue))
56
57         if _, err = io.ReadFull(conn, buf); err != nil {
58                 logger.Error("handshake: read prologue", "err", err)
59                 return
60         }
61         if string(buf) != vors.NoisePrologue {
62                 logger.Error("handshake: wrong prologue", "err", err)
63                 return
64         }
65
66         hs, err := noise.NewHandshakeState(noise.Config{
67                 CipherSuite:   vors.NoiseCipherSuite,
68                 Pattern:       noise.HandshakeNK,
69                 Initiator:     false,
70                 StaticKeypair: noise.DHKey{Private: Prv, Public: Pub},
71                 Prologue:      []byte(vors.NoisePrologue),
72         })
73         if err != nil {
74                 log.Fatalln("noise.NewHandshakeState:", err)
75         }
76         nsConn := vors.NewNSConn(conn)
77         buf = <-nsConn.Rx
78         if buf == nil {
79                 logger.Error("read handshake", "err", nsConn.Err)
80                 return
81         }
82         peer := &Peer{
83                 logger: logger,
84                 conn:   nsConn,
85                 stats:  &Stats{},
86                 rx:     make(chan []byte),
87                 tx:     make(chan []byte, 10),
88                 alive:  make(chan struct{}),
89         }
90         var room *Room
91         {
92                 argsRaw, _, _, err := hs.ReadMessage(nil, buf)
93                 if err != nil {
94                         logger.Error("handshake: decrypt", "err", err)
95                         return
96                 }
97                 args, err := vors.ArgsDecode(argsRaw)
98                 if err != nil {
99                         logger.Error("handshake: decode args", "err", err)
100                         return
101                 }
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)
106                 RoomsM.Lock()
107                 room = Rooms[roomName]
108                 if room == nil {
109                         room = &Room{
110                                 name:  roomName,
111                                 key:   key,
112                                 peers: make(map[byte]*Peer),
113                                 alive: make(chan struct{}),
114                         }
115                         Rooms[roomName] = room
116                         RoomsM.Unlock()
117                         go func() {
118                                 if *NoGUI {
119                                         return
120                                 }
121                                 tick := time.Tick(vors.ScreenRefresh)
122                                 var now time.Time
123                                 var v *gocui.View
124                                 for {
125                                         select {
126                                         case <-room.alive:
127                                                 GUI.DeleteView(room.name)
128                                                 return
129                                         case now = <-tick:
130                                                 v, err = GUI.View(room.name)
131                                                 if err == nil {
132                                                         v.Clear()
133                                                         v.Write([]byte(strings.Join(room.Stats(now), "\n")))
134                                                 }
135                                         }
136                                 }
137                         }()
138                 } else {
139                         RoomsM.Unlock()
140                 }
141                 if room.key != key {
142                         logger.Error("wrong password")
143                         buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
144                                 []byte(vors.CmdErr), []byte("wrong password"),
145                         ))
146                         if err != nil {
147                                 log.Fatal(err)
148                         }
149                         nsConn.Tx(buf)
150                         return
151                 }
152         }
153         peer.room = room
154
155         room.peersM.RLock()
156         for _, p := range room.peers {
157                 if p.name != peer.name {
158                         continue
159                 }
160                 logger.Error("name already taken")
161                 buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
162                         []byte(vors.CmdErr), []byte("name already taken"),
163                 ))
164                 if err != nil {
165                         log.Fatal(err)
166                 }
167                 room.peersM.RUnlock()
168                 nsConn.Tx(buf)
169                 return
170         }
171         room.peersM.RUnlock()
172
173         {
174                 var i byte
175                 var ok bool
176                 var found bool
177                 PeersM.Lock()
178                 for i = 0; i <= (1<<8)-1; i++ {
179                         if _, ok = Peers[i]; !ok {
180                                 peer.sid = i
181                                 found = true
182                                 break
183                         }
184                 }
185                 if found {
186                         Peers[peer.sid] = peer
187                         go peer.Tx()
188                 }
189                 PeersM.Unlock()
190                 if !found {
191                         buf, _, _, err = hs.WriteMessage(nil, vors.ArgsEncode(
192                                 []byte(vors.CmdErr), []byte("too many users"),
193                         ))
194                         if err != nil {
195                                 log.Fatal(err)
196                         }
197                         nsConn.Tx(buf)
198                         return
199                 }
200         }
201         logger = logger.With("sid", peer.sid)
202         room.peersM.Lock()
203         room.peers[peer.sid] = peer
204         room.peersM.Unlock()
205         logger.Info("logged in")
206
207         defer func() {
208                 logger.Info("removing")
209                 PeersM.Lock()
210                 delete(Peers, peer.sid)
211                 room.peersM.Lock()
212                 delete(room.peers, peer.sid)
213                 room.peersM.Unlock()
214                 PeersM.Unlock()
215                 s := vors.ArgsEncode([]byte(vors.CmdDel), []byte{peer.sid})
216                 room.peersM.RLock()
217                 for _, p := range room.peers {
218                         p.tx <- s
219                 }
220                 room.peersM.RUnlock()
221         }()
222
223         {
224                 var cookie vors.Cookie
225                 if _, err = io.ReadFull(rand.Reader, cookie[:]); err != nil {
226                         log.Fatalln("cookie:", err)
227                 }
228                 gotCookie := make(chan *net.UDPAddr)
229                 Cookies[cookie] = gotCookie
230
231                 var txCS, rxCS *noise.CipherState
232                 buf, txCS, rxCS, err := hs.WriteMessage(nil,
233                         vors.ArgsEncode([]byte(vors.CmdCookie), cookie[:]))
234                 if err != nil {
235                         log.Fatalln("hs.WriteMessage:", err)
236                 }
237                 if err = nsConn.Tx(buf); err != nil {
238                         logger.Error("handshake write", "err", err)
239                         delete(Cookies, cookie)
240                         return
241                 }
242                 peer.rxCS, peer.txCS = txCS, rxCS
243
244                 timeout := time.NewTimer(vors.PingTime)
245                 select {
246                 case peer.addr = <-gotCookie:
247                 case <-timeout.C:
248                         logger.Error("cookie timeout")
249                         delete(Cookies, cookie)
250                         return
251                 }
252                 delete(Cookies, cookie)
253                 if !timeout.Stop() {
254                         <-timeout.C
255                 }
256         }
257         go peer.Rx()
258         peer.tx <- vors.ArgsEncode([]byte(vors.CmdSID), []byte{peer.sid})
259
260         room.peersM.RLock()
261         for _, p := range room.peers {
262                 if p.sid == peer.sid {
263                         continue
264                 }
265                 peer.tx <- vors.ArgsEncode(
266                         []byte(vors.CmdAdd), []byte{p.sid}, []byte(p.name), p.key)
267         }
268         room.peersM.RUnlock()
269
270         {
271                 xof, err := blake2s.NewXOF(chacha20.KeySize+16, nil)
272                 if err != nil {
273                         log.Fatalln(err)
274                 }
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 {
279                         log.Fatalln(err)
280                 }
281                 peer.mac = siphash.New(peer.key[chacha20.KeySize:])
282         }
283
284         {
285                 s := vors.ArgsEncode(
286                         []byte(vors.CmdAdd), []byte{peer.sid}, []byte(peer.name), peer.key)
287                 room.peersM.RLock()
288                 for _, p := range room.peers {
289                         if p.sid != peer.sid {
290                                 p.tx <- s
291                         }
292                 }
293                 room.peersM.RUnlock()
294         }
295
296         seen := time.Now()
297         go func(seen *time.Time) {
298                 ticker := time.Tick(vors.PingTime)
299                 var now time.Time
300                 for {
301                         select {
302                         case now = <-ticker:
303                                 if seen.Add(2 * vors.PingTime).Before(now) {
304                                         logger.Error("timeout", "seen", seen)
305                                         peer.Close()
306                                         return
307                                 }
308                         case <-peer.alive:
309                                 return
310                         }
311                 }
312         }(&seen)
313
314         for buf := range peer.rx {
315                 args, err := vors.ArgsDecode(buf)
316                 if err != nil {
317                         logger.Error("decode args", "err", err)
318                         break
319                 }
320                 if len(args) == 0 {
321                         logger.Error("empty args")
322                         break
323                 }
324                 seen = time.Now()
325                 switch cmd := string(args[0]); cmd {
326                 case vors.CmdPing:
327                         peer.tx <- vors.ArgsEncode([]byte(vors.CmdPong))
328                 case vors.CmdMuted:
329                         peer.muted = true
330                         s := vors.ArgsEncode([]byte(vors.CmdMuted), []byte{peer.sid})
331                         room.peersM.RLock()
332                         for _, p := range room.peers {
333                                 if p.sid != peer.sid {
334                                         p.tx <- s
335                                 }
336                         }
337                         room.peersM.RUnlock()
338                 case vors.CmdUnmuted:
339                         peer.muted = false
340                         s := vors.ArgsEncode([]byte(vors.CmdUnmuted), []byte{peer.sid})
341                         room.peersM.RLock()
342                         for _, p := range room.peers {
343                                 if p.sid != peer.sid {
344                                         p.tx <- s
345                                 }
346                         }
347                         room.peersM.RUnlock()
348                 case vors.CmdChat:
349                         if len(args) != 2 {
350                                 logger.Error("wrong len(args)")
351                                 continue
352                         }
353                         s := vors.ArgsEncode([]byte(vors.CmdChat), []byte{peer.sid}, args[1])
354                         room.peersM.RLock()
355                         for _, p := range room.peers {
356                                 if p.sid != peer.sid {
357                                         p.tx <- s
358                                 }
359                         }
360                         room.peersM.RUnlock()
361                 default:
362                         logger.Error("unknown", "cmd", cmd)
363                 }
364         }
365 }
366
367 func main() {
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")
377                 flag.PrintDefaults()
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.
385 Press F10 to quit.`)
386         }
387         flag.Parse()
388         log.SetFlags(log.Lmicroseconds | log.Lshortfile)
389
390         if *warranty {
391                 fmt.Println(vors.Warranty)
392                 return
393         }
394         if *version {
395                 fmt.Println(vors.GetVersion())
396                 return
397         }
398
399         {
400                 data, err := os.ReadFile(*kpFile)
401                 if err != nil {
402                         log.Fatal(err)
403                 }
404                 Prv, Pub = data[:len(data)/2], data[len(data)/2:]
405         }
406
407         vors.PreferIPv4 = *prefer4
408         lnTCP, err := net.ListenTCP("tcp",
409                 net.TCPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
410         if err != nil {
411                 log.Fatal(err)
412         }
413         lnUDP, err := net.ListenUDP("udp",
414                 net.UDPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
415         if err != nil {
416                 log.Fatal(err)
417         }
418
419         LoggerReady := make(chan struct{})
420         if *NoGUI {
421                 close(GUIReadyC)
422                 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
423                 close(LoggerReady)
424         } else {
425                 GUI, err = gocui.NewGui(gocui.OutputNormal)
426                 if err != nil {
427                         log.Fatal(err)
428                 }
429                 defer GUI.Close()
430                 GUI.SetManagerFunc(guiLayout)
431                 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone,
432                         func(g *gocui.Gui, v *gocui.View) error {
433                                 go func() {
434                                         time.Sleep(100 * time.Millisecond)
435                                         os.Exit(0)
436                                 }()
437                                 return gocui.ErrQuit
438                         }); err != nil {
439                         log.Fatal(err)
440                 }
441
442                 go func() {
443                         <-GUIReadyC
444                         v, err := GUI.View("logs")
445                         if err != nil {
446                                 log.Fatal(err)
447                         }
448                         slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
449                         close(LoggerReady)
450                         for {
451                                 time.Sleep(vors.ScreenRefresh)
452                                 GUI.Update(func(gui *gocui.Gui) error {
453                                         return nil
454                                 })
455                         }
456                 }()
457         }
458
459         go func() {
460                 <-LoggerReady
461                 buf := make([]byte, 2*vors.FrameLen)
462                 var n int
463                 var from *net.UDPAddr
464                 var err error
465                 var sid byte
466                 var peer *Peer
467                 tag := make([]byte, siphash.Size)
468                 for {
469                         n, from, err = lnUDP.ReadFromUDP(buf)
470                         if err != nil {
471                                 log.Fatalln("recvfrom:", err)
472                         }
473
474                         if n == vors.CookieLen {
475                                 var cookie vors.Cookie
476                                 copy(cookie[:], buf)
477                                 if c, ok := Cookies[cookie]; ok {
478                                         c <- from
479                                         close(c)
480                                         continue
481                                 }
482                         }
483
484                         sid = buf[0]
485                         peer = Peers[sid]
486                         if peer == nil {
487                                 slog.Info("unknown", "sid", sid, "from", from)
488                                 continue
489                         }
490
491                         if peer.addr == nil ||
492                                 from.Port != peer.addr.Port ||
493                                 !from.IP.Equal(peer.addr.IP) {
494                                 slog.Info("wrong addr",
495                                         "peer", peer.name,
496                                         "our", peer.addr,
497                                         "got", from)
498                                 continue
499                         }
500
501                         peer.stats.pktsRx++
502                         peer.stats.bytesRx += vors.IPHdrLen(from.IP) + 8 + uint64(n)
503                         if n == 1 {
504                                 continue
505                         }
506                         if n <= 4+siphash.Size {
507                                 peer.stats.bads++
508                                 continue
509                         }
510
511                         peer.mac.Reset()
512                         if _, err = peer.mac.Write(buf[:n-siphash.Size]); err != nil {
513                                 log.Fatal(err)
514                         }
515                         peer.mac.Sum(tag[:0])
516                         if subtle.ConstantTimeCompare(
517                                 tag[:siphash.Size],
518                                 buf[n-siphash.Size:n],
519                         ) != 1 {
520                                 peer.stats.bads++
521                                 continue
522                         }
523
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 {
528                                         continue
529                                 }
530                                 p.stats.pktsTx++
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)
534                                 }
535                         }
536                         peer.room.peersM.RUnlock()
537                 }
538         }()
539
540         go func() {
541                 <-LoggerReady
542                 slog.Info("listening",
543                         "bind", *bind,
544                         "pub", base64.RawURLEncoding.EncodeToString(Pub))
545                 for {
546                         conn, err := lnTCP.AcceptTCP()
547                         if err != nil {
548                                 log.Fatalln("accept:", err)
549                         }
550                         go newPeer(conn)
551                 }
552         }()
553
554         if *NoGUI {
555                 <-make(chan struct{})
556         }
557         err = GUI.MainLoop()
558         if err != nil && err != gocui.ErrQuit {
559                 log.Fatal(err)
560         }
561 }