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:
@@ -7,6 +7,7 @@ import { apiFetch, copyText, bytes, timeAgo, type NodeInfo, type PageProps, type
|
||||
import { IcoPlus, IcoTrash, IcoCopy, IcoQR, IcoKey, IcoRefresh, IcoSettings } from '../../components/icons';
|
||||
import ServerPicker, { serverName } from '../../components/ServerPicker';
|
||||
import PriorityChip from '../../components/PriorityChip';
|
||||
import TorrentChip from '../../components/TorrentChip';
|
||||
import './masterkeys.css';
|
||||
|
||||
// Вкладка «Мастер-ключи»: sen://-подписки для SenAWG. Мастер-ключ — это ссылка
|
||||
@@ -16,7 +17,7 @@ import './masterkeys.css';
|
||||
interface MasterKey { id: number; uuid: string; label: string; device_limit: number; devices: number; servers: number[]; created_at: number; deleting?: number[] }
|
||||
interface Device {
|
||||
id: number; device_id: string; device_name: string; platform: string; version: string; created_at: number; last_seen: number | null;
|
||||
rekey_requested: boolean; online: boolean; lastHandshake: number; rx: number; tx: number; priority?: Priority;
|
||||
rekey_requested: boolean; online: boolean; lastHandshake: number; rx: number; tx: number; priority?: Priority; torrent?: boolean;
|
||||
servers_total: number; servers_ok: number; // на скольких серверах ключа уже стоит его текущий ключ
|
||||
}
|
||||
|
||||
@@ -284,7 +285,8 @@ export default function MasterKeysPage({ token, showMsg }: PageProps) {
|
||||
<span className={`chip chip--${d.online ? 'online' : 'offline'}`}>
|
||||
<span className="chip-dot" />{d.online ? 'Онлайн' : 'Офлайн'}
|
||||
</span>
|
||||
<PriorityChip priority={d.priority} />
|
||||
<PriorityChip priority={d.priority} torrent={d.torrent} />
|
||||
<TorrentChip torrent={d.torrent} />
|
||||
<div className="mk-device-traffic">
|
||||
<span className="mk-metric"><i className="mk-metric-name" data-sfx=" rx">↓</i><b>{bytes(d.rx)}</b></span>
|
||||
<span className="mk-metric"><i className="mk-metric-name" data-sfx=" tx">↑</i><b>{bytes(d.tx)}</b></span>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '../../lib/shared';
|
||||
import { IcoPlus, IcoRefresh, IcoQR, IcoTrash, IcoGlobe } from '../../components/icons';
|
||||
import PriorityChip from '../../components/PriorityChip';
|
||||
import TorrentChip from '../../components/TorrentChip';
|
||||
import './users.css';
|
||||
|
||||
// Вкладка «Пользователи»: форма создания + таблица (десктоп) / карточки (мобильный)
|
||||
@@ -29,7 +30,7 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
||||
const data = await apiFetch('GET', '/api/users/stats', tok);
|
||||
setUsers(prev => prev.map(u => {
|
||||
const s = (data.users as User[] ?? []).find(x => x.name === u.name);
|
||||
return s ? { ...u, online: s.online, lastHandshake: s.lastHandshake, rx: s.rx, tx: s.tx, priority: s.priority } : u;
|
||||
return s ? { ...u, online: s.online, lastHandshake: s.lastHandshake, rx: s.rx, tx: s.tx, priority: s.priority, torrent: s.torrent } : u;
|
||||
}));
|
||||
} catch { /* silent */ }
|
||||
}, []);
|
||||
@@ -187,7 +188,7 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
||||
: 'никогда'}
|
||||
</span>
|
||||
</td>
|
||||
<td><PriorityChip priority={u.priority} /></td>
|
||||
<td><span className="user-chips"><PriorityChip priority={u.priority} torrent={u.torrent} /><TorrentChip torrent={u.torrent} /></span></td>
|
||||
<td>
|
||||
{u.name}
|
||||
{u.key_gen !== serverGen && (
|
||||
@@ -254,7 +255,8 @@ export default function UsersPage({ token, showMsg }: PageProps) {
|
||||
<div className="user-card-ip">
|
||||
<IcoGlobe />
|
||||
<span className="user-card-ip-text">{u.ip}</span>
|
||||
<PriorityChip priority={u.priority} />
|
||||
<PriorityChip priority={u.priority} torrent={u.torrent} />
|
||||
<TorrentChip torrent={u.torrent} />
|
||||
</div>
|
||||
<div className="user-card-metrics">
|
||||
<div className="metric-block">
|
||||
|
||||
@@ -47,6 +47,7 @@ LICENSE file in the root directory of this source tree.
|
||||
}
|
||||
.user-card-ip {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--neutral-text);
|
||||
@@ -78,3 +79,6 @@ LICENSE file in the root directory of this source tree.
|
||||
/* .tip-wrap на мобильной растягивается на всю ширину — метке это не нужно. */
|
||||
.user-gen { width: auto; }
|
||||
}
|
||||
|
||||
/* Чипы приоритета и торрентов в одной ячейке */
|
||||
.user-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; }
|
||||
|
||||
Reference in New Issue
Block a user