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/>.
36 "github.com/dustin/go-humanize"
37 "github.com/jroimartin/gocui"
38 vors "go.stargrave.org/vors/internal"
39 "golang.org/x/crypto/blake2s"
40 "golang.org/x/crypto/chacha20poly1305"
45 MinVersion: tls.VersionTLS13,
46 CurvePreferences: []tls.CurveID{tls.X25519},
49 Passwd = flag.String("passwd", "", "Shared password")
50 Peers = map[byte]*Peer{}
63 func newPeer(connRaw net.Conn) {
64 logger := slog.With("remote", connRaw.RemoteAddr().String())
65 logger.Info("connected")
67 if len(Peers) == 256 {
68 logger.Error("too many peers")
71 conn := tls.Server(connRaw, TLSCfg)
72 err := conn.Handshake()
74 logger.Error("handshake:", "err", err)
79 scanner := bufio.NewScanner(conn)
80 peer := Peer{conn: conn, stats: &Stats{dead: make(chan struct{})}}
81 peer.addr = net.UDPAddrFromAddrPort(
82 netip.MustParseAddrPort(conn.RemoteAddr().String()))
87 chlng := make([]byte, 16)
88 if _, err = io.ReadFull(rand.Reader, chlng); err != nil {
92 chlngHex := hex.EncodeToString(chlng)
93 if _, err = io.Copy(conn, strings.NewReader(chlngHex+"\n")); err != nil {
94 logger.Error("write challenge:", "err", err)
97 h, err := blake2s.New256([]byte(*Passwd))
101 h.Write([]byte(chlngHex))
103 logger.Error("read password:", "err", scanner.Err())
106 cols := strings.Fields(scanner.Text())
108 logger.Error("no name")
109 io.Copy(conn, strings.NewReader("no name\n"))
113 if peer.name == "myself" {
114 logger.Error("reserved name")
115 io.Copy(conn, strings.NewReader("reserved name\n"))
118 logger = logger.With("name", cols[1])
119 if hex.EncodeToString(h.Sum(nil)) != cols[0] {
120 logger.Error("wrong password")
121 io.Copy(conn, strings.NewReader("wrong password\n"))
124 for _, p := range Peers {
125 if p.name == peer.name {
126 logger.Error("name already taken")
127 io.Copy(conn, strings.NewReader("name already taken\n"))
134 for i = 0; i <= 255; i++ {
135 if _, ok = Peers[i]; !ok {
140 Peers[peer.sid] = &peer
142 logger = logger.With("sid", peer.sid)
143 logger.Info("authenticated")
145 logger.Info("removing")
147 delete(Peers, peer.sid)
148 close(peer.stats.dead)
149 s := fmt.Sprintf("%s %d\n", vors.CmdDel, peer.sid)
150 for _, p := range Peers {
151 go io.Copy(p.conn, strings.NewReader(s))
155 if _, err = io.Copy(conn, strings.NewReader(
156 fmt.Sprintf("OK %d\n", peer.sid))); err != nil {
157 logger.Error("write ok:", "err", err)
160 for _, p := range Peers {
161 if p.sid == peer.sid {
164 if _, err = io.Copy(conn, strings.NewReader(fmt.Sprintf(
165 "%s %d %s %s\n", vors.CmdAdd, p.sid, p.name, hex.EncodeToString(p.key),
167 logger.Error("write ADD:", "err", err)
171 tlsState := conn.ConnectionState()
172 peer.key, err = tlsState.ExportKeyingMaterial(
173 strconv.Itoa(int(peer.sid)), nil, chacha20poly1305.KeySize)
178 // assume atomic write
179 s := fmt.Sprintf("%s %d %s %s\n",
180 vors.CmdAdd, peer.sid, peer.name, hex.EncodeToString(peer.key))
181 for _, p := range Peers {
182 if p.sid == peer.sid {
185 go io.Copy(p.conn, strings.NewReader(s))
189 go func(seen *time.Time) {
190 for now := range time.Tick(vors.PingTime) {
191 if seen.Add(2 * vors.PingTime).Before(now) {
192 logger.Error("timeout:", "seen", seen)
198 go func(stats *Stats) {
202 tick := time.Tick(vors.ScreenRefresh)
208 GUI.DeleteView(peer.name)
212 "Rx/Tx: %s / %s | %s / %s",
213 humanize.Comma(stats.pktsRx),
214 humanize.Comma(stats.pktsTx),
215 humanize.IBytes(stats.bytesRx),
216 humanize.IBytes(stats.bytesTx),
218 if stats.last.Add(time.Second).After(now) {
219 s += " | " + vors.CGreen + "TALK" + vors.CReset
221 v, err = GUI.View(peer.name)
230 if scanner.Text() == vors.CmdPing {
231 if _, err = io.Copy(conn,
232 strings.NewReader(vors.CmdPong+"\n")); err != nil {
233 logger.Error("write ok:", "err", err)
239 if scanner.Err() != nil {
240 logger.Error(scanner.Err().Error())
246 bind := flag.String("bind", "[::1]:12345", "TCP/UDP port to listen on")
247 pemFile := flag.String("pem", "keypair.pem", "PEM with keypair")
249 log.SetFlags(log.Lmicroseconds | log.Lshortfile)
251 log.Fatal("no -passwd specified")
253 if err := parsePEM(*pemFile); err != nil {
257 addrTCP, err := net.ResolveTCPAddr("tcp", *bind)
261 addrUDP, err := net.ResolveUDPAddr("udp", *bind)
265 lnTCP, err := net.ListenTCP("tcp", addrTCP)
269 lnUDP, err := net.ListenUDP("udp", addrUDP)
274 LoggerReady := make(chan struct{})
277 slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil)))
280 GUI, err = gocui.NewGui(gocui.OutputNormal)
285 GUI.SetManagerFunc(guiLayout)
286 if err := GUI.SetKeybinding("", gocui.KeyF10, gocui.ModNone, guiQuit); err != nil {
292 v, err := GUI.View("logs")
296 slog.SetDefault(slog.New(slog.NewTextHandler(v, nil)))
299 time.Sleep(vors.ScreenRefresh)
300 GUI.Update(func(gui *gocui.Gui) error {
309 buf := make([]byte, 2*vors.FrameLen)
311 var from *net.UDPAddr
316 n, from, err = lnUDP.ReadFromUDP(buf)
318 log.Fatalln("recvfrom:", err)
323 slog.Info("unknown:", "sid", sid, "from", from)
326 if from.Port != peer.addr.Port || !from.IP.Equal(peer.addr.IP) {
327 slog.Info("wrong addr:",
334 peer.stats.bytesRx += uint64(n)
338 peer.stats.last = time.Now()
339 for _, p := range Peers {
344 p.stats.bytesTx += uint64(n)
345 if _, err = lnUDP.WriteToUDP(buf[:n], p.addr); err != nil {
346 slog.Warn("sendto:", "peer", peer.name, "err", err)
354 slog.Info("listening", "bind", *bind, "spki", SPKI)
356 conn, err := lnTCP.Accept()
358 log.Fatalln("accept:", err)
365 dummy := make(chan struct{})
369 if err != nil && err != gocui.ErrQuit {