Block BitTorrent on the server

- awg-ctrl: optional nft table awg_torrent (GET/PUT /api/torrent, off by
  default). A connection is judged by its first packets, like Xray's
  sniffing: DHT, UDP trackers, uTP SYN, and with @ih support the plain
  TCP handshake and HTTP announce. A match marks the connection in
  conntrack and drops it whole; without @ih only the UDP part is used
- devices that tried are kept in a 10-minute set and flagged as
  "torrent" in user and device stats; with shaping on they sit in the
  background class while the flag lasts
- metrics: awg_torrent_block_enabled/active, awg_torrent_blocked_total,
  awg_torrent_devices and per-peer awg_peer_torrent
- panel: "Торренты" section in server settings, "торренты" chip on users
  and devices; Save sends only what changed
This commit is contained in:
2026-10-17 23:35:30 +03:00
parent cf6af5ce0a
commit c73da602d6
16 changed files with 575 additions and 55 deletions
+18 -2
View File
@@ -87,7 +87,8 @@ func (s *Server) routes() []route {
}
ps := stats[pub]
list = append(list, ob{{"name", n}, {"ip", ip}, {"key_gen", kg}, {"online", ps.Online},
{"lastHandshake", ps.LastHandshake}, {"rx", ps.Rx}, {"tx", ps.Tx}, {"priority", s.shaping.PriorityOf(ip)}})
{"lastHandshake", ps.LastHandshake}, {"rx", ps.Rx}, {"tx", ps.Tx}, {"priority", s.shaping.PriorityOf(ip)},
{"torrent", s.torrent.Seen(ip)}})
}
rows.Close()
return ok(ob{{"users", list}})
@@ -147,7 +148,7 @@ func (s *Server) routes() []route {
ps := stats[p.PubKey]
list = append(list, ob{{"pub_key", p.PubKey}, {"ip", p.IP}, {"psk_key", p.PskKey}, {"owner", p.Owner},
{"online", ps.Online}, {"lastHandshake", ps.LastHandshake}, {"rx", ps.Rx}, {"tx", ps.Tx},
{"priority", s.shaping.PriorityOf(p.IP)}})
{"priority", s.shaping.PriorityOf(p.IP)}, {"torrent", s.torrent.Seen(p.IP)}})
}
return ok(ob{{"peers", list}})
}),
@@ -357,6 +358,21 @@ func (s *Server) routes() []route {
return ok(s.shaping.Status())
}),
// Блокировка BitTorrent. Тоже под /api/ и через PUT — чтобы проходила через awg-agent на ноде.
R("GET", "/api/torrent", false, a, func(c *rctx) (result, error) { return ok(s.torrent.Status()) }),
R("PUT", "/api/torrent", false, a, func(c *rctx) (result, error) {
enabled, isBool := c.obj().Get2("enabled").(bool)
if !isBool {
return result{}, httpErr(400, "enabled должен быть true/false")
}
// nft не нашёлся или ядро отказало: оператору нужна причина, а не 500.
if err := s.torrent.Update(TorrentSettings{Enabled: enabled}); err != nil {
return result{}, httpErr(500, err.Error())
}
return ok(s.torrent.Status())
}),
R("GET", "/api/metrics", false, a, func(c *rctx) (result, error) {
d, err := s.metricsCached()
if err != nil {