diff --git a/app/cmd/client.go b/app/cmd/client.go index b20facb030..9a785d26bb 100644 --- a/app/cmd/client.go +++ b/app/cmd/client.go @@ -179,7 +179,7 @@ func (c *clientConfig) fillConnFactory(hyConfig *client.Config) error { if hyConfig.ServerAddr.Network() == "udphop" { hopAddr := hyConfig.ServerAddr.(*udphop.UDPHopAddr) newFunc = func(addr net.Addr) (net.PacketConn, error) { - return udphop.NewUDPHopPacketConn(hopAddr, c.Transport.UDP.HopInterval) + return udphop.NewUDPHopPacketConn(hopAddr, c.Transport.UDP.HopInterval, nil) } } else { newFunc = func(addr net.Addr) (net.PacketConn, error) { diff --git a/app/go.mod b/app/go.mod index 51f43b1bcf..57e7c763ac 100644 --- a/app/go.mod +++ b/app/go.mod @@ -17,7 +17,7 @@ require ( ) require ( - github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f // indirect + github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d // indirect github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect diff --git a/app/go.sum b/app/go.sum index 239e55d551..aedcb4cd32 100644 --- a/app/go.sum +++ b/app/go.sum @@ -40,8 +40,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f h1:uVh0qpEslrWjgzx9vOcyCqsOY3c9kofDZ1n+qaw35ZY= github.com/apernet/go-tproxy v0.0.0-20230809025308-8f4723fd742f/go.mod h1:xkkq9D4ygcldQQhKS/w9CadiCKwCngU7K9E3DaKahpM= -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f h1:4jBGc3SlgQT8YFqHhfnK7EVFVY292CxagfNqfPiQZhY= -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d h1:K1DMSNtPcaZ/lihYmOHnjThNfUX7cD6SNuVRFnVLVmI= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= diff --git a/app/internal/utils/bpsconv_test.go b/app/internal/utils/bpsconv_test.go index 2de4d3dc2f..32265605a0 100644 --- a/app/internal/utils/bpsconv_test.go +++ b/app/internal/utils/bpsconv_test.go @@ -13,12 +13,12 @@ func TestStringToBps(t *testing.T) { wantErr bool }{ {"bps", args{"800 bps"}, 100, false}, - {"kbps", args{"800 kbps"}, 102400, false}, - {"mbps", args{"800 mbps"}, 104857600, false}, - {"gbps", args{"800 gbps"}, 107374182400, false}, - {"tbps", args{"800 tbps"}, 109951162777600, false}, - {"mbps simp", args{"100m"}, 13107200, false}, - {"gbps simp upper", args{"2G"}, 268435456, false}, + {"kbps", args{"800 kbps"}, 100_000, false}, + {"mbps", args{"800 mbps"}, 100_000_000, false}, + {"gbps", args{"800 gbps"}, 100_000_000_000, false}, + {"tbps", args{"800 tbps"}, 100_000_000_000_000, false}, + {"mbps simp", args{"100m"}, 12_500_000, false}, + {"gbps simp upper", args{"2G"}, 250_000_000, false}, {"invalid 1", args{"damn"}, 0, true}, {"invalid 2", args{"6444"}, 0, true}, {"invalid 3", args{"5.4 mbps"}, 0, true}, diff --git a/app/internal/utils/geoloader.go b/app/internal/utils/geoloader.go index 56cb205e4f..fa9775ba71 100644 --- a/app/internal/utils/geoloader.go +++ b/app/internal/utils/geoloader.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io" "net/http" "os" @@ -15,6 +16,7 @@ const ( geoipURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geoip.dat" geositeFilename = "geosite.dat" geositeURL = "https://cdn.jsdelivr.net/gh/Loyalsoldier/v2ray-rules-dat@release/geosite.dat" + geoDlTmpPattern = ".hysteria-geoloader.dlpart.*" geoDefaultUpdateInterval = 7 * 24 * time.Hour // 7 days ) @@ -41,6 +43,10 @@ func (l *GeoLoader) shouldDownload(filename string) bool { if os.IsNotExist(err) { return true } + if info.Size() == 0 { + // empty files are loadable by v2geo, but we consider it broken + return true + } dt := time.Now().Sub(info.ModTime()) if l.UpdateInterval == 0 { return dt > geoDefaultUpdateInterval @@ -49,7 +55,7 @@ func (l *GeoLoader) shouldDownload(filename string) bool { } } -func (l *GeoLoader) download(filename, url string) error { +func (l *GeoLoader) downloadAndCheck(filename, url string, checkFunc func(filename string) error) error { l.DownloadFunc(filename, url) resp, err := http.Get(url) @@ -59,16 +65,34 @@ func (l *GeoLoader) download(filename, url string) error { } defer resp.Body.Close() - f, err := os.Create(filename) + f, err := os.CreateTemp(".", geoDlTmpPattern) if err != nil { l.DownloadErrFunc(err) return err } - defer f.Close() + defer os.Remove(f.Name()) _, err = io.Copy(f, resp.Body) - l.DownloadErrFunc(err) - return err + if err != nil { + f.Close() + l.DownloadErrFunc(err) + return err + } + f.Close() + + err = checkFunc(f.Name()) + if err != nil { + l.DownloadErrFunc(fmt.Errorf("integrity check failed: %w", err)) + return err + } + + err = os.Rename(f.Name(), filename) + if err != nil { + l.DownloadErrFunc(fmt.Errorf("rename failed: %w", err)) + return err + } + + return nil } func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) { @@ -81,10 +105,24 @@ func (l *GeoLoader) LoadGeoIP() (map[string]*v2geo.GeoIP, error) { autoDL = true filename = geoipFilename } - if autoDL && l.shouldDownload(filename) { - err := l.download(filename, geoipURL) + if autoDL { + if !l.shouldDownload(filename) { + m, err := v2geo.LoadGeoIP(filename) + if err == nil { + l.geoipMap = m + return m, nil + } + // file is broken, download it again + } + err := l.downloadAndCheck(filename, geoipURL, func(filename string) error { + _, err := v2geo.LoadGeoIP(filename) + return err + }) if err != nil { - return nil, err + // as long as the previous download exists, fallback to it + if _, serr := os.Stat(filename); os.IsNotExist(serr) { + return nil, err + } } } m, err := v2geo.LoadGeoIP(filename) @@ -105,10 +143,24 @@ func (l *GeoLoader) LoadGeoSite() (map[string]*v2geo.GeoSite, error) { autoDL = true filename = geositeFilename } - if autoDL && l.shouldDownload(filename) { - err := l.download(filename, geositeURL) + if autoDL { + if !l.shouldDownload(filename) { + m, err := v2geo.LoadGeoSite(filename) + if err == nil { + l.geositeMap = m + return m, nil + } + // file is broken, download it again + } + err := l.downloadAndCheck(filename, geositeURL, func(filename string) error { + _, err := v2geo.LoadGeoSite(filename) + return err + }) if err != nil { - return nil, err + // as long as the previous download exists, fallback to it + if _, serr := os.Stat(filename); os.IsNotExist(serr) { + return nil, err + } } } m, err := v2geo.LoadGeoSite(filename) diff --git a/core/go.mod b/core/go.mod index 392e6bb774..3268576968 100644 --- a/core/go.mod +++ b/core/go.mod @@ -3,7 +3,7 @@ module github.com/apernet/hysteria/core go 1.21 require ( - github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f + github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d github.com/stretchr/testify v1.8.4 go.uber.org/goleak v1.2.1 golang.org/x/exp v0.0.0-20221205204356-47842c84f3db diff --git a/core/go.sum b/core/go.sum index a44e749c39..fa8c8b83d0 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,5 +1,5 @@ -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f h1:4jBGc3SlgQT8YFqHhfnK7EVFVY292CxagfNqfPiQZhY= -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d h1:K1DMSNtPcaZ/lihYmOHnjThNfUX7cD6SNuVRFnVLVmI= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= diff --git a/extras/go.mod b/extras/go.mod index e22bf77a46..57a25e02b9 100644 --- a/extras/go.mod +++ b/extras/go.mod @@ -15,7 +15,7 @@ require ( ) require ( - github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f // indirect + github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect diff --git a/extras/go.sum b/extras/go.sum index b6d2e2396c..a02e3a8179 100644 --- a/extras/go.sum +++ b/extras/go.sum @@ -1,5 +1,5 @@ -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f h1:4jBGc3SlgQT8YFqHhfnK7EVFVY292CxagfNqfPiQZhY= -github.com/apernet/quic-go v0.41.1-0.20240122005439-5bf4609c416f/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d h1:K1DMSNtPcaZ/lihYmOHnjThNfUX7cD6SNuVRFnVLVmI= +github.com/apernet/quic-go v0.41.1-0.20240301003057-e18162de481d/go.mod h1:4GInxO6ypy63J2NaO5rQx1wRp6K8YHI6zqLG+VswU6I= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6 h1:4NNbNM2Iq/k57qEu7WfL67UrbPq1uFWxW4qODCohi+0= github.com/babolivier/go-doh-client v0.0.0-20201028162107-a76cff4cb8b6/go.mod h1:J29hk+f9lJrblVIfiJOtTFk+OblBawmib4uz/VdKzlg= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= diff --git a/extras/outbounds/ob_socks5.go b/extras/outbounds/ob_socks5.go index 21bc6b6f5d..d6d7add891 100644 --- a/extras/outbounds/ob_socks5.go +++ b/extras/outbounds/ob_socks5.go @@ -31,7 +31,31 @@ type errSOCKS5RequestFailed struct { } func (e errSOCKS5RequestFailed) Error() string { - return fmt.Sprintf("SOCKS5 request failed: %d", e.Rep) + var msg string + // RFC 1928 + switch e.Rep { + case 0x00: + msg = "succeeded" + case 0x01: + msg = "general SOCKS server failure" + case 0x02: + msg = "connection not allowed by ruleset" + case 0x03: + msg = "Network unreachable" + case 0x04: + msg = "Host unreachable" + case 0x05: + msg = "Connection refused" + case 0x06: + msg = "TTL expired" + case 0x07: + msg = "Command not supported" + case 0x08: + msg = "Address type not supported" + default: + msg = "undefined" + } + return fmt.Sprintf("SOCKS5 request failed: %s (%d)", msg, e.Rep) } // socks5Outbound is a PluggableOutbound that connects to the target using diff --git a/extras/transport/udphop/conn.go b/extras/transport/udphop/conn.go index ccb0b38f04..f20c583882 100644 --- a/extras/transport/udphop/conn.go +++ b/extras/transport/udphop/conn.go @@ -17,9 +17,10 @@ const ( ) type udpHopPacketConn struct { - Addr net.Addr - Addrs []net.Addr - HopInterval time.Duration + Addr net.Addr + Addrs []net.Addr + HopInterval time.Duration + ListenUDPFunc ListenUDPFunc connMutex sync.RWMutex prevConn net.PacketConn @@ -43,29 +44,37 @@ type udpPacket struct { Err error } -func NewUDPHopPacketConn(addr *UDPHopAddr, hopInterval time.Duration) (net.PacketConn, error) { +type ListenUDPFunc func() (net.PacketConn, error) + +func NewUDPHopPacketConn(addr *UDPHopAddr, hopInterval time.Duration, listenUDPFunc ListenUDPFunc) (net.PacketConn, error) { if hopInterval == 0 { hopInterval = defaultHopInterval } else if hopInterval < 5*time.Second { return nil, errors.New("hop interval must be at least 5 seconds") } + if listenUDPFunc == nil { + listenUDPFunc = func() (net.PacketConn, error) { + return net.ListenUDP("udp", nil) + } + } addrs, err := addr.addrs() if err != nil { return nil, err } - curConn, err := net.ListenUDP("udp", nil) + curConn, err := listenUDPFunc() if err != nil { return nil, err } hConn := &udpHopPacketConn{ - Addr: addr, - Addrs: addrs, - HopInterval: hopInterval, - prevConn: nil, - currentConn: curConn, - addrIndex: rand.Intn(len(addrs)), - recvQueue: make(chan *udpPacket, packetQueueSize), - closeChan: make(chan struct{}), + Addr: addr, + Addrs: addrs, + HopInterval: hopInterval, + ListenUDPFunc: listenUDPFunc, + prevConn: nil, + currentConn: curConn, + addrIndex: rand.Intn(len(addrs)), + recvQueue: make(chan *udpPacket, packetQueueSize), + closeChan: make(chan struct{}), bufPool: sync.Pool{ New: func() interface{} { return make([]byte, udpBufferSize) @@ -121,7 +130,7 @@ func (u *udpHopPacketConn) hop() { if u.closed { return } - newConn, err := net.ListenUDP("udp", nil) + newConn, err := u.ListenUDPFunc() if err != nil { // Could be temporary, just skip this hop return