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/>.
37 "github.com/dustin/go-humanize"
38 "github.com/jroimartin/gocui"
39 vors "go.stargrave.org/vors/internal"
40 "golang.org/x/crypto/blake2s"
41 "golang.org/x/crypto/chacha20"
42 "golang.org/x/crypto/chacha20poly1305"
43 "golang.org/x/crypto/poly1305"
48 MinVersion: tls.VersionTLS13,
49 CurvePreferences: []tls.CurveID{tls.X25519},
52 Passwd = flag.String("passwd", "", "Shared password")
53 Peers = map[byte]*Peer{}
66 func newPeer(connRaw net.Conn) {
67 logger := slog.With("remote", connRaw.RemoteAddr().String())
68 logger.Info("connected")
70 if len(Peers) == 256 {
71 logger.Error("too many peers")
74 conn := tls.Server(connRaw, TLSCfg)
75 err := conn.Handshake()
77 logger.Error("handshake:", "err", err)
82 scanner := bufio.NewScanner(conn)
83 peer := Peer{conn: conn, stats: &Stats{dead: make(chan struct{})}}
84 peer.addr = net.UDPAddrFromAddrPort(
85 netip.MustParseAddrPort(conn.RemoteAddr().String()))
90 chlng := make([]byte, 16)
91 if _, err = io.ReadFull(rand.Reader, chlng); err != nil {
95 chlngHex := hex.EncodeToString(chlng)
96 if _, err = io.Copy(conn, strings.NewReader(chlngHex+"\n")); err != nil {
97 logger.Error("write challenge:", "err", err)
100 h, err := blake2s.New256([]byte(*Passwd))
104 h.Write([]byte(chlngHex))
106 logger.Error("read password:", "err", scanner.Err())
109 cols := strings.Fields(scanner.Text())
111 logger.Error("no name")
112 io.Copy(conn, strings.NewReader("no name\n"))
116 if peer.name == "myself" {
117 logger.Error("reserved name")
118 io.Copy(conn, strings.NewReader("reserved name\n"))
121 logger = logger.With("name", cols[1])
122 if hex.EncodeToString(h.Sum(nil)) != cols[0] {
123 logger.Error("wrong password")
124 io.Copy(conn, strings.NewReader("wrong password\n"))
127 for _, p := range Peers {
128 if p.name == peer.name {
129 logger.Error("name already taken")
130 io.Copy(conn, strings.NewReader("name already taken\n"))
137 for i = 0; i <= 255; i++ {
138 if _, ok = Peers[i]; !ok {
143 Peers[peer.sid] = &peer
145 logger = logger.With("sid", peer.sid)
146 logger.Info("authenticated")
148 logger.Info("removing")
150 delete(Peers, peer.sid)
151 close(peer.stats.dead)
152 s := fmt.Sprintf("%s %d\n", vors.CmdDel, peer.sid)
153 for _, p := range Peers {
154 go io.Copy(p.conn, strings.NewReader(s))
158 if _, err = io.Copy(conn, strings.NewReader(
159 fmt.Sprintf("OK %d\n", peer.sid))); err != nil {
160 logger.Error("write ok:", "err", err)
163 for _, p := range Peers {
164 if p.sid == peer.sid {
167 if _, err = io.Copy(conn, strings.NewReader(fmt.Sprintf(
168 "%s %d %s %s\n", vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key),
170 logger.Error("write ADD:", "err", err)
174 tlsState := conn.ConnectionState()
175 peer.key, err = tlsState.ExportKeyingMaterial(
176 strconv.Itoa(int(peer.sid)), nil, chacha20poly1305.KeySize)
181 // assume atomic write
182 s := fmt.Sprintf("%s %d %s %s\n",
183 vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key))
184 for _, p := range Peers {
185 if p.sid == peer.sid {
188 go io.Copy(p.conn, strings.NewReader(s))
192 go func(seen *time.Time) {
193 for now := range time.Tick(vors.PingTime) {
194 if seen.Add(2 * vors.PingTime).Before(now) {
195 logger.Error("timeout:", "seen", seen)
201 go func(stats *Stats) {
205 tick := time.Tick(vors.ScreenRefresh)
211 GUI.DeleteView(peer.name)
215 "Rx/Tx: %s / %s | %s / %s",
216 humanize.Comma(stats.pktsRx),
217 humanize.Comma(stats.pktsTx),
218 humanize.IBytes(stats.bytesRx),
219 humanize.IBytes(stats.bytesTx),
221 if stats.last.Add(time.Second).After(now) {
222 s += " | " + vors.CGreen + "TALK" + vors.CReset
224 v, err = GUI.View(peer.name)
233 if scanner.Text() == vors.CmdPing {
234 if _, err = io.Copy(conn,
235 strings.NewReader(vors.CmdPong+"\n")); err != nil {
236 logger.Error("write ok:", "err", err)
242 if scanner.Err() != nil {
243 logger.Error(scanner.Err().Error())
249 bind := flag.String("bind", "[::1]:12345", "TCP/UDP port to listen on")
250 pemFile := flag.String("pem", "keypair.pem", "PEM with keypair")
252 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
254 log.Fatal("no -passwd specified")
256 if err := parsePEM(*pemFile); err != nil {
260 addrTCP, err := net.ResolveTCPAddr("tcp", *bind)
264 addrUDP, err := net.ResolveUDPAddr("udp", *bind)
268 lnTCP, err := net.ListenTCP("tcp", addrTCP)
272 lnUDP, err := net.ListenUDP("udp", addrUDP)
277 LoggerReady := make(chan struct{})
280 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
283 GUI, err = gocui.NewGui(gocui.OutputNormal)
288 GUI.SetManagerFunc(guiLayout)
289 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
295 v, err := GUI.View("logs")
299 slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
302 time.Sleep(vors.ScreenRefresh)
303 GUI.Update(func(gui *gocui.Gui) error {
312 buf := make([]byte, 2*vors.FrameLen)
314 var from *net.UDPAddr
318 var ciph *chacha20.Cipher
320 var mac *poly1305.MAC
321 tag := make([]byte, poly1305.TagSize)
322 nonce := make([]byte, 12)
324 n, from, err = lnUDP.ReadFromUDP(buf)
326 log.Fatalln("recvfrom:", err)
331 slog.Info("unknown:", "sid", sid, "from", from)
334 if from.Port != peer.addr.Port || !from.IP.Equal(peer.addr.IP) {
335 slog.Info("wrong addr:",
342 peer.stats.bytesRx += uint64(n)
346 if n <= 4+vors.TagLen {
347 slog.Info("too small:", "peer", peer.name, "len", n)
351 copy(nonce[len(nonce)-4:], buf)
352 ciph, err = chacha20.NewUnauthenticatedCipher(peer.key, nonce)
357 ciph.XORKeyStream(macKey[:], macKey[:])
359 mac = poly1305.New(&macKey)
360 if _, err = mac.Write(buf[4 : n-vors.TagLen]); err != nil {
364 if subtle.ConstantTimeCompare(
366 buf[n-vors.TagLen:n],
368 log.Println("decrypt:", peer.name, "tag differs")
369 slog.Info("MAC failed:", "peer", peer.name, "len", n)
373 peer.stats.last = time.Now()
374 for _, p := range Peers {
379 p.stats.bytesTx += uint64(n)
380 if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil {
381 slog.Warn("sendto:", "peer", peer.name, "err", err)
389 slog.Info("listening", "bind", *bind, "spki", SPKI)
391 conn, err := lnTCP.Accept()
393 log.Fatalln("accept:", err)
400 dummy := make(chan struct{})
404 if err != nil && err != gocui.ErrQuit {