mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-22 02:47:14 +00:00
fix(panel-proxy): route custom geo and http(s) Telegram through panelProxy
Custom geosite/geoip downloads built their own ssrfSafeTransport and never used the configured Panel Network Proxy, so geo updates failed on servers where GitHub is filtered. Route all custom-geo HTTP (startup probes + downloads) through panelProxy when set, falling back to the direct SSRF-guarded transport otherwise; the target URL stays SSRF-validated. The Telegram bot only honored a socks5:// panel proxy and silently rejected http(s)://, despite the setting advertising both. Branch the fasthttp dialer (FasthttpHTTPDialer for http(s), FasthttpSocksDialer for socks5) and accept all three schemes in the fallback and NewBot validation. Add tests proving the panel proxy is used by custom geo and that the bot dialer speaks HTTP CONNECT vs SOCKS5 per scheme.
This commit is contained in:
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/mhsanaei/3x-ui/v3/database"
|
||||
"github.com/mhsanaei/3x-ui/v3/database/model"
|
||||
"github.com/mhsanaei/3x-ui/v3/logger"
|
||||
"github.com/mhsanaei/3x-ui/v3/util/netproxy"
|
||||
"github.com/mhsanaei/3x-ui/v3/util/netsafe"
|
||||
)
|
||||
|
||||
@@ -73,6 +74,7 @@ type CustomGeoService struct {
|
||||
updateAllGetAll func() ([]model.CustomGeoResource, error)
|
||||
updateAllApply func(id int, onStartup bool) (string, error)
|
||||
updateAllRestart func() error
|
||||
getPanelProxy func() (string, error)
|
||||
}
|
||||
|
||||
func NewCustomGeoService() *CustomGeoService {
|
||||
@@ -82,6 +84,7 @@ func NewCustomGeoService() *CustomGeoService {
|
||||
s.updateAllGetAll = s.GetAll
|
||||
s.updateAllApply = s.applyDownloadAndPersist
|
||||
s.updateAllRestart = func() error { return s.serverService.RestartXrayService() }
|
||||
s.getPanelProxy = (&SettingService{}).GetPanelProxy
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -206,12 +209,32 @@ func ssrfSafeTransport() http.RoundTripper {
|
||||
return cloned
|
||||
}
|
||||
|
||||
func probeCustomGeoURLWithGET(rawURL string) error {
|
||||
sanitizedURL, err := (&CustomGeoService{}).sanitizeURL(rawURL)
|
||||
func (s *CustomGeoService) httpClient(timeout time.Duration) *http.Client {
|
||||
proxyURL := ""
|
||||
if s.getPanelProxy != nil {
|
||||
if p, err := s.getPanelProxy(); err != nil {
|
||||
logger.Warning("custom geo: read panel proxy:", err)
|
||||
} else {
|
||||
proxyURL = strings.TrimSpace(p)
|
||||
}
|
||||
}
|
||||
if proxyURL != "" {
|
||||
client, err := netproxy.NewHTTPClient(proxyURL, timeout)
|
||||
if err != nil {
|
||||
logger.Warningf("custom geo: invalid panel proxy %q, using direct connection: %v", proxyURL, err)
|
||||
} else {
|
||||
return client
|
||||
}
|
||||
}
|
||||
return &http.Client{Timeout: timeout, Transport: ssrfSafeTransport()}
|
||||
}
|
||||
|
||||
func (s *CustomGeoService) probeCustomGeoURLWithGET(rawURL string) error {
|
||||
sanitizedURL, err := s.sanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := &http.Client{Timeout: customGeoProbeTimeout, Transport: ssrfSafeTransport()}
|
||||
client := s.httpClient(customGeoProbeTimeout)
|
||||
req, err := http.NewRequest(http.MethodGet, sanitizedURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -231,12 +254,12 @@ func probeCustomGeoURLWithGET(rawURL string) error {
|
||||
}
|
||||
}
|
||||
|
||||
func probeCustomGeoURL(rawURL string) error {
|
||||
sanitizedURL, err := (&CustomGeoService{}).sanitizeURL(rawURL)
|
||||
func (s *CustomGeoService) probeCustomGeoURL(rawURL string) error {
|
||||
sanitizedURL, err := s.sanitizeURL(rawURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := &http.Client{Timeout: customGeoProbeTimeout, Transport: ssrfSafeTransport()}
|
||||
client := s.httpClient(customGeoProbeTimeout)
|
||||
req, err := http.NewRequest(http.MethodHead, sanitizedURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -251,7 +274,7 @@ func probeCustomGeoURL(rawURL string) error {
|
||||
return nil
|
||||
}
|
||||
if sc == http.StatusMethodNotAllowed || sc == http.StatusNotImplemented {
|
||||
return probeCustomGeoURLWithGET(rawURL)
|
||||
return s.probeCustomGeoURLWithGET(rawURL)
|
||||
}
|
||||
return fmt.Errorf("head status %d", sc)
|
||||
}
|
||||
@@ -283,7 +306,7 @@ func (s *CustomGeoService) EnsureOnStartup() {
|
||||
continue
|
||||
}
|
||||
logger.Infof("custom geo startup id=%d alias=%s path=%s: missing or needs repair, probing source", r.Id, r.Alias, localPath)
|
||||
if err := probeCustomGeoURL(r.Url); err != nil {
|
||||
if err := s.probeCustomGeoURL(r.Url); err != nil {
|
||||
logger.Warningf("custom geo startup id=%d alias=%s url=%s: probe: %v (attempting download anyway)", r.Id, r.Alias, r.Url, err)
|
||||
}
|
||||
_, _ = s.applyDownloadAndPersist(r.Id, true)
|
||||
@@ -366,7 +389,7 @@ func (s *CustomGeoService) downloadToPathOnce(resourceURL, destPath string, last
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Minute, Transport: ssrfSafeTransport()}
|
||||
client := s.httpClient(10 * time.Minute)
|
||||
// lgtm[go/request-forgery]
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user