新增调试信息

This commit is contained in:
2025-10-27 22:15:25 +08:00
parent ae62457d8c
commit 04642cb2f0
5479 changed files with 683397 additions and 3450 deletions
@@ -0,0 +1,49 @@
//go:build !windows
package config
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
func ExecuteCmd(executablePath string, background bool, args ...string) (string, error) {
cwd := filepath.Dir(executablePath)
if appimage := os.Getenv("APPIMAGE"); appimage != "" {
executablePath = appimage
if !background {
return "Fail", fmt.Errorf("Appimage cannot have service")
}
}
commands := [][]string{
{"cocoasudo", "--prompt=Hiddify needs root for tunneling.", executablePath},
{"gksu", executablePath},
{"pkexec", executablePath},
{"xterm", "-e", "sudo", executablePath, strings.Join(args, " ")},
{"sudo", executablePath},
}
var err error
var cmd *exec.Cmd
for _, command := range commands {
cmd = exec.Command(command[0], command[1:]...)
cmd.Dir = cwd
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Printf("Running command: %v\n", command)
if background {
err = cmd.Start()
} else {
err = cmd.Run()
}
if err == nil {
return "Ok", nil
}
}
return "", fmt.Errorf("Error executing run as root shell command")
}
@@ -0,0 +1,32 @@
//go:build windows
package config
import (
"os"
"strings"
"syscall"
"golang.org/x/sys/windows"
)
func ExecuteCmd(exe string, background bool, args ...string) (string, error) {
verb := "runas"
cwd, err := os.Getwd() // Error handling added
if err != nil {
return "", err
}
verbPtr, _ := syscall.UTF16PtrFromString(verb)
exePtr, _ := syscall.UTF16PtrFromString(exe)
cwdPtr, _ := syscall.UTF16PtrFromString(cwd)
argPtr, _ := syscall.UTF16PtrFromString(strings.Join(args, " "))
var showCmd int32 = 0 // SW_NORMAL
err = windows.ShellExecute(0, verbPtr, exePtr, argPtr, cwdPtr, showCmd)
if err != nil {
return "", err
}
return "", nil
}
+188
View File
@@ -0,0 +1,188 @@
package config
import (
context "context"
"fmt"
"log"
"net"
"os"
"path/filepath"
"runtime"
"time"
pb "github.com/hiddify/hiddify-core/hiddifyrpc"
"github.com/sagernet/sing-box/option"
dns "github.com/sagernet/sing-dns"
grpc "google.golang.org/grpc"
)
const (
serviceURL = "http://localhost:18020"
startEndpoint = "/start"
stopEndpoint = "/stop"
)
var tunnelServiceRunning = false
func isSupportedOS() bool {
return runtime.GOOS == "windows" || runtime.GOOS == "linux"
}
func ActivateTunnelService(opt HiddifyOptions) (bool, error) {
tunnelServiceRunning = true
// if !isSupportedOS() {
// return false, E.New("Unsupported OS: " + runtime.GOOS)
// }
go startTunnelRequestWithFailover(opt, true)
return true, nil
}
func DeactivateTunnelServiceForce() (bool, error) {
return stopTunnelRequest()
}
func DeactivateTunnelService() (bool, error) {
// if !isSupportedOS() {
// return true, nil
// }
if tunnelServiceRunning {
res, err := stopTunnelRequest()
if err != nil {
tunnelServiceRunning = false
}
return res, err
} else {
go stopTunnelRequest()
}
return true, nil
}
func startTunnelRequestWithFailover(opt HiddifyOptions, installService bool) {
res, err := startTunnelRequest(opt, installService)
fmt.Printf("Start Tunnel Result: %v\n", res)
if err != nil {
fmt.Printf("Start Tunnel Failed! Stopping core... err=%v\n", err)
// StopAndAlert(pb.MessageType.MessageType_UNEXPECTED_ERROR, "Start Tunnel Failed! Stopping...")
}
}
func isPortInUse(port string) bool {
listener, err := net.Listen("tcp", "127.0.0.1:"+port)
if err != nil {
return true // Port is in use
}
defer listener.Close()
return false // Port is available
}
func startTunnelRequest(opt HiddifyOptions, installService bool) (bool, error) {
if !isPortInUse("18020") {
if installService {
return runTunnelService(opt)
}
return false, fmt.Errorf("service is not running")
}
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
if err != nil {
log.Printf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewTunnelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
_, _ = c.Stop(ctx, &pb.Empty{})
res, err := c.Start(ctx, &pb.TunnelStartRequest{
Ipv6: opt.IPv6Mode == option.DomainStrategy(dns.DomainStrategyUseIPv4),
ServerPort: int32(opt.InboundOptions.MixedPort),
StrictRoute: opt.InboundOptions.StrictRoute,
EndpointIndependentNat: true,
Stack: opt.InboundOptions.TUNStack,
})
if err != nil {
log.Printf("could not greet: %+v %+v", res, err)
if installService {
ExitTunnelService()
return runTunnelService(opt)
}
return false, err
}
return true, nil
}
func stopTunnelRequest() (bool, error) {
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
if err != nil {
log.Printf("did not connect: %v", err)
return false, err
}
defer conn.Close()
c := pb.NewTunnelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
defer cancel()
res, err := c.Stop(ctx, &pb.Empty{})
if err != nil {
log.Printf("did not Stopped: %v %v", res, err)
_, _ = c.Stop(ctx, &pb.Empty{})
return false, err
}
return true, nil
}
func ExitTunnelService() (bool, error) {
conn, err := grpc.Dial("127.0.0.1:18020", grpc.WithInsecure())
if err != nil {
log.Printf("did not connect: %v", err)
return false, err
}
defer conn.Close()
c := pb.NewTunnelServiceClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
defer cancel()
res, err := c.Exit(ctx, &pb.Empty{})
if res != nil {
log.Printf("did not exit: %v %v", res, err)
return false, err
}
return true, nil
}
func runTunnelService(opt HiddifyOptions) (bool, error) {
executablePath := getTunnelServicePath()
fmt.Printf("Executable path is %s", executablePath)
out, err := ExecuteCmd(executablePath, false, "tunnel", "install")
fmt.Println("Shell command executed:", out, err)
if err != nil {
out, err = ExecuteCmd(executablePath, true, "tunnel", "run")
fmt.Println("Shell command executed without flag:", out, err)
}
if err == nil {
<-time.After(1 * time.Second) // wait until service loaded completely
}
return startTunnelRequest(opt, false)
}
func getTunnelServicePath() string {
var fullPath string
exePath, _ := os.Executable()
binFolder := filepath.Dir(exePath)
switch runtime.GOOS {
case "windows":
fullPath = "HiddifyCli.exe"
case "darwin":
fallthrough
default:
fullPath = "HiddifyCli"
}
abspath, _ := filepath.Abs(filepath.Join(binFolder, fullPath))
return abspath
}
+869
View File
@@ -0,0 +1,869 @@
package config
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"math/rand"
"net"
"net/netip"
"net/url"
"runtime"
"strings"
"time"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
dns "github.com/sagernet/sing-dns"
)
const (
DNSRemoteTag = "dns-remote"
DNSLocalTag = "dns-local"
DNSDirectTag = "dns-direct"
DNSBlockTag = "dns-block"
DNSFakeTag = "dns-fake"
DNSTricksDirectTag = "dns-trick-direct"
OutboundDirectTag = "direct"
OutboundBypassTag = "bypass"
OutboundBlockTag = "block"
OutboundSelectTag = "select"
OutboundURLTestTag = "auto"
OutboundDNSTag = "dns-out"
OutboundDirectFragmentTag = "direct-fragment"
InboundTUNTag = "tun-in"
InboundMixedTag = "mixed-in"
InboundDNSTag = "dns-in"
)
var OutboundMainProxyTag = OutboundSelectTag
func BuildConfigJson(configOpt HiddifyOptions, input option.Options) (string, error) {
options, err := BuildConfig(configOpt, input)
if err != nil {
return "", err
}
var buffer bytes.Buffer
json.NewEncoder(&buffer)
encoder := json.NewEncoder(&buffer)
encoder.SetIndent("", " ")
err = encoder.Encode(options)
if err != nil {
return "", err
}
return buffer.String(), nil
}
// TODO include selectors
func BuildConfig(opt HiddifyOptions, input option.Options) (*option.Options, error) {
fmt.Printf("config options: %++v\n", opt)
var options option.Options
if opt.EnableFullConfig {
options.Inbounds = input.Inbounds
options.DNS = input.DNS
options.Route = input.Route
}
setClashAPI(&options, &opt)
setLog(&options, &opt)
setInbound(&options, &opt)
setDns(&options, &opt)
setRoutingOptions(&options, &opt)
setFakeDns(&options, &opt)
err := setOutbounds(&options, &input, &opt)
if err != nil {
return nil, err
}
return &options, nil
}
func addForceDirect(options *option.Options, opt *HiddifyOptions, directDNSDomains map[string]bool) {
remoteDNSAddress := opt.RemoteDnsAddress
if strings.Contains(remoteDNSAddress, "://") {
remoteDNSAddress = strings.SplitAfter(remoteDNSAddress, "://")[1]
}
parsedUrl, err := url.Parse(fmt.Sprintf("https://%s", remoteDNSAddress))
if err == nil && net.ParseIP(parsedUrl.Host) == nil {
directDNSDomains[parsedUrl.Host] = true
}
if len(directDNSDomains) > 0 {
// trickDnsDomains := []string{}
// directDNSDomains = removeDuplicateStr(directDNSDomains)
// b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[bool](10))
// for _, d := range directDNSDomains {
// b.Go(d, func() (bool, error) {
// return isBlockedDomain(d), nil
// })
// }
// b.Wait()
// for domain, isBlock := range b.Result() {
// if isBlock.Value {
// trickDnsDomains = append(trickDnsDomains, domain)
// }
// }
// trickDomains := strings.Join(trickDnsDomains, ",")
// trickRule := Rule{Domains: trickDomains, Outbound: OutboundBypassTag}
// trickDnsRule := trickRule.MakeDNSRule()
// trickDnsRule.Server = DNSTricksDirectTag
// options.DNS.Rules = append([]option.DNSRule{{Type: C.RuleTypeDefault, DefaultOptions: trickDnsRule}}, options.DNS.Rules...)
directDNSDomainskeys := make([]string, 0, len(directDNSDomains))
for key := range directDNSDomains {
directDNSDomainskeys = append(directDNSDomainskeys, key)
}
domains := strings.Join(directDNSDomainskeys, ",")
directRule := Rule{Domains: domains, Outbound: OutboundBypassTag}
dnsRule := directRule.MakeDNSRule()
dnsRule.Server = DNSDirectTag
options.DNS.Rules = append([]option.DNSRule{{Type: C.RuleTypeDefault, DefaultOptions: dnsRule}}, options.DNS.Rules...)
}
}
func setOutbounds(options *option.Options, input *option.Options, opt *HiddifyOptions) error {
directDNSDomains := make(map[string]bool)
var outbounds []option.Outbound
var tags []string
OutboundMainProxyTag = OutboundSelectTag
// inbound==warp over proxies
// outbound==proxies over warp
if opt.Warp.EnableWarp {
for _, out := range input.Outbounds {
if out.Type == C.TypeCustom {
if warp, ok := out.CustomOptions["warp"].(map[string]interface{}); ok {
key, _ := warp["key"].(string)
if key == "p1" {
opt.Warp.EnableWarp = false
break
}
}
}
if out.Type == C.TypeWireGuard && (out.WireGuardOptions.PrivateKey == opt.Warp.WireguardConfig.PrivateKey || out.WireGuardOptions.PrivateKey == "p1") {
opt.Warp.EnableWarp = false
break
}
}
}
if opt.Warp.EnableWarp && (opt.Warp.Mode == "warp_over_proxy" || opt.Warp.Mode == "proxy_over_warp") {
out, err := GenerateWarpSingbox(opt.Warp.WireguardConfig, opt.Warp.CleanIP, opt.Warp.CleanPort, opt.Warp.FakePackets, opt.Warp.FakePacketSize, opt.Warp.FakePacketDelay, opt.Warp.FakePacketMode)
if err != nil {
return fmt.Errorf("failed to generate warp config: %v", err)
}
out.Tag = "Hiddify Warp ✅"
if opt.Warp.Mode == "warp_over_proxy" {
out.WireGuardOptions.Detour = OutboundSelectTag
OutboundMainProxyTag = out.Tag
} else {
out.WireGuardOptions.Detour = OutboundDirectTag
}
patchWarp(out, opt, true, nil)
outbounds = append(outbounds, *out)
// tags = append(tags, out.Tag)
}
for _, out := range input.Outbounds {
outbound, serverDomain, err := patchOutbound(out, *opt, options.DNS.StaticIPs)
if err != nil {
return err
}
if serverDomain != "" {
directDNSDomains[serverDomain] = true
}
out = *outbound
switch out.Type {
case C.TypeDirect, C.TypeBlock, C.TypeDNS:
continue
case C.TypeSelector, C.TypeURLTest:
continue
case C.TypeCustom:
continue
default:
if !strings.Contains(out.Tag, "§hide§") {
tags = append(tags, out.Tag)
}
out = patchHiddifyWarpFromConfig(out, *opt)
outbounds = append(outbounds, out)
}
}
urlTest := option.Outbound{
Type: C.TypeURLTest,
Tag: OutboundURLTestTag,
URLTestOptions: option.URLTestOutboundOptions{
Outbounds: tags,
URL: opt.ConnectionTestUrl,
Interval: option.Duration(opt.URLTestInterval.Duration()),
// IdleTimeout: option.Duration(opt.URLTestIdleTimeout.Duration()),
Tolerance: 1,
IdleTimeout: option.Duration(opt.URLTestInterval.Duration().Nanoseconds() * 3),
InterruptExistConnections: true,
},
}
defaultSelect := urlTest.Tag
for _, tag := range tags {
if strings.Contains(tag, "§default§") {
defaultSelect = "§default§"
}
}
selector := option.Outbound{
Type: C.TypeSelector,
Tag: OutboundSelectTag,
SelectorOptions: option.SelectorOutboundOptions{
Outbounds: append([]string{urlTest.Tag}, tags...),
Default: defaultSelect,
InterruptExistConnections: true,
},
}
outbounds = append([]option.Outbound{selector, urlTest}, outbounds...)
options.Outbounds = append(
outbounds,
[]option.Outbound{
{
Tag: OutboundDNSTag,
Type: C.TypeDNS,
},
{
Tag: OutboundDirectTag,
Type: C.TypeDirect,
},
{
Tag: OutboundDirectFragmentTag,
Type: C.TypeDirect,
DirectOptions: option.DirectOutboundOptions{
DialerOptions: option.DialerOptions{
TCPFastOpen: false,
TLSFragment: option.TLSFragmentOptions{
Enabled: true,
Size: opt.TLSTricks.FragmentSize,
Sleep: opt.TLSTricks.FragmentSleep,
},
},
},
},
{
Tag: OutboundBypassTag,
Type: C.TypeDirect,
},
{
Tag: OutboundBlockTag,
Type: C.TypeBlock,
},
}...,
)
addForceDirect(options, opt, directDNSDomains)
return nil
}
func setClashAPI(options *option.Options, opt *HiddifyOptions) {
if opt.EnableClashApi {
if opt.ClashApiSecret == "" {
opt.ClashApiSecret = generateRandomString(16)
}
options.Experimental = &option.ExperimentalOptions{
ClashAPI: &option.ClashAPIOptions{
ExternalController: fmt.Sprintf("%s:%d", "127.0.0.1", opt.ClashApiPort),
Secret: opt.ClashApiSecret,
},
CacheFile: &option.CacheFileOptions{
Enabled: true,
Path: "clash.db",
},
}
}
}
func setLog(options *option.Options, opt *HiddifyOptions) {
options.Log = &option.LogOptions{
Level: opt.LogLevel,
Output: opt.LogFile,
Disabled: false,
Timestamp: true,
DisableColor: true,
}
}
func setInbound(options *option.Options, opt *HiddifyOptions) {
var inboundDomainStrategy option.DomainStrategy
if !opt.ResolveDestination {
inboundDomainStrategy = option.DomainStrategy(dns.DomainStrategyAsIS)
} else {
inboundDomainStrategy = opt.IPv6Mode
}
if opt.EnableTunService {
ActivateTunnelService(*opt)
} else if opt.EnableTun {
tunInbound := option.Inbound{
Type: C.TypeTun,
Tag: InboundTUNTag,
TunOptions: option.TunInboundOptions{
Stack: opt.TUNStack,
MTU: opt.MTU,
AutoRoute: true,
StrictRoute: opt.StrictRoute,
EndpointIndependentNat: true,
// GSO: runtime.GOOS != "windows",
InboundOptions: option.InboundOptions{
SniffEnabled: true,
SniffOverrideDestination: false,
DomainStrategy: inboundDomainStrategy,
},
},
}
switch opt.IPv6Mode {
case option.DomainStrategy(dns.DomainStrategyUseIPv4):
tunInbound.TunOptions.Inet4Address = []netip.Prefix{
netip.MustParsePrefix("172.19.0.1/28"),
}
case option.DomainStrategy(dns.DomainStrategyUseIPv6):
tunInbound.TunOptions.Inet6Address = []netip.Prefix{
netip.MustParsePrefix("fdfe:dcba:9876::1/126"),
}
default:
tunInbound.TunOptions.Inet4Address = []netip.Prefix{
netip.MustParsePrefix("172.19.0.1/28"),
}
tunInbound.TunOptions.Inet6Address = []netip.Prefix{
netip.MustParsePrefix("fdfe:dcba:9876::1/126"),
}
}
options.Inbounds = append(options.Inbounds, tunInbound)
}
var bind string
if opt.AllowConnectionFromLAN {
bind = "0.0.0.0"
} else {
bind = "127.0.0.1"
}
options.Inbounds = append(
options.Inbounds,
option.Inbound{
Type: C.TypeMixed,
Tag: InboundMixedTag,
MixedOptions: option.HTTPMixedInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.MustParseAddr(bind)),
ListenPort: opt.MixedPort,
InboundOptions: option.InboundOptions{
SniffEnabled: true,
SniffOverrideDestination: true,
DomainStrategy: inboundDomainStrategy,
},
},
SetSystemProxy: opt.SetSystemProxy,
},
},
)
options.Inbounds = append(
options.Inbounds,
option.Inbound{
Type: C.TypeDirect,
Tag: InboundDNSTag,
DirectOptions: option.DirectInboundOptions{
ListenOptions: option.ListenOptions{
Listen: option.NewListenAddress(netip.MustParseAddr(bind)),
ListenPort: opt.LocalDnsPort,
},
// OverrideAddress: "1.1.1.1",
// OverridePort: 53,
},
},
)
}
func setDns(options *option.Options, opt *HiddifyOptions) {
options.DNS = &option.DNSOptions{
StaticIPs: map[string][]string{},
DNSClientOptions: option.DNSClientOptions{
IndependentCache: opt.IndependentDNSCache,
},
Final: DNSRemoteTag,
Servers: []option.DNSServerOptions{
{
Tag: DNSRemoteTag,
Address: opt.RemoteDnsAddress,
AddressResolver: DNSDirectTag,
Strategy: opt.RemoteDnsDomainStrategy,
},
{
Tag: DNSTricksDirectTag,
Address: "https://sky.rethinkdns.com/",
// AddressResolver: "dns-local",
Strategy: opt.DirectDnsDomainStrategy,
Detour: OutboundDirectFragmentTag,
},
{
Tag: DNSDirectTag,
Address: opt.DirectDnsAddress,
AddressResolver: DNSLocalTag,
Strategy: opt.DirectDnsDomainStrategy,
Detour: OutboundDirectTag,
},
{
Tag: DNSLocalTag,
Address: "local",
Detour: OutboundDirectTag,
},
{
Tag: DNSBlockTag,
Address: "rcode://success",
},
},
}
sky_rethinkdns := getIPs([]string{"www.speedtest.net", "sky.rethinkdns.com"})
if len(sky_rethinkdns) > 0 {
options.DNS.StaticIPs["sky.rethinkdns.com"] = sky_rethinkdns
}
}
func setFakeDns(options *option.Options, opt *HiddifyOptions) {
if opt.EnableFakeDNS {
inet4Range := netip.MustParsePrefix("198.18.0.0/15")
inet6Range := netip.MustParsePrefix("fc00::/18")
options.DNS.FakeIP = &option.DNSFakeIPOptions{
Enabled: true,
Inet4Range: &inet4Range,
Inet6Range: &inet6Range,
}
options.DNS.Servers = append(
options.DNS.Servers,
option.DNSServerOptions{
Tag: DNSFakeTag,
Address: "fakeip",
Strategy: option.DomainStrategy(dns.DomainStrategyUseIPv4),
},
)
options.DNS.Rules = append(
options.DNS.Rules,
option.DNSRule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultDNSRule{
Inbound: []string{InboundTUNTag},
Server: DNSFakeTag,
DisableCache: true,
},
},
)
}
}
func setRoutingOptions(options *option.Options, opt *HiddifyOptions) {
dnsRules := []option.DefaultDNSRule{}
routeRules := []option.Rule{}
rulesets := []option.RuleSet{}
if opt.EnableTun && runtime.GOOS == "android" {
routeRules = append(
routeRules,
option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Inbound: []string{InboundTUNTag},
PackageName: []string{"app.brAccelerator.com"},
Outbound: OutboundBypassTag,
},
},
)
// routeRules = append(
// routeRules,
// option.Rule{
// Type: C.RuleTypeDefault,
// DefaultOptions: option.DefaultRule{
// ProcessName: []string{"Hiddify", "Hiddify.exe", "HiddifyCli", "HiddifyCli.exe"},
// Outbound: OutboundBypassTag,
// },
// },
// )
}
routeRules = append(routeRules, option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Inbound: []string{InboundDNSTag},
Outbound: OutboundDNSTag,
},
})
routeRules = append(routeRules, option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
Port: []uint16{53},
Outbound: OutboundDNSTag,
},
})
// {
// Type: C.RuleTypeDefault,
// DefaultOptions: option.DefaultRule{
// ClashMode: "Direct",
// Outbound: OutboundDirectTag,
// },
// },
// {
// Type: C.RuleTypeDefault,
// DefaultOptions: option.DefaultRule{
// ClashMode: "Global",
// Outbound: OutboundMainProxyTag,
// },
// }, }
if opt.BypassLAN {
routeRules = append(
routeRules,
option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
// GeoIP: []string{"private"},
IPIsPrivate: true,
Outbound: OutboundBypassTag,
},
},
)
}
for _, rule := range opt.Rules {
routeRule := rule.MakeRule()
switch rule.Outbound {
case "bypass":
routeRule.Outbound = OutboundBypassTag
case "block":
routeRule.Outbound = OutboundBlockTag
case "proxy":
routeRule.Outbound = OutboundMainProxyTag
}
if routeRule.IsValid() {
routeRules = append(
routeRules,
option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: routeRule,
},
)
}
dnsRule := rule.MakeDNSRule()
switch rule.Outbound {
case "bypass":
dnsRule.Server = DNSDirectTag
case "block":
dnsRule.Server = DNSBlockTag
dnsRule.DisableCache = true
case "proxy":
if opt.EnableFakeDNS {
fakeDnsRule := dnsRule
fakeDnsRule.Server = DNSFakeTag
fakeDnsRule.Inbound = []string{InboundTUNTag, InboundMixedTag}
dnsRules = append(dnsRules, fakeDnsRule)
}
dnsRule.Server = DNSRemoteTag
}
dnsRules = append(dnsRules, dnsRule)
}
parsedURL, err := url.Parse(opt.ConnectionTestUrl)
if err == nil {
var dnsCPttl uint32 = 3000
dnsRules = append(dnsRules, option.DefaultDNSRule{
Domain: []string{parsedURL.Host},
Server: DNSRemoteTag,
RewriteTTL: &dnsCPttl,
DisableCache: false,
})
}
if opt.BlockAds {
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geosite-ads",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geosite-category-ads-all.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geosite-malware",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geosite-malware.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geosite-phishing",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geosite-phishing.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geosite-cryptominers",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geosite-cryptominers.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geoip-phishing",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geoip-phishing.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geoip-malware",
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/block/geoip-malware.srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
routeRules = append(routeRules, option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
RuleSet: []string{
"geosite-ads",
"geosite-malware",
"geosite-phishing",
"geosite-cryptominers",
"geoip-malware",
"geoip-phishing",
},
Outbound: OutboundBlockTag,
},
})
dnsRules = append(dnsRules, option.DefaultDNSRule{
RuleSet: []string{
"geosite-ads",
"geosite-malware",
"geosite-phishing",
"geosite-cryptominers",
"geoip-malware",
"geoip-phishing",
},
Server: DNSBlockTag,
// DisableCache: true,
})
}
if opt.Region != "other" {
dnsRules = append(dnsRules, option.DefaultDNSRule{
DomainSuffix: []string{"." + opt.Region},
Server: DNSDirectTag,
})
routeRules = append(routeRules, option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
DomainSuffix: []string{"." + opt.Region},
Outbound: OutboundDirectTag,
},
})
dnsRules = append(dnsRules, option.DefaultDNSRule{
RuleSet: []string{
"geoip-" + opt.Region,
"geosite-" + opt.Region,
},
Server: DNSDirectTag,
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geoip-" + opt.Region,
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geoip-" + opt.Region + ".srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
rulesets = append(rulesets, option.RuleSet{
Type: C.RuleSetTypeRemote,
Tag: "geosite-" + opt.Region,
Format: C.RuleSetFormatBinary,
RemoteOptions: option.RemoteRuleSet{
URL: "https://raw.githubusercontent.com/hiddify/hiddify-geo/rule-set/country/geosite-" + opt.Region + ".srs",
UpdateInterval: option.Duration(5 * time.Hour * 24),
},
})
routeRules = append(routeRules, option.Rule{
Type: C.RuleTypeDefault,
DefaultOptions: option.DefaultRule{
RuleSet: []string{
"geoip-" + opt.Region,
"geosite-" + opt.Region,
},
Outbound: OutboundDirectTag,
},
})
}
options.Route = &option.RouteOptions{
Rules: routeRules,
Final: OutboundMainProxyTag,
AutoDetectInterface: true,
OverrideAndroidVPN: true,
RuleSet: rulesets,
// GeoIP: &option.GeoIPOptions{
// Path: opt.GeoIPPath,
// },
// Geosite: &option.GeositeOptions{
// Path: opt.GeoSitePath,
// },
}
if opt.EnableDNSRouting {
for _, dnsRule := range dnsRules {
if dnsRule.IsValid() {
options.DNS.Rules = append(
options.DNS.Rules,
option.DNSRule{
Type: C.RuleTypeDefault,
DefaultOptions: dnsRule,
},
)
}
}
}
}
func patchHiddifyWarpFromConfig(out option.Outbound, opt HiddifyOptions) option.Outbound {
if opt.Warp.EnableWarp && opt.Warp.Mode == "proxy_over_warp" {
if out.DirectOptions.Detour == "" {
out.DirectOptions.Detour = "Hiddify Warp ✅"
}
if out.HTTPOptions.Detour == "" {
out.HTTPOptions.Detour = "Hiddify Warp ✅"
}
if out.Hysteria2Options.Detour == "" {
out.Hysteria2Options.Detour = "Hiddify Warp ✅"
}
if out.HysteriaOptions.Detour == "" {
out.HysteriaOptions.Detour = "Hiddify Warp ✅"
}
if out.SSHOptions.Detour == "" {
out.SSHOptions.Detour = "Hiddify Warp ✅"
}
if out.ShadowTLSOptions.Detour == "" {
out.ShadowTLSOptions.Detour = "Hiddify Warp ✅"
}
if out.ShadowsocksOptions.Detour == "" {
out.ShadowsocksOptions.Detour = "Hiddify Warp ✅"
}
if out.ShadowsocksROptions.Detour == "" {
out.ShadowsocksROptions.Detour = "Hiddify Warp ✅"
}
if out.SocksOptions.Detour == "" {
out.SocksOptions.Detour = "Hiddify Warp ✅"
}
if out.TUICOptions.Detour == "" {
out.TUICOptions.Detour = "Hiddify Warp ✅"
}
if out.TorOptions.Detour == "" {
out.TorOptions.Detour = "Hiddify Warp ✅"
}
if out.TrojanOptions.Detour == "" {
out.TrojanOptions.Detour = "Hiddify Warp ✅"
}
if out.VLESSOptions.Detour == "" {
out.VLESSOptions.Detour = "Hiddify Warp ✅"
}
if out.VMessOptions.Detour == "" {
out.VMessOptions.Detour = "Hiddify Warp ✅"
}
if out.WireGuardOptions.Detour == "" {
out.WireGuardOptions.Detour = "Hiddify Warp ✅"
}
}
return out
}
func getIPs(domains []string) []string {
res := []string{}
for _, d := range domains {
ips, err := net.LookupHost(d)
if err != nil {
continue
}
for _, ip := range ips {
if !strings.HasPrefix(ip, "10.") {
res = append(res, ip)
}
}
}
return res
}
func isBlockedDomain(domain string) bool {
if strings.HasPrefix("full:", domain) {
return false
}
ips, err := net.LookupHost(domain)
if err != nil {
// fmt.Println(err)
return true
}
// Print the IP addresses associated with the domain
fmt.Printf("IP addresses for %s:\n", domain)
for _, ip := range ips {
if strings.HasPrefix(ip, "10.") {
return true
}
}
return false
}
func removeDuplicateStr(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}
func generateRandomString(length int) string {
// Determine the number of bytes needed
bytesNeeded := (length*6 + 7) / 8
// Generate random bytes
randomBytes := make([]byte, bytesNeeded)
_, err := rand.Read(randomBytes)
if err != nil {
return "hiddify"
}
// Encode random bytes to base64
randomString := base64.URLEncoding.EncodeToString(randomBytes)
// Trim padding characters and return the string
return randomString[:length]
}
+8
View File
@@ -0,0 +1,8 @@
{
"log": {},
"dns": {},
"inbounds": [],
"outbounds": [],
"route": {},
"experimental": {}
}
+6
View File
@@ -0,0 +1,6 @@
package config
const (
WarpOverProxy = "warp_over_proxy"
ProxyOverWarp = "proxy_over_warp"
)
+389
View File
@@ -0,0 +1,389 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v4.25.3
// source: core.proto
package config
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type ParseConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
TempPath string `protobuf:"bytes,1,opt,name=tempPath,proto3" json:"tempPath,omitempty"`
Path string `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"`
Debug bool `protobuf:"varint,3,opt,name=debug,proto3" json:"debug,omitempty"`
}
func (x *ParseConfigRequest) Reset() {
*x = ParseConfigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_core_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ParseConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ParseConfigRequest) ProtoMessage() {}
func (x *ParseConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ParseConfigRequest.ProtoReflect.Descriptor instead.
func (*ParseConfigRequest) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{0}
}
func (x *ParseConfigRequest) GetTempPath() string {
if x != nil {
return x.TempPath
}
return ""
}
func (x *ParseConfigRequest) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *ParseConfigRequest) GetDebug() bool {
if x != nil {
return x.Debug
}
return false
}
type ParseConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Error *string `protobuf:"bytes,1,opt,name=error,proto3,oneof" json:"error,omitempty"`
}
func (x *ParseConfigResponse) Reset() {
*x = ParseConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_core_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *ParseConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ParseConfigResponse) ProtoMessage() {}
func (x *ParseConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use ParseConfigResponse.ProtoReflect.Descriptor instead.
func (*ParseConfigResponse) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{1}
}
func (x *ParseConfigResponse) GetError() string {
if x != nil && x.Error != nil {
return *x.Error
}
return ""
}
type GenerateConfigRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Path string `protobuf:"bytes,1,opt,name=path,proto3" json:"path,omitempty"`
Debug bool `protobuf:"varint,2,opt,name=debug,proto3" json:"debug,omitempty"`
}
func (x *GenerateConfigRequest) Reset() {
*x = GenerateConfigRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_core_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenerateConfigRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenerateConfigRequest) ProtoMessage() {}
func (x *GenerateConfigRequest) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenerateConfigRequest.ProtoReflect.Descriptor instead.
func (*GenerateConfigRequest) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{2}
}
func (x *GenerateConfigRequest) GetPath() string {
if x != nil {
return x.Path
}
return ""
}
func (x *GenerateConfigRequest) GetDebug() bool {
if x != nil {
return x.Debug
}
return false
}
type GenerateConfigResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Config string `protobuf:"bytes,1,opt,name=config,proto3" json:"config,omitempty"`
Error *string `protobuf:"bytes,2,opt,name=error,proto3,oneof" json:"error,omitempty"`
}
func (x *GenerateConfigResponse) Reset() {
*x = GenerateConfigResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_core_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenerateConfigResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenerateConfigResponse) ProtoMessage() {}
func (x *GenerateConfigResponse) ProtoReflect() protoreflect.Message {
mi := &file_core_proto_msgTypes[3]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenerateConfigResponse.ProtoReflect.Descriptor instead.
func (*GenerateConfigResponse) Descriptor() ([]byte, []int) {
return file_core_proto_rawDescGZIP(), []int{3}
}
func (x *GenerateConfigResponse) GetConfig() string {
if x != nil {
return x.Config
}
return ""
}
func (x *GenerateConfigResponse) GetError() string {
if x != nil && x.Error != nil {
return *x.Error
}
return ""
}
var File_core_proto protoreflect.FileDescriptor
var file_core_proto_rawDesc = []byte{
0x0a, 0x0a, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x43, 0x6f,
0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x5a, 0x0a, 0x12, 0x50,
0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x1a, 0x0a, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x50, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20,
0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x65, 0x6d, 0x70, 0x50, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a,
0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74,
0x68, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08,
0x52, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x22, 0x3a, 0x0a, 0x13, 0x50, 0x61, 0x72, 0x73, 0x65,
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x19,
0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52,
0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72,
0x72, 0x6f, 0x72, 0x22, 0x41, 0x0a, 0x15, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68,
0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
0x05, 0x64, 0x65, 0x62, 0x75, 0x67, 0x22, 0x55, 0x0a, 0x16, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f,
0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72,
0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xc6, 0x01,
0x0a, 0x0b, 0x43, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x54, 0x0a,
0x0b, 0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x21, 0x2e, 0x43,
0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x50, 0x61, 0x72,
0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x22, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x50, 0x61, 0x72, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x12, 0x61, 0x0a, 0x12, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x46,
0x75, 0x6c, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x24, 0x2e, 0x43, 0x6f, 0x6e, 0x66,
0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x25, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e,
0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0b, 0x5a, 0x09, 0x2e, 0x2e, 0x2f, 0x63, 0x6f, 0x6e,
0x66, 0x69, 0x67, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_core_proto_rawDescOnce sync.Once
file_core_proto_rawDescData = file_core_proto_rawDesc
)
func file_core_proto_rawDescGZIP() []byte {
file_core_proto_rawDescOnce.Do(func() {
file_core_proto_rawDescData = protoimpl.X.CompressGZIP(file_core_proto_rawDescData)
})
return file_core_proto_rawDescData
}
var file_core_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
var file_core_proto_goTypes = []interface{}{
(*ParseConfigRequest)(nil), // 0: HiddifyOptions.ParseConfigRequest
(*ParseConfigResponse)(nil), // 1: HiddifyOptions.ParseConfigResponse
(*GenerateConfigRequest)(nil), // 2: HiddifyOptions.GenerateConfigRequest
(*GenerateConfigResponse)(nil), // 3: HiddifyOptions.GenerateConfigResponse
}
var file_core_proto_depIdxs = []int32{
0, // 0: HiddifyOptions.CoreService.ParseConfig:input_type -> HiddifyOptions.ParseConfigRequest
2, // 1: HiddifyOptions.CoreService.GenerateFullConfig:input_type -> HiddifyOptions.GenerateConfigRequest
1, // 2: HiddifyOptions.CoreService.ParseConfig:output_type -> HiddifyOptions.ParseConfigResponse
3, // 3: HiddifyOptions.CoreService.GenerateFullConfig:output_type -> HiddifyOptions.GenerateConfigResponse
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_core_proto_init() }
func file_core_proto_init() {
if File_core_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_core_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ParseConfigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*ParseConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenerateConfigRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_core_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenerateConfigResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_core_proto_msgTypes[1].OneofWrappers = []interface{}{}
file_core_proto_msgTypes[3].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_core_proto_rawDesc,
NumEnums: 0,
NumMessages: 4,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_core_proto_goTypes,
DependencyIndexes: file_core_proto_depIdxs,
MessageInfos: file_core_proto_msgTypes,
}.Build()
File_core_proto = out.File
file_core_proto_rawDesc = nil
file_core_proto_goTypes = nil
file_core_proto_depIdxs = nil
}
+146
View File
@@ -0,0 +1,146 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v4.25.3
// source: core.proto
package config
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
CoreService_ParseConfig_FullMethodName = "/HiddifyOptions.CoreService/ParseConfig"
CoreService_GenerateFullConfig_FullMethodName = "/HiddifyOptions.CoreService/GenerateFullConfig"
)
// CoreServiceClient is the client API for CoreService service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type CoreServiceClient interface {
ParseConfig(ctx context.Context, in *ParseConfigRequest, opts ...grpc.CallOption) (*ParseConfigResponse, error)
GenerateFullConfig(ctx context.Context, in *GenerateConfigRequest, opts ...grpc.CallOption) (*GenerateConfigResponse, error)
}
type coreServiceClient struct {
cc grpc.ClientConnInterface
}
func NewCoreServiceClient(cc grpc.ClientConnInterface) CoreServiceClient {
return &coreServiceClient{cc}
}
func (c *coreServiceClient) ParseConfig(ctx context.Context, in *ParseConfigRequest, opts ...grpc.CallOption) (*ParseConfigResponse, error) {
out := new(ParseConfigResponse)
err := c.cc.Invoke(ctx, CoreService_ParseConfig_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *coreServiceClient) GenerateFullConfig(ctx context.Context, in *GenerateConfigRequest, opts ...grpc.CallOption) (*GenerateConfigResponse, error) {
out := new(GenerateConfigResponse)
err := c.cc.Invoke(ctx, CoreService_GenerateFullConfig_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// CoreServiceServer is the server API for CoreService service.
// All implementations must embed UnimplementedCoreServiceServer
// for forward compatibility
type CoreServiceServer interface {
ParseConfig(context.Context, *ParseConfigRequest) (*ParseConfigResponse, error)
GenerateFullConfig(context.Context, *GenerateConfigRequest) (*GenerateConfigResponse, error)
mustEmbedUnimplementedCoreServiceServer()
}
// UnimplementedCoreServiceServer must be embedded to have forward compatible implementations.
type UnimplementedCoreServiceServer struct {
}
func (UnimplementedCoreServiceServer) ParseConfig(context.Context, *ParseConfigRequest) (*ParseConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ParseConfig not implemented")
}
func (UnimplementedCoreServiceServer) GenerateFullConfig(context.Context, *GenerateConfigRequest) (*GenerateConfigResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GenerateFullConfig not implemented")
}
func (UnimplementedCoreServiceServer) mustEmbedUnimplementedCoreServiceServer() {}
// UnsafeCoreServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CoreServiceServer will
// result in compilation errors.
type UnsafeCoreServiceServer interface {
mustEmbedUnimplementedCoreServiceServer()
}
func RegisterCoreServiceServer(s grpc.ServiceRegistrar, srv CoreServiceServer) {
s.RegisterService(&CoreService_ServiceDesc, srv)
}
func _CoreService_ParseConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ParseConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServiceServer).ParseConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CoreService_ParseConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServiceServer).ParseConfig(ctx, req.(*ParseConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
func _CoreService_GenerateFullConfig_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GenerateConfigRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(CoreServiceServer).GenerateFullConfig(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: CoreService_GenerateFullConfig_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(CoreServiceServer).GenerateFullConfig(ctx, req.(*GenerateConfigRequest))
}
return interceptor(ctx, in, info, handler)
}
// CoreService_ServiceDesc is the grpc.ServiceDesc for CoreService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var CoreService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "HiddifyOptions.CoreService",
HandlerType: (*CoreServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "ParseConfig",
Handler: _CoreService_ParseConfig_Handler,
},
{
MethodName: "GenerateFullConfig",
Handler: _CoreService_GenerateFullConfig_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "core.proto",
}
+45
View File
@@ -0,0 +1,45 @@
package config
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime/debug"
"github.com/sagernet/sing-box/option"
)
func SaveCurrentConfig(path string, options option.Options) error {
json, err := ToJson(options)
if err != nil {
return err
}
p, err := filepath.Abs(path)
fmt.Printf("Saving config to %v %+v\n", p, err)
if err != nil {
return err
}
return os.WriteFile(p, []byte(json), 0644)
}
func ToJson(options option.Options) (string, error) {
var buffer bytes.Buffer
encoder := json.NewEncoder(&buffer)
encoder.SetIndent("", " ")
// fmt.Printf("%+v\n", options)
err := encoder.Encode(options)
if err != nil {
fmt.Printf("ERROR in coding:%+v\n", err)
return "", err
}
return buffer.String(), nil
}
func DeferPanicToError(name string, err func(error)) {
if r := recover(); r != nil {
s := fmt.Errorf("%s panic: %s\n%s", name, r, string(debug.Stack()))
err(s)
}
}
+155
View File
@@ -0,0 +1,155 @@
package config
import (
"github.com/sagernet/sing-box/option"
dns "github.com/sagernet/sing-dns"
)
type HiddifyOptions struct {
EnableFullConfig bool `json:"enable-full-config"`
LogLevel string `json:"log-level"`
LogFile string `json:"log-file"`
EnableClashApi bool `json:"enable-clash-api"`
ClashApiPort uint16 `json:"clash-api-port"`
ClashApiSecret string `json:"web-secret"`
Region string `json:"region"`
BlockAds bool `json:"block-ads"`
UseXrayCoreWhenPossible bool `json:"use-xray-core-when-possible"`
// GeoIPPath string `json:"geoip-path"`
// GeoSitePath string `json:"geosite-path"`
Rules []Rule `json:"rules"`
Warp WarpOptions `json:"warp"`
Warp2 WarpOptions `json:"warp2"`
Mux MuxOptions `json:"mux"`
TLSTricks TLSTricks `json:"tls-tricks"`
DNSOptions
InboundOptions
URLTestOptions
RouteOptions
}
type DNSOptions struct {
RemoteDnsAddress string `json:"remote-dns-address"`
RemoteDnsDomainStrategy option.DomainStrategy `json:"remote-dns-domain-strategy"`
DirectDnsAddress string `json:"direct-dns-address"`
DirectDnsDomainStrategy option.DomainStrategy `json:"direct-dns-domain-strategy"`
IndependentDNSCache bool `json:"independent-dns-cache"`
EnableFakeDNS bool `json:"enable-fake-dns"`
EnableDNSRouting bool `json:"enable-dns-routing"`
}
type InboundOptions struct {
EnableTun bool `json:"enable-tun"`
EnableTunService bool `json:"enable-tun-service"`
SetSystemProxy bool `json:"set-system-proxy"`
MixedPort uint16 `json:"mixed-port"`
TProxyPort uint16 `json:"tproxy-port"`
LocalDnsPort uint16 `json:"local-dns-port"`
MTU uint32 `json:"mtu"`
StrictRoute bool `json:"strict-route"`
TUNStack string `json:"tun-implementation"`
}
type URLTestOptions struct {
ConnectionTestUrl string `json:"connection-test-url"`
URLTestInterval DurationInSeconds `json:"url-test-interval"`
// URLTestIdleTimeout DurationInSeconds `json:"url-test-idle-timeout"`
}
type RouteOptions struct {
ResolveDestination bool `json:"resolve-destination"`
IPv6Mode option.DomainStrategy `json:"ipv6-mode"`
BypassLAN bool `json:"bypass-lan"`
AllowConnectionFromLAN bool `json:"allow-connection-from-lan"`
}
type TLSTricks struct {
EnableFragment bool `json:"enable-fragment"`
FragmentSize string `json:"fragment-size"`
FragmentSleep string `json:"fragment-sleep"`
MixedSNICase bool `json:"mixed-sni-case"`
EnablePadding bool `json:"enable-padding"`
PaddingSize string `json:"padding-size"`
}
type MuxOptions struct {
Enable bool `json:"enable"`
Padding bool `json:"padding"`
MaxStreams int `json:"max-streams"`
Protocol string `json:"protocol"`
}
type WarpOptions struct {
Id string `json:"id"`
EnableWarp bool `json:"enable"`
Mode string `json:"mode"`
WireguardConfigStr string `json:"wireguard-config"`
WireguardConfig WarpWireguardConfig `json:"wireguardConfig"` // TODO check
FakePackets string `json:"noise"`
FakePacketSize string `json:"noise-size"`
FakePacketDelay string `json:"noise-delay"`
FakePacketMode string `json:"noise-mode"`
CleanIP string `json:"clean-ip"`
CleanPort uint16 `json:"clean-port"`
Account WarpAccount
}
func DefaultHiddifyOptions() *HiddifyOptions {
return &HiddifyOptions{
DNSOptions: DNSOptions{
RemoteDnsAddress: "1.1.1.1",
RemoteDnsDomainStrategy: option.DomainStrategy(dns.DomainStrategyAsIS),
DirectDnsAddress: "1.1.1.1",
DirectDnsDomainStrategy: option.DomainStrategy(dns.DomainStrategyAsIS),
IndependentDNSCache: false,
EnableFakeDNS: false,
EnableDNSRouting: false,
},
InboundOptions: InboundOptions{
EnableTun: false,
SetSystemProxy: false,
MixedPort: 12334,
TProxyPort: 12335,
LocalDnsPort: 16450,
MTU: 9000,
StrictRoute: true,
TUNStack: "mixed",
},
URLTestOptions: URLTestOptions{
ConnectionTestUrl: "http://cp.cloudflare.com/",
URLTestInterval: DurationInSeconds(600),
// URLTestIdleTimeout: DurationInSeconds(6000),
},
RouteOptions: RouteOptions{
ResolveDestination: false,
IPv6Mode: option.DomainStrategy(dns.DomainStrategyAsIS),
BypassLAN: false,
AllowConnectionFromLAN: false,
},
LogLevel: "warn",
// LogFile: "/dev/null",
LogFile: "box.log",
Region: "other",
EnableClashApi: true,
ClashApiPort: 16756,
ClashApiSecret: "",
// GeoIPPath: "geoip.db",
// GeoSitePath: "geosite.db",
Rules: []Rule{},
Mux: MuxOptions{
Enable: false,
Padding: true,
MaxStreams: 8,
Protocol: "h2mux",
},
TLSTricks: TLSTricks{
EnableFragment: false,
FragmentSize: "10-100",
FragmentSleep: "50-200",
MixedSNICase: false,
EnablePadding: false,
PaddingSize: "1200-1500",
},
UseXrayCoreWhenPossible: false,
}
}
+185
View File
@@ -0,0 +1,185 @@
package config
import (
"encoding/json"
"fmt"
"net"
C "github.com/sagernet/sing-box/constant"
"github.com/sagernet/sing-box/option"
)
type outboundMap map[string]interface{}
func patchOutboundMux(base option.Outbound, configOpt HiddifyOptions, obj outboundMap) outboundMap {
if configOpt.Mux.Enable {
multiplex := option.OutboundMultiplexOptions{
Enabled: true,
Padding: configOpt.Mux.Padding,
MaxStreams: configOpt.Mux.MaxStreams,
Protocol: configOpt.Mux.Protocol,
}
obj["multiplex"] = multiplex
// } else {
// delete(obj, "multiplex")
}
return obj
}
func patchOutboundTLSTricks(base option.Outbound, configOpt HiddifyOptions, obj outboundMap) outboundMap {
if base.Type == C.TypeSelector || base.Type == C.TypeURLTest || base.Type == C.TypeBlock || base.Type == C.TypeDNS {
return obj
}
if isOutboundReality(base) {
return obj
}
var tls *option.OutboundTLSOptions
var transport *option.V2RayTransportOptions
if base.VLESSOptions.OutboundTLSOptionsContainer.TLS != nil {
tls = base.VLESSOptions.OutboundTLSOptionsContainer.TLS
transport = base.VLESSOptions.Transport
} else if base.TrojanOptions.OutboundTLSOptionsContainer.TLS != nil {
tls = base.TrojanOptions.OutboundTLSOptionsContainer.TLS
transport = base.TrojanOptions.Transport
} else if base.VMessOptions.OutboundTLSOptionsContainer.TLS != nil {
tls = base.VMessOptions.OutboundTLSOptionsContainer.TLS
transport = base.VMessOptions.Transport
}
if base.Type == C.TypeXray {
if configOpt.TLSTricks.EnableFragment {
if obj["xray_fragment"] == nil || obj["xray_fragment"].(map[string]any)["packets"] == "" {
obj["xray_fragment"] = map[string]any{
"packets": "tlshello",
"length": configOpt.TLSTricks.FragmentSize,
"interval": configOpt.TLSTricks.FragmentSleep,
}
}
}
}
if base.Type == C.TypeDirect {
return patchOutboundFragment(base, configOpt, obj)
}
if tls == nil || !tls.Enabled || transport == nil {
return obj
}
if transport.Type != C.V2RayTransportTypeWebsocket && transport.Type != C.V2RayTransportTypeGRPC && transport.Type != C.V2RayTransportTypeHTTPUpgrade {
return obj
}
if outtls, ok := obj["tls"].(map[string]interface{}); ok {
obj = patchOutboundFragment(base, configOpt, obj)
tlsTricks := tls.TLSTricks
if tlsTricks == nil {
tlsTricks = &option.TLSTricksOptions{}
}
tlsTricks.MixedCaseSNI = tlsTricks.MixedCaseSNI || configOpt.TLSTricks.MixedSNICase
if configOpt.TLSTricks.EnablePadding {
tlsTricks.PaddingMode = "random"
tlsTricks.PaddingSize = configOpt.TLSTricks.PaddingSize
// fmt.Printf("--------------------%+v----%+v", tlsTricks.PaddingSize, configOpt)
outtls["utls"] = map[string]interface{}{
"enabled": true,
"fingerprint": "custom",
}
}
outtls["tls_tricks"] = tlsTricks
// if tlsTricks.MixedCaseSNI || tlsTricks.PaddingMode != "" {
// // } else {
// // tls["tls_tricks"] = nil
// }
// fmt.Printf("-------%+v------------- ", tlsTricks)
}
return obj
}
func patchOutboundFragment(base option.Outbound, configOpt HiddifyOptions, obj outboundMap) outboundMap {
if configOpt.TLSTricks.EnableFragment {
obj["tcp_fast_open"] = false
obj["tls_fragment"] = option.TLSFragmentOptions{
Enabled: configOpt.TLSTricks.EnableFragment,
Size: configOpt.TLSTricks.FragmentSize,
Sleep: configOpt.TLSTricks.FragmentSleep,
}
}
return obj
}
func isOutboundReality(base option.Outbound) bool {
// this function checks reality status ONLY FOR VLESS.
// Some other protocols can also use reality, but it's discouraged as stated in the reality document
if base.Type != C.TypeVLESS {
return false
}
if base.VLESSOptions.OutboundTLSOptionsContainer.TLS == nil {
return false
}
if base.VLESSOptions.OutboundTLSOptionsContainer.TLS.Reality == nil {
return false
}
return base.VLESSOptions.OutboundTLSOptionsContainer.TLS.Reality.Enabled
}
func patchOutbound(base option.Outbound, configOpt HiddifyOptions, staticIpsDns map[string][]string) (*option.Outbound, string, error) {
formatErr := func(err error) error {
return fmt.Errorf("error patching outbound[%s][%s]: %w", base.Tag, base.Type, err)
}
err := patchWarp(&base, &configOpt, true, staticIpsDns)
if err != nil {
return nil, "", formatErr(err)
}
var outbound option.Outbound
jsonData, err := base.MarshalJSON()
if err != nil {
return nil, "", formatErr(err)
}
var obj outboundMap
err = json.Unmarshal(jsonData, &obj)
if err != nil {
return nil, "", formatErr(err)
}
var serverDomain string
if detour, ok := obj["detour"].(string); !ok || detour == "" {
if server, ok := obj["server"].(string); ok {
if server != "" && net.ParseIP(server) == nil {
serverDomain = fmt.Sprintf("full:%s", server)
}
}
}
obj = patchOutboundTLSTricks(base, configOpt, obj)
switch base.Type {
case C.TypeVMess, C.TypeVLESS, C.TypeTrojan, C.TypeShadowsocks:
obj = patchOutboundMux(base, configOpt, obj)
}
modifiedJson, err := json.Marshal(obj)
if err != nil {
return nil, "", formatErr(err)
}
err = outbound.UnmarshalJSON(modifiedJson)
if err != nil {
return nil, "", formatErr(err)
}
return &outbound, serverDomain, nil
}
// func (o outboundMap) transportType() string {
// if transport, ok := o["transport"].(map[string]interface{}); ok {
// if transportType, ok := transport["type"].(string); ok {
// return transportType
// }
// }
// return ""
// }
+142
View File
@@ -0,0 +1,142 @@
package config
import (
"bytes"
"context"
_ "embed"
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/hiddify/ray2sing/ray2sing"
"github.com/sagernet/sing-box/experimental/libbox"
"github.com/sagernet/sing-box/option"
"github.com/sagernet/sing/common/batch"
SJ "github.com/sagernet/sing/common/json"
"github.com/xmdhs/clash2singbox/convert"
"github.com/xmdhs/clash2singbox/model/clash"
"gopkg.in/yaml.v3"
)
//go:embed config.json.template
var configByte []byte
func ParseConfig(path string, debug bool) ([]byte, error) {
content, err := os.ReadFile(path)
os.Chdir(filepath.Dir(path))
if err != nil {
return nil, err
}
return ParseConfigContent(string(content), debug, nil, false)
}
func ParseConfigContentToOptions(contentstr string, debug bool, configOpt *HiddifyOptions, fullConfig bool) (*option.Options, error) {
content, err := ParseConfigContent(contentstr, debug, configOpt, fullConfig)
if err != nil {
return nil, err
}
var options option.Options
err = json.Unmarshal(content, &options)
if err != nil {
return nil, err
}
return &options, nil
}
func ParseConfigContent(contentstr string, debug bool, configOpt *HiddifyOptions, fullConfig bool) ([]byte, error) {
if configOpt == nil {
configOpt = DefaultHiddifyOptions()
}
content := []byte(contentstr)
var jsonObj map[string]interface{} = make(map[string]interface{})
fmt.Printf("Convert using json\n")
var tmpJsonResult any
jsonDecoder := json.NewDecoder(SJ.NewCommentFilter(bytes.NewReader(content)))
if err := jsonDecoder.Decode(&tmpJsonResult); err == nil {
if tmpJsonObj, ok := tmpJsonResult.(map[string]interface{}); ok {
if tmpJsonObj["outbounds"] == nil {
jsonObj["outbounds"] = []interface{}{jsonObj}
} else {
if fullConfig || (configOpt != nil && configOpt.EnableFullConfig) {
jsonObj = tmpJsonObj
} else {
jsonObj["outbounds"] = tmpJsonObj["outbounds"]
}
}
} else if jsonArray, ok := tmpJsonResult.([]map[string]interface{}); ok {
jsonObj["outbounds"] = jsonArray
} else {
return nil, fmt.Errorf("[SingboxParser] Incorrect Json Format")
}
newContent, _ := json.MarshalIndent(jsonObj, "", " ")
return patchConfig(newContent, "SingboxParser", configOpt)
}
v2rayStr, err := ray2sing.Ray2Singbox(string(content), configOpt.UseXrayCoreWhenPossible)
if err == nil {
return patchConfig([]byte(v2rayStr), "V2rayParser", configOpt)
}
fmt.Printf("Convert using clash\n")
clashObj := clash.Clash{}
if err := yaml.Unmarshal(content, &clashObj); err == nil && clashObj.Proxies != nil {
if len(clashObj.Proxies) == 0 {
return nil, fmt.Errorf("[ClashParser] no outbounds found")
}
converted, err := convert.Clash2sing(clashObj)
if err != nil {
return nil, fmt.Errorf("[ClashParser] converting clash to sing-box error: %w", err)
}
output := configByte
output, err = convert.Patch(output, converted, "", "", nil)
if err != nil {
return nil, fmt.Errorf("[ClashParser] patching clash config error: %w", err)
}
return patchConfig(output, "ClashParser", configOpt)
}
return nil, fmt.Errorf("unable to determine config format")
}
func patchConfig(content []byte, name string, configOpt *HiddifyOptions) ([]byte, error) {
options := option.Options{}
err := json.Unmarshal(content, &options)
if err != nil {
return nil, fmt.Errorf("[SingboxParser] unmarshal error: %w", err)
}
b, _ := batch.New(context.Background(), batch.WithConcurrencyNum[*option.Outbound](2))
for _, base := range options.Outbounds {
out := base
b.Go(base.Tag, func() (*option.Outbound, error) {
err := patchWarp(&out, configOpt, false, nil)
if err != nil {
return nil, fmt.Errorf("[Warp] patch warp error: %w", err)
}
// options.Outbounds[i] = base
return &out, nil
})
}
if res, err := b.WaitAndGetResult(); err != nil {
return nil, err
} else {
for i, base := range options.Outbounds {
options.Outbounds[i] = *res[base.Tag].Value
}
}
content, _ = json.MarshalIndent(options, "", " ")
fmt.Printf("%s\n", content)
return validateResult(content, name)
}
func validateResult(content []byte, name string) ([]byte, error) {
err := libbox.CheckConfig(string(content))
if err != nil {
return nil, fmt.Errorf("[%s] invalid sing-box config: %w", name, err)
}
return content, nil
}
+96
View File
@@ -0,0 +1,96 @@
package config
import (
"strconv"
"strings"
"github.com/sagernet/sing-box/option"
)
type Rule struct {
RuleSetUrl string `json:"rule-set-url"`
Domains string `json:"domains"`
IP string `json:"ip"`
Port string `json:"port"`
Network string `json:"network"`
Protocol string `json:"protocol"`
Outbound string `json:"outbound"`
}
func (r *Rule) MakeRule() option.DefaultRule {
rule := option.DefaultRule{}
if len(r.Domains) > 0 {
rule = makeDomainRule(rule, strings.Split(r.Domains, ","))
}
if len(r.IP) > 0 {
rule = makeIpRule(rule, strings.Split(r.IP, ","))
}
if len(r.Port) > 0 {
rule = makePortRule(rule, strings.Split(r.Port, ","))
}
if len(r.Network) > 0 {
rule.Network = append(rule.Network, r.Network)
}
if len(r.Protocol) > 0 {
rule.Protocol = append(rule.Protocol, strings.Split(r.Protocol, ",")...)
}
return rule
}
func (r *Rule) MakeDNSRule() option.DefaultDNSRule {
rule := option.DefaultDNSRule{}
domains := strings.Split(r.Domains, ",")
for _, item := range domains {
if strings.HasPrefix(item, "geosite:") {
rule.Geosite = append(rule.Geosite, strings.TrimPrefix(item, "geosite:"))
} else if strings.HasPrefix(item, "full:") {
rule.Domain = append(rule.Domain, strings.ToLower(strings.TrimPrefix(item, "full:")))
} else if strings.HasPrefix(item, "domain:") {
rule.DomainSuffix = append(rule.DomainSuffix, strings.ToLower(strings.TrimPrefix(item, "domain:")))
} else if strings.HasPrefix(item, "regexp:") {
rule.DomainRegex = append(rule.DomainRegex, strings.ToLower(strings.TrimPrefix(item, "regexp:")))
} else if strings.HasPrefix(item, "keyword:") {
rule.DomainKeyword = append(rule.DomainKeyword, strings.ToLower(strings.TrimPrefix(item, "keyword:")))
}
}
return rule
}
func makeDomainRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.HasPrefix(item, "geosite:") {
options.Geosite = append(options.Geosite, strings.TrimPrefix(item, "geosite:"))
} else if strings.HasPrefix(item, "full:") {
options.Domain = append(options.Domain, strings.ToLower(strings.TrimPrefix(item, "full:")))
} else if strings.HasPrefix(item, "domain:") {
options.DomainSuffix = append(options.DomainSuffix, strings.ToLower(strings.TrimPrefix(item, "domain:")))
} else if strings.HasPrefix(item, "regexp:") {
options.DomainRegex = append(options.DomainRegex, strings.ToLower(strings.TrimPrefix(item, "regexp:")))
} else if strings.HasPrefix(item, "keyword:") {
options.DomainKeyword = append(options.DomainKeyword, strings.ToLower(strings.TrimPrefix(item, "keyword:")))
}
}
return options
}
func makeIpRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.HasPrefix(item, "geoip:") {
options.GeoIP = append(options.GeoIP, strings.TrimPrefix(item, "geoip:"))
} else {
options.IPCIDR = append(options.IPCIDR, item)
}
}
return options
}
func makePortRule(options option.DefaultRule, list []string) option.DefaultRule {
for _, item := range list {
if strings.Contains(item, ":") {
options.PortRange = append(options.PortRange, item)
} else if i, err := strconv.Atoi(item); err == nil {
options.Port = append(options.Port, uint16(i))
}
}
return options
}
+75
View File
@@ -0,0 +1,75 @@
package config
import (
context "context"
"fmt"
"log"
"net"
"os"
"path/filepath"
"github.com/sagernet/sing-box/option"
"google.golang.org/grpc"
)
type server struct {
UnimplementedCoreServiceServer
}
func String(s string) *string {
return &s
}
func (s *server) ParseConfig(ctx context.Context, in *ParseConfigRequest) (*ParseConfigResponse, error) {
config, err := ParseConfig(in.TempPath, in.Debug)
if err != nil {
return &ParseConfigResponse{Error: String(err.Error())}, nil
}
err = os.WriteFile(in.Path, config, 0o644)
if err != nil {
return nil, err
}
return &ParseConfigResponse{Error: String("")}, nil
}
func (s *server) GenerateFullConfig(ctx context.Context, in *GenerateConfigRequest) (*GenerateConfigResponse, error) {
os.Chdir(filepath.Dir(in.Path))
content, err := os.ReadFile(in.Path)
if err != nil {
return nil, err
}
var options option.Options
err = options.UnmarshalJSON(content)
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}
config, err := BuildConfigJson(*DefaultHiddifyOptions(), options)
if err != nil {
return nil, err
}
return &GenerateConfigResponse{
Config: config,
}, nil
}
func StartGRPCServer(port uint16) error {
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
s := grpc.NewServer()
RegisterCoreServiceServer(s, &server{})
log.Println("Server started on :", port)
go func() {
if err := s.Serve(lis); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}()
return nil
}
+25
View File
@@ -0,0 +1,25 @@
package config
import (
"encoding/json"
"time"
)
type DurationInSeconds int
func (d DurationInSeconds) MarshalJSON() ([]byte, error) {
return json.Marshal(int64(d))
}
func (d *DurationInSeconds) UnmarshalJSON(bytes []byte) error {
var v int64
if err := json.Unmarshal(bytes, &v); err != nil {
return err
}
*d = DurationInSeconds(v)
return nil
}
func (d DurationInSeconds) Duration() time.Duration {
return time.Duration(d) * time.Second
}
+270
View File
@@ -0,0 +1,270 @@
package config
import (
"encoding/base64"
"fmt"
"log/slog"
"net/netip"
"os"
"strings"
"github.com/bepass-org/warp-plus/warp"
"github.com/hiddify/hiddify-core/v2/common"
C "github.com/sagernet/sing-box/constant"
// "github.com/bepass-org/wireguard-go/warp"
"github.com/hiddify/hiddify-core/v2/db"
"github.com/sagernet/sing-box/option"
T "github.com/sagernet/sing-box/option"
)
type SingboxConfig struct {
Type string `json:"type"`
Tag string `json:"tag"`
Server string `json:"server"`
ServerPort int `json:"server_port"`
LocalAddress []string `json:"local_address"`
PrivateKey string `json:"private_key"`
PeerPublicKey string `json:"peer_public_key"`
Reserved []int `json:"reserved"`
MTU int `json:"mtu"`
}
func wireGuardToSingbox(wgConfig WarpWireguardConfig, server string, port uint16) (*T.Outbound, error) {
clientID, _ := base64.StdEncoding.DecodeString(wgConfig.ClientID)
if len(clientID) < 2 {
clientID = []byte{0, 0, 0}
}
out := T.Outbound{
Type: "wireguard",
Tag: "WARP",
WireGuardOptions: T.WireGuardOutboundOptions{
ServerOptions: T.ServerOptions{
Server: server,
ServerPort: port,
},
PrivateKey: wgConfig.PrivateKey,
PeerPublicKey: wgConfig.PeerPublicKey,
Reserved: []uint8{clientID[0], clientID[1], clientID[2]},
// Reserved: []uint8{0, 0, 0},
MTU: 1330,
},
}
ips := []string{wgConfig.LocalAddressIPv4 + "/24", wgConfig.LocalAddressIPv6 + "/128"}
for _, addr := range ips {
if addr == "" {
continue
}
prefix, err := netip.ParsePrefix(addr)
if err != nil {
return nil, err // Handle the error appropriately
}
out.WireGuardOptions.LocalAddress = append(out.WireGuardOptions.LocalAddress, prefix)
}
return &out, nil
}
func getRandomIP() string {
ipPort, err := warp.RandomWarpEndpoint(true, true)
if err == nil {
return ipPort.Addr().String()
}
return "engage.cloudflareclient.com"
}
func generateWarp(license string, host string, port uint16, fakePackets string, fakePacketsSize string, fakePacketsDelay string, fakePacketsMode string) (*T.Outbound, error) {
_, _, wgConfig, err := GenerateWarpInfo(license, "", "")
if err != nil {
return nil, err
}
if wgConfig == nil {
return nil, fmt.Errorf("invalid warp config")
}
return GenerateWarpSingbox(*wgConfig, host, port, fakePackets, fakePacketsSize, fakePacketsDelay, fakePacketsMode)
}
func GenerateWarpSingbox(wgConfig WarpWireguardConfig, host string, port uint16, fakePackets string, fakePacketsSize string, fakePacketsDelay string, fakePacketMode string) (*T.Outbound, error) {
if host == "" {
host = "auto4"
}
if (host == "auto" || host == "auto4" || host == "auto6") && fakePackets == "" {
fakePackets = "1-3"
}
if fakePackets != "" && fakePacketsSize == "" {
fakePacketsSize = "10-30"
}
if fakePackets != "" && fakePacketsDelay == "" {
fakePacketsDelay = "10-30"
}
singboxConfig, err := wireGuardToSingbox(wgConfig, host, port)
if err != nil {
fmt.Printf("%v %v", singboxConfig, err)
return nil, err
}
singboxConfig.WireGuardOptions.FakePackets = fakePackets
singboxConfig.WireGuardOptions.FakePacketsSize = fakePacketsSize
singboxConfig.WireGuardOptions.FakePacketsDelay = fakePacketsDelay
singboxConfig.WireGuardOptions.FakePacketsMode = fakePacketMode
return singboxConfig, nil
}
func GenerateWarpInfo(license string, oldAccountId string, oldAccessToken string) (*warp.Identity, string, *WarpWireguardConfig, error) {
if oldAccountId != "" && oldAccessToken != "" {
err := warp.DeleteDevice(oldAccessToken, oldAccountId)
if err != nil {
fmt.Printf("Error in removing old device: %v\n", err)
} else {
fmt.Printf("Old Device Removed")
}
}
l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
identity, err := warp.CreateIdentityOnly(l, license)
res := "Error!"
var warpcfg WarpWireguardConfig
if err == nil {
res = "Success"
res = fmt.Sprintf("Warp+ enabled: %t\n", identity.Account.WarpPlus)
res += fmt.Sprintf("\nAccount type: %s\n", identity.Account.AccountType)
warpcfg = WarpWireguardConfig{
PrivateKey: identity.PrivateKey,
PeerPublicKey: identity.Config.Peers[0].PublicKey,
LocalAddressIPv4: identity.Config.Interface.Addresses.V4,
LocalAddressIPv6: identity.Config.Interface.Addresses.V6,
ClientID: identity.Config.ClientID,
}
}
return &identity, res, &warpcfg, err
}
func getOrGenerateWarpLocallyIfNeeded(warpOptions *WarpOptions) WarpWireguardConfig {
if warpOptions.WireguardConfig.PrivateKey != "" {
return warpOptions.WireguardConfig
}
table := db.GetTable[WarpOptions]()
dbWarpOptions, err := table.Get(warpOptions.Id)
if err == nil && dbWarpOptions.WireguardConfig.PrivateKey != "" {
return warpOptions.WireguardConfig
}
license := ""
if len(warpOptions.Id) == 26 { // warp key is 26 characters long
license = warpOptions.Id
} else if len(warpOptions.Id) > 28 && warpOptions.Id[2] == '_' { // warp key is 26 characters long
license = warpOptions.Id[3:]
}
accountidentity, _, wireguardConfig, err := GenerateWarpInfo(license, warpOptions.Account.AccountID, warpOptions.Account.AccessToken)
if err != nil {
return WarpWireguardConfig{}
}
warpOptions.Account = WarpAccount{
AccountID: accountidentity.ID,
AccessToken: accountidentity.Token,
}
warpOptions.WireguardConfig = *wireguardConfig
table.UpdateInsert(warpOptions)
return *wireguardConfig
}
func patchWarp(base *option.Outbound, configOpt *HiddifyOptions, final bool, staticIpsDns map[string][]string) error {
if base.Type == C.TypeCustom {
if warp, ok := base.CustomOptions["warp"].(map[string]interface{}); ok {
key, _ := warp["key"].(string)
host, _ := warp["host"].(string)
port, _ := warp["port"].(uint16)
detour, _ := warp["detour"].(string)
fakePackets, _ := warp["fake_packets"].(string)
fakePacketsSize, _ := warp["fake_packets_size"].(string)
fakePacketsDelay, _ := warp["fake_packets_delay"].(string)
fakePacketsMode, _ := warp["fake_packets_mode"].(string)
var warpOutbound *T.Outbound
var err error
is_saved_key := len(key) > 1 && key[0] == 'p'
if (configOpt == nil || !final) && is_saved_key {
return nil
}
var wireguardConfig WarpWireguardConfig
if is_saved_key {
var warpOpt *WarpOptions
if key == "p1" {
warpOpt = &configOpt.Warp
} else if key == "p2" {
warpOpt = &configOpt.Warp2
} else {
warpOpt = &WarpOptions{
Id: key,
}
}
warpOpt.Id = key
wireguardConfig = getOrGenerateWarpLocallyIfNeeded(warpOpt)
} else {
_, _, wgConfig, err := GenerateWarpInfo(key, "", "")
if err != nil {
return err
}
wireguardConfig = *wgConfig
}
warpOutbound, err = GenerateWarpSingbox(wireguardConfig, host, port, fakePackets, fakePacketsSize, fakePacketsDelay, fakePacketsMode)
if err != nil {
fmt.Printf("Error generating warp config: %v", err)
return err
}
warpOutbound.WireGuardOptions.Detour = detour
base.Type = C.TypeWireGuard
base.WireGuardOptions = warpOutbound.WireGuardOptions
}
}
if final && base.Type == C.TypeWireGuard {
host := base.WireGuardOptions.Server
if host == "default" || host == "random" || host == "auto" || host == "auto4" || host == "auto6" || isBlockedDomain(host) {
// if base.WireGuardOptions.Detour != "" {
// base.WireGuardOptions.Server = "162.159.192.1"
// } else {
rndDomain := strings.ToLower(generateRandomString(20))
staticIpsDns[rndDomain] = []string{}
if host != "auto4" {
if host == "auto6" || common.CanConnectIPv6() {
randomIpPort, _ := warp.RandomWarpEndpoint(false, true)
staticIpsDns[rndDomain] = append(staticIpsDns[rndDomain], randomIpPort.Addr().String())
}
}
if host != "auto6" {
randomIpPort, _ := warp.RandomWarpEndpoint(true, false)
staticIpsDns[rndDomain] = append(staticIpsDns[rndDomain], randomIpPort.Addr().String())
}
base.WireGuardOptions.Server = rndDomain
// }
}
if base.WireGuardOptions.ServerPort == 0 {
port := warp.RandomWarpPort()
base.WireGuardOptions.ServerPort = port
}
if base.WireGuardOptions.Detour != "" {
if base.WireGuardOptions.MTU < 100 {
base.WireGuardOptions.MTU = 1280
}
base.WireGuardOptions.FakePackets = ""
base.WireGuardOptions.FakePacketsDelay = ""
base.WireGuardOptions.FakePacketsSize = ""
}
// if base.WireGuardOptions.Detour == "" {
// base.WireGuardOptions.GSO = runtime.GOOS != "windows"
// }
}
return nil
}
+50
View File
@@ -0,0 +1,50 @@
package config
import (
"encoding/json"
)
type WarpAccount struct {
AccountID string `json:"account-id"`
AccessToken string `json:"access-token"`
}
type WarpWireguardConfig struct {
PrivateKey string `json:"private-key"`
LocalAddressIPv4 string `json:"local-address-ipv4"`
LocalAddressIPv6 string `json:"local-address-ipv6"`
PeerPublicKey string `json:"peer-public-key"`
ClientID string `json:"client-id"`
}
type WarpGenerationResponse struct {
WarpAccount
Log string `json:"log"`
Config WarpWireguardConfig `json:"config"`
}
func GenerateWarpAccount(licenseKey string, accountId string, accessToken string) (string, error) {
identity, log, wg, err := GenerateWarpInfo(licenseKey, accountId, accessToken)
if err != nil {
return "", err
}
warpAccount := WarpAccount{
AccountID: identity.ID,
AccessToken: identity.Token,
}
warpConfig := WarpWireguardConfig{
PrivateKey: wg.PrivateKey,
LocalAddressIPv4: wg.LocalAddressIPv4,
LocalAddressIPv6: wg.LocalAddressIPv6,
PeerPublicKey: wg.PeerPublicKey,
ClientID: wg.ClientID,
}
response := WarpGenerationResponse{warpAccount, log, warpConfig}
responseJson, err := json.Marshal(response)
if err != nil {
return "", err
}
return string(responseJson), nil
}