mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
265 lines
6.6 KiB
Go
265 lines
6.6 KiB
Go
package proxy
|
|
|
|
import (
|
|
"ant-chrome/backend/internal/apppath"
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/tls"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const xrayTLSInsecurePinKey = "_antInsecureSkipVerify"
|
|
|
|
var xrayTLSPinCache sync.Map
|
|
|
|
func (m *XrayManager) buildRuntimeConfig(key string, outbound map[string]interface{}, port int, dnsServers string) (string, error) {
|
|
return m.buildRuntimeConfigWithRoute(
|
|
key,
|
|
[]interface{}{outbound},
|
|
[]interface{}{
|
|
map[string]interface{}{
|
|
"type": "field",
|
|
"inboundTag": []string{"socks-in"},
|
|
"outboundTag": "proxy-out",
|
|
},
|
|
},
|
|
port,
|
|
dnsServers,
|
|
)
|
|
}
|
|
|
|
func (m *XrayManager) buildRuntimeConfigWithRoute(key string, outbounds []interface{}, rules []interface{}, port int, dnsServers string) (string, error) {
|
|
baseDir := m.resolveWorkdir(key)
|
|
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
|
return "", err
|
|
}
|
|
outbounds = sanitizeXrayOutbounds(outbounds)
|
|
outbounds = pinXrayInsecureTLSOutbounds(outbounds)
|
|
cfgPath := filepath.Join(baseDir, "xray-config.json")
|
|
cfg := map[string]interface{}{
|
|
"log": map[string]interface{}{
|
|
"loglevel": "warning",
|
|
"error": filepath.Join(baseDir, "xray-error.log"),
|
|
},
|
|
"inbounds": []interface{}{
|
|
map[string]interface{}{
|
|
"tag": "socks-in",
|
|
"port": port,
|
|
"listen": "127.0.0.1",
|
|
"protocol": "socks",
|
|
"settings": map[string]interface{}{
|
|
"udp": true,
|
|
},
|
|
"sniffing": xrayBrowserSniffingConfig(),
|
|
},
|
|
},
|
|
"outbounds": append(outbounds,
|
|
map[string]interface{}{
|
|
"protocol": "direct",
|
|
"tag": "direct",
|
|
},
|
|
map[string]interface{}{
|
|
"protocol": "blackhole",
|
|
"tag": "block",
|
|
},
|
|
),
|
|
"routing": map[string]interface{}{
|
|
"rules": rules,
|
|
},
|
|
}
|
|
if dnsCfg := parseDnsConfig(dnsServers); dnsCfg != nil {
|
|
cfg["dns"] = dnsCfg
|
|
} else {
|
|
cfg["dns"] = defaultXrayDNSConfig()
|
|
}
|
|
data, err := json.MarshalIndent(cfg, "", " ")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := os.WriteFile(cfgPath, data, 0o644); err != nil {
|
|
return "", err
|
|
}
|
|
return cfgPath, nil
|
|
}
|
|
|
|
func defaultXrayDNSConfig() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"servers": []interface{}{"223.5.5.5", "119.29.29.29"},
|
|
}
|
|
}
|
|
|
|
func sanitizeXrayOutbounds(outbounds []interface{}) []interface{} {
|
|
for _, outbound := range outbounds {
|
|
removeXrayDeprecatedFields(outbound)
|
|
}
|
|
return outbounds
|
|
}
|
|
|
|
func pinXrayInsecureTLSOutbounds(outbounds []interface{}) []interface{} {
|
|
for _, outbound := range outbounds {
|
|
pinXrayInsecureTLSOutbound(outbound)
|
|
}
|
|
return outbounds
|
|
}
|
|
|
|
func pinXrayInsecureTLSOutbound(outbound interface{}) {
|
|
item, ok := outbound.(map[string]interface{})
|
|
if !ok {
|
|
return
|
|
}
|
|
stream, _ := item["streamSettings"].(map[string]interface{})
|
|
tlsSettings, _ := stream["tlsSettings"].(map[string]interface{})
|
|
if tlsSettings == nil || !truthy(tlsSettings[xrayTLSInsecurePinKey]) {
|
|
return
|
|
}
|
|
delete(tlsSettings, xrayTLSInsecurePinKey)
|
|
serverName := getStringAny(tlsSettings["serverName"])
|
|
host, port := firstXrayOutboundEndpoint(item)
|
|
if host == "" || port == "" {
|
|
return
|
|
}
|
|
if serverName == "" {
|
|
serverName = host
|
|
}
|
|
if fingerprints := fetchTLSPeerCertPins(host, port, serverName); len(fingerprints) > 0 {
|
|
tlsSettings["pinnedPeerCertSha256"] = strings.Join(fingerprints, ",")
|
|
}
|
|
}
|
|
|
|
func firstXrayOutboundEndpoint(outbound map[string]interface{}) (string, string) {
|
|
protocol := strings.ToLower(getStringAny(outbound["protocol"]))
|
|
settings, _ := outbound["settings"].(map[string]interface{})
|
|
if settings == nil {
|
|
return "", ""
|
|
}
|
|
switch protocol {
|
|
case "trojan":
|
|
servers, _ := settings["servers"].([]interface{})
|
|
if len(servers) == 0 {
|
|
return "", ""
|
|
}
|
|
server, _ := servers[0].(map[string]interface{})
|
|
if server == nil {
|
|
return "", ""
|
|
}
|
|
return getStringAny(server["address"]), getPortString(server["port"])
|
|
case "vless", "vmess":
|
|
vnext, _ := settings["vnext"].([]interface{})
|
|
if len(vnext) == 0 {
|
|
return "", ""
|
|
}
|
|
server, _ := vnext[0].(map[string]interface{})
|
|
if server == nil {
|
|
return "", ""
|
|
}
|
|
return getStringAny(server["address"]), getPortString(server["port"])
|
|
}
|
|
return "", ""
|
|
}
|
|
|
|
func fetchTLSPeerCertPins(host string, port string, serverName string) []string {
|
|
cacheKey := strings.ToLower(strings.TrimSpace(host)) + ":" + strings.TrimSpace(port) + "|" + strings.ToLower(strings.TrimSpace(serverName))
|
|
if cached, ok := xrayTLSPinCache.Load(cacheKey); ok {
|
|
if pins, ok := cached.([]string); ok && len(pins) > 0 {
|
|
return append([]string(nil), pins...)
|
|
}
|
|
}
|
|
fingerprint, err := fetchTLSPeerCertPin(host, port, serverName)
|
|
if err != nil || fingerprint == "" {
|
|
return nil
|
|
}
|
|
fingerprints := []string{fingerprint}
|
|
if len(fingerprints) > 0 {
|
|
xrayTLSPinCache.Store(cacheKey, append([]string(nil), fingerprints...))
|
|
}
|
|
return fingerprints
|
|
}
|
|
|
|
func fetchTLSPeerCertPin(host string, port string, serverName string) (string, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1200*time.Millisecond)
|
|
defer cancel()
|
|
dialer := &tls.Dialer{
|
|
NetDialer: &net.Dialer{Timeout: time.Second},
|
|
Config: &tls.Config{ServerName: serverName, InsecureSkipVerify: true},
|
|
}
|
|
conn, err := dialer.DialContext(ctx, "tcp", host+":"+port)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer conn.Close()
|
|
tlsConn, ok := conn.(*tls.Conn)
|
|
if !ok {
|
|
return "", nil
|
|
}
|
|
state := tlsConn.ConnectionState()
|
|
if len(state.PeerCertificates) == 0 {
|
|
return "", nil
|
|
}
|
|
sum := sha256.Sum256(state.PeerCertificates[0].Raw)
|
|
return hex.EncodeToString(sum[:]), nil
|
|
}
|
|
|
|
func getPortString(value interface{}) string {
|
|
switch v := value.(type) {
|
|
case int:
|
|
return strconv.Itoa(v)
|
|
case int64:
|
|
return strconv.FormatInt(v, 10)
|
|
case float64:
|
|
return strconv.Itoa(int(v))
|
|
case string:
|
|
return strings.TrimSpace(v)
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func getStringAny(value interface{}) string {
|
|
s, _ := value.(string)
|
|
return strings.TrimSpace(s)
|
|
}
|
|
|
|
func truthy(value interface{}) bool {
|
|
switch v := value.(type) {
|
|
case bool:
|
|
return v
|
|
case string:
|
|
return strings.EqualFold(v, "true") || v == "1"
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func removeXrayDeprecatedFields(value interface{}) {
|
|
switch item := value.(type) {
|
|
case map[string]interface{}:
|
|
delete(item, "allowInsecure")
|
|
for _, nested := range item {
|
|
removeXrayDeprecatedFields(nested)
|
|
}
|
|
case []interface{}:
|
|
for _, nested := range item {
|
|
removeXrayDeprecatedFields(nested)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *XrayManager) resolveWorkdir(key string) string {
|
|
root := strings.TrimSpace(m.Config.Browser.UserDataRoot)
|
|
if root == "" {
|
|
root = "data"
|
|
}
|
|
if !filepath.IsAbs(root) {
|
|
root = apppath.Resolve(m.AppRoot, root)
|
|
}
|
|
return filepath.Join(root, "_xray", key)
|
|
}
|