mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-01 17:00:21 +08:00
refactor(proxy): 将 aether-proxy 从 HMAC 正向代理迁移到 WebSocket 隧道模式
移除 HMAC 认证、TLS 自签名证书、HTTP CONNECT 代理和代发(delegate)模式, 改为 aether-proxy 主动通过 WebSocket 连接 Aether 服务端建立隧道。 Aether 服务端新增: - WebSocket 隧道端点 (proxy_tunnel.py) - TunnelManager 管理隧道连接和请求分发 - TunnelTransport 作为 httpx 自定义 transport 层 - 基于二进制帧的隧道协议 (tunnel_protocol.py) aether-proxy (Rust) 重构: - 新增 tunnel 模块 (client/dispatcher/stream_handler/protocol) - 支持多 Aether 服务端连接 ([[servers]] 配置) - 移除 proxy/auth/delegate 模块和 hyper 依赖 - 改用 tokio-tungstenite 实现 WebSocket 客户端 同时: - 添加浏览器指纹 Headers 绕过 Cloudflare 防护 - 删除节点时自动清理 Provider/Endpoint 的代理引用 - 数据库迁移: 新增 tunnel_mode/tunnel_connected/tunnel_connected_at 字段
This commit is contained in:
@@ -5,7 +5,6 @@ export interface ProxyNodeRemoteConfig {
|
||||
allowed_ports?: number[]
|
||||
log_level?: string
|
||||
heartbeat_interval?: number
|
||||
timestamp_tolerance?: number
|
||||
}
|
||||
|
||||
export interface ProxyNode {
|
||||
@@ -16,6 +15,9 @@ export interface ProxyNode {
|
||||
region: string | null
|
||||
status: 'online' | 'unhealthy' | 'offline'
|
||||
is_manual: boolean
|
||||
tunnel_mode: boolean
|
||||
tunnel_connected: boolean
|
||||
tunnel_connected_at: string | null
|
||||
// 手动节点专用字段
|
||||
proxy_url?: string
|
||||
proxy_username?: string
|
||||
@@ -101,9 +103,4 @@ export const proxyNodesApi = {
|
||||
const response = await apiClient.post<ProxyNodeTestResult>('/api/admin/proxy-nodes/test-url', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
async getHmacKey(): Promise<{ proxy_hmac_key: string }> {
|
||||
const response = await apiClient.get<{ proxy_hmac_key: string }>('/api/admin/proxy-nodes/hmac-key')
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
|
||||
@@ -95,16 +95,6 @@
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="h-4 w-px bg-border" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
title="复制 HMAC Key"
|
||||
@click="copyHmacKey"
|
||||
>
|
||||
<Copy class="w-3.5 h-3.5" />
|
||||
</Button>
|
||||
<div class="h-4 w-px bg-border" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@@ -172,11 +162,18 @@
|
||||
>
|
||||
手动
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="node.tunnel_mode"
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
Tunnel
|
||||
</Badge>
|
||||
<HardwareTooltip :node="node" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-4">
|
||||
<code class="text-xs text-muted-foreground">{{ node.is_manual ? (node.proxy_url || `${node.ip}:${node.port}`) : `${node.ip}:${node.port}` }}</code>
|
||||
<code class="text-xs text-muted-foreground">{{ nodeAddress(node) }}</code>
|
||||
</TableCell>
|
||||
<TableCell class="py-4">
|
||||
<span class="text-sm text-muted-foreground">{{ formatRegion(node.region) }}</span>
|
||||
@@ -282,9 +279,16 @@
|
||||
>
|
||||
手动
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="node.tunnel_mode"
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
Tunnel
|
||||
</Badge>
|
||||
<HardwareTooltip :node="node" />
|
||||
</div>
|
||||
<code class="text-xs text-muted-foreground">{{ node.is_manual ? (node.proxy_url || `${node.ip}:${node.port}`) : `${node.ip}:${node.port}` }}</code>
|
||||
<code class="text-xs text-muted-foreground">{{ nodeAddress(node) }}</code>
|
||||
</div>
|
||||
<Badge
|
||||
:variant="statusVariant(node.status)"
|
||||
@@ -521,15 +525,6 @@
|
||||
max="600"
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<Label>时间戳容差 (秒)</Label>
|
||||
<Input
|
||||
v-model="configForm.timestamp_tolerance"
|
||||
type="number"
|
||||
min="10"
|
||||
max="3600"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="configNode"
|
||||
@@ -560,7 +555,6 @@
|
||||
import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { useProxyNodesStore } from '@/stores/proxy-nodes'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { useClipboard } from '@/composables/useClipboard'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { proxyNodesApi, type ProxyNode, type ProxyNodeRemoteConfig } from '@/api/proxy-nodes'
|
||||
|
||||
@@ -586,13 +580,12 @@ import {
|
||||
Dialog,
|
||||
} from '@/components/ui'
|
||||
|
||||
import { Search, Trash2, Plus, SquarePen, Activity, Loader2, Settings, Copy } from 'lucide-vue-next'
|
||||
import { Search, Trash2, Plus, SquarePen, Activity, Loader2, Settings } from 'lucide-vue-next'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { formatRegion } from '@/utils/region'
|
||||
import HardwareTooltip from './components/HardwareTooltip.vue'
|
||||
|
||||
const { success, error: toastError } = useToast()
|
||||
const { copyToClipboard } = useClipboard()
|
||||
const { confirmDanger } = useConfirm()
|
||||
const store = useProxyNodesStore()
|
||||
|
||||
@@ -621,7 +614,6 @@ const configForm = ref({
|
||||
allowed_ports: '',
|
||||
log_level: 'info',
|
||||
heartbeat_interval: '30',
|
||||
timestamp_tolerance: '300',
|
||||
})
|
||||
|
||||
// 测试连通性
|
||||
@@ -686,15 +678,6 @@ async function handleTestUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
async function copyHmacKey() {
|
||||
try {
|
||||
const { proxy_hmac_key } = await proxyNodesApi.getHmacKey()
|
||||
await copyToClipboard(proxy_hmac_key)
|
||||
} catch (err: unknown) {
|
||||
toastError(parseApiError(err, '获取 HMAC Key 失败'))
|
||||
}
|
||||
}
|
||||
|
||||
function handleEdit(node: ProxyNode) {
|
||||
editingNode.value = node
|
||||
addForm.value = {
|
||||
@@ -766,7 +749,6 @@ function handleConfig(node: ProxyNode) {
|
||||
allowed_ports: rc.allowed_ports?.join(', ') || '',
|
||||
log_level: rc.log_level || 'info',
|
||||
heartbeat_interval: String(rc.heartbeat_interval || node.heartbeat_interval || 30),
|
||||
timestamp_tolerance: String(rc.timestamp_tolerance || 300),
|
||||
}
|
||||
showConfigDialog.value = true
|
||||
}
|
||||
@@ -800,10 +782,6 @@ async function handleSaveConfig() {
|
||||
if (!isNaN(hb) && hb >= 5) {
|
||||
data.heartbeat_interval = hb
|
||||
}
|
||||
const tt = parseInt(configForm.value.timestamp_tolerance)
|
||||
if (!isNaN(tt) && tt >= 10) {
|
||||
data.timestamp_tolerance = tt
|
||||
}
|
||||
await proxyNodesApi.updateNodeConfig(configNode.value.id, data)
|
||||
success('远程配置已保存,将在下次心跳时生效')
|
||||
handleConfigDialogClose(false)
|
||||
@@ -817,7 +795,7 @@ async function handleSaveConfig() {
|
||||
|
||||
async function handleDelete(node: ProxyNode) {
|
||||
const confirmed = await confirmDanger(
|
||||
`确定要删除代理节点 "${node.name}" (${node.ip}:${node.port}) 吗?`,
|
||||
`确定要删除代理节点 "${node.name}" (${node.tunnel_mode ? node.ip : `${node.ip}:${node.port}`}) 吗?`,
|
||||
'删除节点'
|
||||
)
|
||||
if (!confirmed) return
|
||||
@@ -890,4 +868,10 @@ function formatTime(iso: string | null) {
|
||||
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`
|
||||
return d.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
function nodeAddress(node: ProxyNode) {
|
||||
if (node.is_manual) return node.proxy_url || `${node.ip}:${node.port}`
|
||||
if (node.tunnel_mode) return node.ip || 'WebSocket Tunnel'
|
||||
return `${node.ip}:${node.port}`
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user