feat: 添加IP竞速拨号器与IP偏好设置,连接状态显示域名与实际IP
- 新增竞速拨号器(racedial.go):域名连接时并行解析所有A/AAAA记录, 同时对所有IP发起TCP连接,第一个成功的胜出,确保选择延迟最低的地址 - ServerProfile新增IPPreference字段(auto/v4/v6),支持用户指定IP版本偏好 - 填写服务器IP时跳过域名解析直接使用IP,顺序failover - 连接成功后状态栏显示「已连接 (域名 -> 实际IP)」 - DB迁移v6:server_profiles表添加ip_preference列 - WebSocket拨号与HTTP登录均使用竞速拨号器 - 补充中英文i18n翻译
This commit is contained in:
+19
-1
@@ -55,7 +55,10 @@ func (s *Store) migrate() error {
|
||||
if err := s.migrateV4(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.migrateV5()
|
||||
if err := s.migrateV5(); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.migrateV6()
|
||||
}
|
||||
|
||||
func (s *Store) migrateV2() error {
|
||||
@@ -290,6 +293,20 @@ func (s *Store) migrateV5() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// migrateV6 adds the ip_preference column to server_profiles for
|
||||
// controlling IPv4/IPv6 address selection when connecting by hostname.
|
||||
// Idempotent: skips if the column already exists.
|
||||
func (s *Store) migrateV6() error {
|
||||
if columnExists(s.db, "server_profiles", "ip_preference") {
|
||||
return nil
|
||||
}
|
||||
_, err := s.db.Exec(`ALTER TABLE server_profiles ADD COLUMN ip_preference TEXT NOT NULL DEFAULT 'auto'`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("migrate v6 add ip_preference: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// splitCIDRsByFamily splits a comma-separated CIDR string into IPv4 and
|
||||
// IPv6 parts. Used for migration from the old custom_cidrs column.
|
||||
func splitCIDRsByFamily(customCIDRs string) (v4, v6 string) {
|
||||
@@ -354,6 +371,7 @@ CREATE TABLE IF NOT EXISTS server_profiles (
|
||||
cidr_v6_urls TEXT NOT NULL DEFAULT '',
|
||||
mtu_override INTEGER NOT NULL DEFAULT 0,
|
||||
auto_connect INTEGER NOT NULL DEFAULT 0,
|
||||
ip_preference TEXT NOT NULL DEFAULT 'auto',
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_connected_at DATETIME
|
||||
);
|
||||
|
||||
+12
-6
@@ -16,13 +16,15 @@ func (s *Store) CreateProfile(p *model.ServerProfile) (int64, error) {
|
||||
username, auth_mode, routing_mode,
|
||||
cidr_v4, cidr_v6, cidr_v4_urls, cidr_v6_urls,
|
||||
mtu_override, auto_connect,
|
||||
tls_ca_cert, tls_ca_path, tls_insecure, tls_pinned_hash)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
tls_ca_cert, tls_ca_path, tls_insecure, tls_pinned_hash,
|
||||
ip_preference)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
||||
p.Name, p.Protocol, p.Host, p.ServerIPs, p.Port, p.Path,
|
||||
p.Username, p.AuthMode, p.RoutingMode,
|
||||
p.CIDRV4, p.CIDRV6, p.CIDRV4URLs, p.CIDRV6URLs,
|
||||
p.MTUOverride, p.AutoConnect,
|
||||
p.TLSCACert, p.TLSCAPath, p.TLSInsecure, p.TLSPinnedHash,
|
||||
p.IPPreference,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("insert profile: %w", err)
|
||||
@@ -43,6 +45,7 @@ func (s *Store) GetProfile(id int64) (*model.ServerProfile, error) {
|
||||
cidr_v4, cidr_v6, cidr_v4_urls, cidr_v6_urls,
|
||||
mtu_override, auto_connect,
|
||||
tls_ca_cert, tls_ca_path, tls_insecure, tls_pinned_hash,
|
||||
ip_preference,
|
||||
created_at, last_connected_at
|
||||
FROM server_profiles WHERE id = ?`, id,
|
||||
).Scan(&p.ID, &p.Name, &p.Protocol, &p.Host, &p.ServerIPs, &p.Port, &p.Path,
|
||||
@@ -50,7 +53,7 @@ func (s *Store) GetProfile(id int64) (*model.ServerProfile, error) {
|
||||
&p.CIDRV4, &p.CIDRV6, &p.CIDRV4URLs, &p.CIDRV6URLs,
|
||||
&p.MTUOverride, &p.AutoConnect,
|
||||
&p.TLSCACert, &p.TLSCAPath, &p.TLSInsecure, &p.TLSPinnedHash,
|
||||
&p.CreatedAt, &last)
|
||||
&p.IPPreference, &p.CreatedAt, &last)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get profile %d: %w", id, err)
|
||||
}
|
||||
@@ -68,6 +71,7 @@ func (s *Store) ListProfiles() ([]model.ServerProfile, error) {
|
||||
cidr_v4, cidr_v6, cidr_v4_urls, cidr_v6_urls,
|
||||
mtu_override, auto_connect,
|
||||
tls_ca_cert, tls_ca_path, tls_insecure, tls_pinned_hash,
|
||||
ip_preference,
|
||||
created_at, last_connected_at
|
||||
FROM server_profiles ORDER BY name`)
|
||||
if err != nil {
|
||||
@@ -84,7 +88,7 @@ func (s *Store) ListProfiles() ([]model.ServerProfile, error) {
|
||||
&p.CIDRV4, &p.CIDRV6, &p.CIDRV4URLs, &p.CIDRV6URLs,
|
||||
&p.MTUOverride, &p.AutoConnect,
|
||||
&p.TLSCACert, &p.TLSCAPath, &p.TLSInsecure, &p.TLSPinnedHash,
|
||||
&p.CreatedAt, &last); err != nil {
|
||||
&p.IPPreference, &p.CreatedAt, &last); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if last.Valid {
|
||||
@@ -103,13 +107,15 @@ func (s *Store) UpdateProfile(p *model.ServerProfile) error {
|
||||
username = ?, auth_mode = ?, routing_mode = ?,
|
||||
cidr_v4 = ?, cidr_v6 = ?, cidr_v4_urls = ?, cidr_v6_urls = ?,
|
||||
mtu_override = ?, auto_connect = ?,
|
||||
tls_ca_cert = ?, tls_ca_path = ?, tls_insecure = ?, tls_pinned_hash = ?
|
||||
tls_ca_cert = ?, tls_ca_path = ?, tls_insecure = ?, tls_pinned_hash = ?,
|
||||
ip_preference = ?
|
||||
WHERE id = ?`,
|
||||
p.Name, p.Protocol, p.Host, p.ServerIPs, p.Port, p.Path,
|
||||
p.Username, p.AuthMode, p.RoutingMode,
|
||||
p.CIDRV4, p.CIDRV6, p.CIDRV4URLs, p.CIDRV6URLs,
|
||||
p.MTUOverride, p.AutoConnect,
|
||||
p.TLSCACert, p.TLSCAPath, p.TLSInsecure, p.TLSPinnedHash, p.ID)
|
||||
p.TLSCACert, p.TLSCAPath, p.TLSInsecure, p.TLSPinnedHash,
|
||||
p.IPPreference, p.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update profile %d: %w", p.ID, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user