diff --git a/examples/provider/remote.json b/examples/provider/remote.json index 4616f7f9..ef26e2e0 100644 --- a/examples/provider/remote.json +++ b/examples/provider/remote.json @@ -47,6 +47,30 @@ // shareable links, or plain link list. "url": "https://example.com/subscription.txt", "user_agent": "sing-box", + // Custom HTTP headers sent with each subscription request. + // Some subscription panels (e.g. Remnawave with incy/happ + // integration) require device-identification headers to + // return the real server list instead of a dummy config. + // + // !!! DO NOT COPY THE VALUE BELOW !!! + // x-hwid must be generated per-device. A random UUID will + // be rejected by the panel. Generate yours on Linux: + // + // python3 -c "import hashlib,socket,getpass;m=open('/etc/machine-id').read().strip();d=hashlib.sha256(f'{m}|{socket.gethostname()}|Linux|amd64|{getpass.getuser()}'.encode()).hexdigest();h=hashlib.sha256(('incy_hwid_'+d).encode()).hexdigest();print(f'{h[:8]}-{h[8:12]}-{h[12:16]}-{h[16:20]}-{h[20:32]}'.upper())" + // + // Algorithm: SHA256("incy_hwid_" + SHA256(deviceId)) + // Linux deviceId: "machineId|hostname|Linux|amd64|user" + // Android deviceId: "androidId|manufacturer|model|brand|device|product|board|hardware" + // Ref: https://github.com/INCY-DEV/incy-docs/blob/main/ru/dev-docs/hwid.md + "headers": { + "x-hwid": ["REPLACE_WITH_GENERATED_HWID"], + // Platform: linux, android, ios, windows, macos + "x-device-os": ["linux"], + // OS version — on Linux: uname -r + "x-ver-os": ["6.12.0-arch1-1"], + // Device model — on Linux: uname -m + "x-device-model": ["x86_64"] + }, // Fetch the subscription through this outbound instead of the // default route (useful when the subscription host is blocked). "download_detour": "direct", diff --git a/option/provider.go b/option/provider.go index 9f11422a..34f75913 100644 --- a/option/provider.go +++ b/option/provider.go @@ -55,6 +55,7 @@ type ProviderLocalOptions struct { type ProviderRemoteOptions struct { URL string `json:"url"` UserAgent string `json:"user_agent,omitempty"` + Headers badoption.HTTPHeader `json:"headers,omitempty"` DownloadDetour string `json:"download_detour,omitempty"` UpdateInterval badoption.Duration `json:"update_interval,omitempty"` diff --git a/provider/remote/provider.go b/provider/remote/provider.go index 119d75a7..7f575b07 100644 --- a/provider/remote/provider.go +++ b/provider/remote/provider.go @@ -59,6 +59,7 @@ type ProviderRemote struct { updateInterval time.Duration exclude *regexp.Regexp include *regexp.Regexp + headers http.Header } func NewProviderRemote(ctx context.Context, router adapter.Router, logFactory log.Factory, tag string, options option.ProviderRemoteOptions) (adapter.Provider, error) { @@ -94,6 +95,7 @@ func NewProviderRemote(ctx context.Context, router adapter.Router, logFactory lo url: options.URL, userAgent: userAgent, downloadDetour: options.DownloadDetour, + headers: options.Headers.Build(), updateInterval: updateInterval, exclude: (*regexp.Regexp)(options.Exclude), include: (*regexp.Regexp)(options.Include), @@ -191,6 +193,9 @@ func (s *ProviderRemote) fetch(ctx context.Context) error { req.Header.Set("If-None-Match", s.lastEtag) } req.Header.Set("User-Agent", s.userAgent) + for name, values := range s.headers { + req.Header[name] = values + } resp, err := client.Do(req.WithContext(ctx)) if err != nil { return err