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/>.
36 "github.com/dchest/siphash"
37 "github.com/flynn/noise"
38 "github.com/jroimartin/gocui"
39 vors "go.stargrave.org/vors/internal"
40 "golang.org/x/crypto/blake2s"
41 "golang.org/x/crypto/chacha20"
46 MinVersion: tls.VersionTLS13,
47 CurvePreferences: []tls.CurveID{tls.X25519},
50 Cookies = map[vors.Cookie]chan *net.UDPAddr{}
53 func newPeer(conn *net.TCPConn) {
54 logger := slog.With("remote", conn.RemoteAddr().String())
55 logger.Info("connected")
57 err := conn.SetNoDelay(true)
59 log.Fatalln("nodelay:", err)
61 buf := make([]byte, len(vors.NoisePrologue))
63 if _, err = io.ReadFull(conn, buf); err != nil {
64 logger.Error("handshake: read prologue", "err", err)
67 if string(buf) != vors.NoisePrologue {
68 logger.Error("handshake: wrong prologue", "err", err)
72 hs, err := noise.NewHandshakeState(noise.Config{
73 CipherSuite: vors.NoiseCipherSuite,
74 Pattern: noise.HandshakeNK,
76 StaticKeypair: noise.DHKey{Private: Prv, Public: Pub},
77 Prologue: []byte(vors.NoisePrologue),
80 log.Fatalln("noise.NewHandshakeState:", err)
82 buf, err = vors.PktRead(conn)
84 logger.Error("read handshake", "err", err)
91 rx: make(chan []byte),
92 tx: make(chan []byte, 10),
93 alive: make(chan struct{}),
97 nameAndRoom, _, _, err := hs.ReadMessage(nil, buf)
99 logger.Error("handshake: decrypt", "err", err)
102 cols := strings.SplitN(string(nameAndRoom), " ", 3)
111 peer.name = string(cols[0])
112 logger = logger.With("name", peer.name, "room", roomName)
114 room = Rooms[roomName]
119 peers: make(map[byte]*Peer),
120 alive: make(chan struct{}),
122 Rooms[roomName] = room
127 tick := time.Tick(vors.ScreenRefresh)
133 GUI.DeleteView(room.name)
136 v, err = GUI.View(room.name)
139 v.Write([]byte(strings.Join(room.Stats(now), "\n")))
147 logger.Error("wrong password")
148 buf, _, _, err = hs.WriteMessage(nil, []byte("wrong password"))
152 vors.PktWrite(conn, buf)
158 for _, p := range room.peers {
159 if p.name != peer.name {
162 logger.Error("name already taken")
163 buf, _, _, err = hs.WriteMessage(nil, []byte("name already taken"))
167 vors.PktWrite(conn, buf)
176 for i = 0; i <= (1<<8)-1; i++ {
177 if _, ok = Peers[i]; !ok {
184 Peers[peer.sid] = peer
189 buf, _, _, err = hs.WriteMessage(nil, []byte("too many users"))
193 vors.PktWrite(conn, buf)
197 logger = logger.With("sid", peer.sid)
198 room.peers[peer.sid] = peer
199 logger.Info("logged in")
202 logger.Info("removing")
204 delete(Peers, peer.sid)
205 delete(room.peers, peer.sid)
207 s := []byte(fmt.Sprintf("%s %d", vors.CmdDel, peer.sid))
208 for _, p := range room.peers {
209 go func(tx chan []byte) { tx <- s }(p.tx)
214 var cookie vors.Cookie
215 if _, err = io.ReadFull(rand.Reader, cookie[:]); err != nil {
216 log.Fatalln("cookie:", err)
218 gotCookie := make(chan *net.UDPAddr)
219 Cookies[cookie] = gotCookie
221 var txCS, rxCS *noise.CipherState
222 buf, txCS, rxCS, err := hs.WriteMessage(nil,
223 []byte(fmt.Sprintf("OK %s", hex.EncodeToString(cookie[:]))))
225 log.Fatalln("hs.WriteMessage:", err)
227 if err = vors.PktWrite(conn, buf); err != nil {
228 logger.Error("handshake write", "err", err)
229 delete(Cookies, cookie)
232 peer.rxCS, peer.txCS = txCS, rxCS
234 timeout := time.NewTimer(vors.PingTime)
236 case peer.addr = <-gotCookie:
238 logger.Error("cookie timeout")
239 delete(Cookies, cookie)
242 delete(Cookies, cookie)
243 logger.Info("got cookie", "addr", peer.addr)
249 peer.tx <- []byte(fmt.Sprintf("SID %d", peer.sid))
251 for _, p := range room.peers {
252 if p.sid == peer.sid {
255 peer.tx <- []byte(fmt.Sprintf("%s %d %s %s",
256 vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key)))
260 xof, err := blake2s.NewXOF(chacha20.KeySize+16, nil)
264 xof.Write([]byte(vors.NoisePrologue))
265 xof.Write(hs.ChannelBinding())
266 peer.key = make([]byte, chacha20.KeySize+16)
267 if _, err = io.ReadFull(xof, peer.key); err != nil {
270 peer.mac = siphash.New(peer.key[chacha20.KeySize:])
274 s := []byte(fmt.Sprintf("%s %d %s %s",
275 vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key)))
276 for _, p := range room.peers {
277 if p.sid != peer.sid {
284 go func(seen *time.Time) {
285 ticker := time.Tick(vors.PingTime)
290 if seen.Add(2 * vors.PingTime).Before(now) {
291 logger.Error("timeout:", "seen", seen)
301 for buf := range peer.rx {
302 if string(buf) == vors.CmdPing {
304 peer.tx <- []byte(vors.CmdPong)
310 bind := flag.String("bind", "[::1]:"+strconv.Itoa(vors.DefaultPort),
311 "host:TCP/UDP port to listen on")
312 kpFile := flag.String("key", "key", "path to keypair file")
313 prefer4 := flag.Bool("4", false,
314 "Prefer obsolete legacy IPv4 address during name resolution")
315 version := flag.Bool("version", false, "print version")
316 warranty := flag.Bool("warranty", false, "print warranty information")
318 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
321 fmt.Println(vors.Warranty)
325 fmt.Println(vors.GetVersion())
330 data, err := os.ReadFile(*kpFile)
334 Prv, Pub = data[:len(data)/2], data[len(data)/2:]
337 vors.PreferIPv4 = *prefer4
338 lnTCP, err := net.ListenTCP("tcp",
339 net.TCPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
343 lnUDP, err := net.ListenUDP("udp",
344 net.UDPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
349 LoggerReady := make(chan struct{})
352 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
355 GUI, err = gocui.NewGui(gocui.OutputNormal)
360 GUI.SetManagerFunc(guiLayout)
361 if err := GUI.SetKeybinding("", 'q', gocui.ModNone, guiQuit); err != nil {
367 v, err := GUI.View("logs")
371 slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
374 time.Sleep(vors.ScreenRefresh)
375 GUI.Update(func(gui *gocui.Gui) error {
384 buf := make([]byte, 2*vors.FrameLen)
386 var from *net.UDPAddr
390 tag := make([]byte, siphash.Size)
392 n, from, err = lnUDP.ReadFromUDP(buf)
394 log.Fatalln("recvfrom:", err)
397 if n == vors.CookieLen {
398 var cookie vors.Cookie
400 if c, ok := Cookies[cookie]; ok {
410 slog.Info("unknown", "sid", sid, "from", from)
414 if peer.addr == nil ||
415 from.Port != peer.addr.Port ||
416 !from.IP.Equal(peer.addr.IP) {
417 slog.Info("wrong addr",
425 peer.stats.bytesRx += vors.IPHdrLen(from.IP) + 8 + uint64(n)
429 if n <= 4+siphash.Size {
435 if _, err = peer.mac.Write(buf[:n-siphash.Size]); err != nil {
438 peer.mac.Sum(tag[:0])
439 if subtle.ConstantTimeCompare(
441 buf[n-siphash.Size:n],
447 peer.stats.last = time.Now()
448 for _, p := range peer.room.peers {
449 if p.sid == sid || p.addr == nil {
453 p.stats.bytesTx += vors.IPHdrLen(p.addr.IP) + 8 + uint64(n)
454 if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil {
455 slog.Warn("sendto", "peer", peer.name, "err", err)
463 slog.Info("listening",
465 "pub", base64.RawURLEncoding.EncodeToString(Pub))
467 conn, err := lnTCP.AcceptTCP()
469 log.Fatalln("accept:", err)
476 <-make(chan struct{})
479 if err != nil && err != gocui.ErrQuit {