123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- package urltest
- import (
- "context"
- "crypto/tls"
- "net"
- "net/http"
- "net/url"
- "sync"
- "time"
- "github.com/sagernet/sing-box/adapter"
- C "github.com/sagernet/sing-box/constant"
- "github.com/sagernet/sing/common"
- M "github.com/sagernet/sing/common/metadata"
- N "github.com/sagernet/sing/common/network"
- "github.com/sagernet/sing/common/ntp"
- )
- var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
- type HistoryStorage struct {
- access sync.RWMutex
- delayHistory map[string]*adapter.URLTestHistory
- updateHook chan<- struct{}
- }
- func NewHistoryStorage() *HistoryStorage {
- return &HistoryStorage{
- delayHistory: make(map[string]*adapter.URLTestHistory),
- }
- }
- func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
- s.updateHook = hook
- }
- func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
- if s == nil {
- return nil
- }
- s.access.RLock()
- defer s.access.RUnlock()
- return s.delayHistory[tag]
- }
- func (s *HistoryStorage) DeleteURLTestHistory(tag string) {
- s.access.Lock()
- delete(s.delayHistory, tag)
- s.notifyUpdated()
- s.access.Unlock()
- }
- func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
- s.access.Lock()
- s.delayHistory[tag] = history
- s.notifyUpdated()
- s.access.Unlock()
- }
- func (s *HistoryStorage) notifyUpdated() {
- updateHook := s.updateHook
- if updateHook != nil {
- select {
- case updateHook <- struct{}{}:
- default:
- }
- }
- }
- func (s *HistoryStorage) Close() error {
- s.access.Lock()
- defer s.access.Unlock()
- s.updateHook = nil
- return nil
- }
- func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
- if link == "" {
- link = "https://www.gstatic.com/generate_204"
- }
- linkURL, err := url.Parse(link)
- if err != nil {
- return
- }
- hostname := linkURL.Hostname()
- port := linkURL.Port()
- if port == "" {
- switch linkURL.Scheme {
- case "http":
- port = "80"
- case "https":
- port = "443"
- }
- }
- start := time.Now()
- instance, err := detour.DialContext(ctx, "tcp", M.ParseSocksaddrHostPortStr(hostname, port))
- if err != nil {
- return
- }
- defer instance.Close()
- if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() {
- start = time.Now()
- }
- req, err := http.NewRequest(http.MethodHead, link, nil)
- if err != nil {
- return
- }
- client := http.Client{
- Transport: &http.Transport{
- DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
- return instance, nil
- },
- TLSClientConfig: &tls.Config{
- Time: ntp.TimeFuncFromContext(ctx),
- RootCAs: adapter.RootPoolFromContext(ctx),
- },
- },
- CheckRedirect: func(req *http.Request, via []*http.Request) error {
- return http.ErrUseLastResponse
- },
- Timeout: C.TCPTimeout,
- }
- defer client.CloseIdleConnections()
- resp, err := client.Do(req.WithContext(ctx))
- if err != nil {
- return
- }
- resp.Body.Close()
- t = uint16(time.Since(start) / time.Millisecond)
- return
- }
|