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/>.
35 "github.com/dustin/go-humanize"
36 "github.com/flynn/noise"
37 "github.com/jroimartin/gocui"
38 vors "go.stargrave.org/vors/internal"
39 "golang.org/x/crypto/blake2s"
40 "golang.org/x/crypto/chacha20"
41 "golang.org/x/crypto/poly1305"
46 MinVersion: tls.VersionTLS13,
47 CurvePreferences: []tls.CurveID{tls.X25519},
49 Peers = map[byte]*Peer{}
52 Cookies = map[vors.Cookie]chan *net.UDPAddr{}
55 func newPeer(conn *net.TCPConn) {
56 logger := slog.With("remote", conn.RemoteAddr().String())
57 logger.Info("connected")
59 if len(Peers) == 1<<8 {
60 logger.Error("too many peers")
63 err := conn.SetNoDelay(true)
65 log.Fatalln("nodelay:", err)
67 buf := make([]byte, len(vors.NoisePrologue))
69 if _, err = io.ReadFull(conn, buf); err != nil {
70 logger.Error("handshake: read prologue", "err", err)
73 if string(buf) != vors.NoisePrologue {
74 logger.Error("handshake: wrong prologue", "err", err)
78 hs, err := noise.NewHandshakeState(noise.Config{
79 CipherSuite: vors.NoiseCipherSuite,
80 Pattern: noise.HandshakeNK,
82 StaticKeypair: noise.DHKey{Private: Prv, Public: Pub},
83 Prologue: []byte(vors.NoisePrologue),
86 log.Fatalln("noise.NewHandshakeState:", err)
88 buf, err = vors.PktRead(conn)
90 logger.Error("read handshake", "err", err)
96 stats: &Stats{alive: make(chan struct{})},
97 rx: make(chan []byte),
98 tx: make(chan []byte, 10),
99 alive: make(chan struct{}),
102 name, _, _, err := hs.ReadMessage(nil, buf)
104 logger.Error("handshake: decrypt", "err", err)
107 peer.name = string(name)
109 logger = logger.With("name", peer.name)
111 for _, p := range Peers {
112 if p.name != peer.name {
115 logger.Error("name already taken")
116 buf, _, _, err = hs.WriteMessage(nil, []byte("name already taken"))
120 vors.PktWrite(conn, buf)
129 for i = 0; i <= (1<<8)-1; i++ {
130 if _, ok = Peers[i]; !ok {
137 Peers[peer.sid] = &peer
142 buf, _, _, err = hs.WriteMessage(nil, []byte("too many users"))
146 vors.PktWrite(conn, buf)
150 logger = logger.With("sid", peer.sid)
151 logger.Info("logged in")
154 logger.Info("removing")
156 delete(Peers, peer.sid)
158 close(peer.stats.alive)
159 s := []byte(fmt.Sprintf("%s %d", vors.CmdDel, peer.sid))
160 for _, p := range Peers {
161 go func(tx chan []byte) { tx <- s }(p.tx)
166 var cookie vors.Cookie
167 if _, err = io.ReadFull(rand.Reader, cookie[:]); err != nil {
168 log.Fatalln("cookie:", err)
170 gotCookie := make(chan *net.UDPAddr)
171 Cookies[cookie] = gotCookie
173 var txCS, rxCS *noise.CipherState
174 buf, txCS, rxCS, err := hs.WriteMessage(nil,
175 []byte(fmt.Sprintf("OK %s", hex.EncodeToString(cookie[:]))))
176 if err = vors.PktWrite(conn, buf); err != nil {
177 logger.Error("handshake write", "err", err)
178 delete(Cookies, cookie)
181 peer.rxCS, peer.txCS = txCS, rxCS
183 timeout := time.NewTimer(vors.PingTime)
185 case peer.addr = <-gotCookie:
187 logger.Error("cookie timeout")
188 delete(Cookies, cookie)
191 delete(Cookies, cookie)
192 logger.Info("got cookie", "addr", peer.addr)
198 peer.tx <- []byte(fmt.Sprintf("SID %d", peer.sid))
200 for _, p := range Peers {
201 if p.sid == peer.sid {
204 peer.tx <- []byte(fmt.Sprintf("%s %d %s %s",
205 vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key)))
209 h, err := blake2s.New256(hs.ChannelBinding())
213 h.Write([]byte(vors.NoisePrologue))
214 peer.key = h.Sum(nil)
218 s := []byte(fmt.Sprintf("%s %d %s %s",
219 vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key)))
220 for _, p := range Peers {
221 if p.sid != peer.sid {
228 go func(seen *time.Time) {
229 ticker := time.Tick(vors.PingTime)
234 if seen.Add(2 * vors.PingTime).Before(now) {
235 logger.Error("timeout:", "seen", seen)
245 go func(stats *Stats) {
249 tick := time.Tick(vors.ScreenRefresh)
255 GUI.DeleteView(peer.name)
259 "%s | Rx/Tx/Bad: %s / %s / %s | %s / %s",
261 humanize.Comma(stats.pktsRx),
262 humanize.Comma(stats.pktsTx),
263 humanize.Comma(stats.bads),
264 humanize.IBytes(stats.bytesRx),
265 humanize.IBytes(stats.bytesTx),
267 if stats.last.Add(vors.ScreenRefresh).After(now) {
268 s += " | " + vors.CGreen + "TALK" + vors.CReset
270 v, err = GUI.View(peer.name)
279 for buf := range peer.rx {
280 if string(buf) == vors.CmdPing {
282 peer.tx <- []byte(vors.CmdPong)
288 bind := flag.String("bind", "[::1]:"+strconv.Itoa(vors.DefaultPort),
289 "Host:TCP/UDP port to listen on")
290 kpFile := flag.String("key", "key", "Path to keypair file")
292 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
295 data, err := os.ReadFile(*kpFile)
299 Prv, Pub = data[:len(data)/2], data[len(data)/2:]
302 lnTCP, err := net.ListenTCP("tcp",
303 net.TCPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
307 lnUDP, err := net.ListenUDP("udp",
308 net.UDPAddrFromAddrPort(netip.MustParseAddrPort(*bind)))
313 LoggerReady := make(chan struct{})
316 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
319 GUI, err = gocui.NewGui(gocui.OutputNormal)
324 GUI.SetManagerFunc(guiLayout)
325 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
331 v, err := GUI.View("logs")
335 slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
338 time.Sleep(vors.ScreenRefresh)
339 GUI.Update(func(gui *gocui.Gui) error {
348 buf := make([]byte, 2*vors.FrameLen)
350 var from *net.UDPAddr
354 var ciph *chacha20.Cipher
356 var mac *poly1305.MAC
357 tag := make([]byte, poly1305.TagSize)
358 nonce := make([]byte, 12)
360 n, from, err = lnUDP.ReadFromUDP(buf)
362 log.Fatalln("recvfrom:", err)
365 if n == vors.CookieLen {
366 var cookie vors.Cookie
368 if c, ok := Cookies[cookie]; ok {
372 slog.Info("unknown cookie", "cookie", cookie)
380 slog.Info("unknown", "sid", sid, "from", from)
384 if from.Port != peer.addr.Port || !from.IP.Equal(peer.addr.IP) {
385 slog.Info("wrong addr",
393 peer.stats.bytesRx += uint64(n)
397 if n <= 4+vors.TagLen {
402 copy(nonce[len(nonce)-4:], buf)
403 ciph, err = chacha20.NewUnauthenticatedCipher(peer.key, nonce)
408 ciph.XORKeyStream(macKey[:], macKey[:])
410 mac = poly1305.New(&macKey)
411 if _, err = mac.Write(buf[4 : n-vors.TagLen]); err != nil {
415 if subtle.ConstantTimeCompare(
417 buf[n-vors.TagLen:n],
423 peer.stats.last = time.Now()
424 for _, p := range Peers {
429 p.stats.bytesTx += uint64(n)
430 if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil {
431 slog.Warn("sendto", "peer", peer.name, "err", err)
439 slog.Info("listening", "bind", *bind, "pub", hex.EncodeToString(Pub))
441 conn, err := lnTCP.AcceptTCP()
443 log.Fatalln("accept:", err)
450 <-make(chan struct{})
453 if err != nil && err != gocui.ErrQuit {