sing-box/common/urltest/urltest.go

132 lines
2.9 KiB
Go
Raw Normal View History

2022-07-22 05:51:08 +00:00
package urltest
import (
"context"
2025-01-08 02:34:45 +00:00
"crypto/tls"
2022-07-22 05:51:08 +00:00
"net"
"net/http"
"net/url"
"sync"
"time"
2025-01-08 02:34:45 +00:00
"github.com/sagernet/sing-box/adapter"
2024-10-30 05:09:05 +00:00
C "github.com/sagernet/sing-box/constant"
2023-09-23 12:26:20 +00:00
"github.com/sagernet/sing/common"
2022-07-22 05:51:08 +00:00
M "github.com/sagernet/sing/common/metadata"
N "github.com/sagernet/sing/common/network"
2025-01-08 02:34:45 +00:00
"github.com/sagernet/sing/common/ntp"
2022-07-22 05:51:08 +00:00
)
2025-01-08 02:34:45 +00:00
var _ adapter.URLTestHistoryStorage = (*HistoryStorage)(nil)
2022-07-22 05:51:08 +00:00
type HistoryStorage struct {
access sync.RWMutex
2025-01-08 02:34:45 +00:00
delayHistory map[string]*adapter.URLTestHistory
updateHook chan<- struct{}
2022-07-22 05:51:08 +00:00
}
func NewHistoryStorage() *HistoryStorage {
return &HistoryStorage{
2025-01-08 02:34:45 +00:00
delayHistory: make(map[string]*adapter.URLTestHistory),
2022-07-22 05:51:08 +00:00
}
}
func (s *HistoryStorage) SetHook(hook chan<- struct{}) {
s.updateHook = hook
2023-07-02 08:45:30 +00:00
}
2025-01-08 02:34:45 +00:00
func (s *HistoryStorage) LoadURLTestHistory(tag string) *adapter.URLTestHistory {
2022-07-22 05:51:08 +00:00
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)
2023-07-02 08:45:30 +00:00
s.access.Unlock()
s.notifyUpdated()
2022-07-22 05:51:08 +00:00
}
2025-01-08 02:34:45 +00:00
func (s *HistoryStorage) StoreURLTestHistory(tag string, history *adapter.URLTestHistory) {
2022-07-22 05:51:08 +00:00
s.access.Lock()
s.delayHistory[tag] = history
2023-07-02 08:45:30 +00:00
s.access.Unlock()
s.notifyUpdated()
}
func (s *HistoryStorage) notifyUpdated() {
updateHook := s.updateHook
if updateHook != nil {
select {
case updateHook <- struct{}{}:
default:
}
2023-07-02 08:45:30 +00:00
}
2022-07-22 05:51:08 +00:00
}
func (s *HistoryStorage) Close() error {
s.updateHook = nil
return nil
}
2022-07-22 05:51:08 +00:00
func URLTest(ctx context.Context, link string, detour N.Dialer) (t uint16, err error) {
2023-04-13 01:03:08 +00:00
if link == "" {
link = "https://www.gstatic.com/generate_204"
}
2022-07-22 05:51:08 +00:00
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()
2023-09-23 12:26:20 +00:00
if earlyConn, isEarlyConn := common.Cast[N.EarlyConn](instance); isEarlyConn && earlyConn.NeedHandshake() {
start = time.Now()
}
2022-07-22 05:51:08 +00:00
req, err := http.NewRequest(http.MethodHead, link, nil)
if err != nil {
return
}
client := http.Client{
2023-09-23 12:26:20 +00:00
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return instance, nil
},
2025-01-08 02:34:45 +00:00
TLSClientConfig: &tls.Config{
Time: ntp.TimeFuncFromContext(ctx),
RootCAs: adapter.RootPoolFromContext(ctx),
},
2023-09-23 12:26:20 +00:00
},
2022-07-22 05:51:08 +00:00
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
2024-10-30 05:09:05 +00:00
Timeout: C.TCPTimeout,
2022-07-22 05:51:08 +00:00
}
defer client.CloseIdleConnections()
2023-09-23 12:26:20 +00:00
resp, err := client.Do(req.WithContext(ctx))
2022-07-22 05:51:08 +00:00
if err != nil {
return
}
resp.Body.Close()
t = uint16(time.Since(start) / time.Millisecond)
return
}