hysteria/app/internal/utils/update.go

96 lines
2.5 KiB
Go

package utils
import (
"context"
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/apernet/hysteria/core/v2/client"
)
const (
updateCheckEndpoint = "https://api.hy2.io/v1/update"
updateCheckTimeout = 10 * time.Second
)
type UpdateChecker struct {
CurrentVersion string
Platform string
Architecture string
Channel string
Side string
Client *http.Client
}
func NewServerUpdateChecker(currentVersion, platform, architecture, channel string) *UpdateChecker {
return &UpdateChecker{
CurrentVersion: currentVersion,
Platform: platform,
Architecture: architecture,
Channel: channel,
Side: "server",
Client: &http.Client{
Timeout: updateCheckTimeout,
},
}
}
// NewClientUpdateChecker ensures that update checks are routed through a HyClient,
// not being sent directly. This safeguard is CRITICAL, especially in scenarios where
// users use Hysteria to bypass censorship. Making direct HTTPS requests to the API
// endpoint could be easily spotted by censors (through SNI, for example), and could
// serve as a signal to identify and penalize Hysteria users.
func NewClientUpdateChecker(currentVersion, platform, architecture, channel string, hyClient client.Client) *UpdateChecker {
return &UpdateChecker{
CurrentVersion: currentVersion,
Platform: platform,
Architecture: architecture,
Channel: channel,
Side: "client",
Client: &http.Client{
Timeout: updateCheckTimeout,
Transport: &http.Transport{
DialContext: func(_ context.Context, network, addr string) (net.Conn, error) {
// Unfortunately HyClient doesn't support context for now
return hyClient.TCP(addr)
},
},
},
}
}
type UpdateResponse struct {
HasUpdate bool `json:"update"`
LatestVersion string `json:"lver"`
URL string `json:"url"`
Urgent bool `json:"urgent"`
}
func (uc *UpdateChecker) Check() (*UpdateResponse, error) {
url := fmt.Sprintf("%s?cver=%s&plat=%s&arch=%s&chan=%s&side=%s",
updateCheckEndpoint,
uc.CurrentVersion,
uc.Platform,
uc.Architecture,
uc.Channel,
uc.Side,
)
resp, err := uc.Client.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
var uResp UpdateResponse
decoder := json.NewDecoder(resp.Body)
if err := decoder.Decode(&uResp); err != nil {
return nil, err
}
return &uResp, nil
}