github.com/v2fly/v2ray-core/v5@v5.16.2-0.20240507031116-8191faa6e095/app/observatory/observer.go (about) 1 //go:build !confonly 2 // +build !confonly 3 4 package observatory 5 6 import ( 7 "context" 8 "net" 9 "net/http" 10 "net/url" 11 "sort" 12 "sync" 13 "time" 14 15 "github.com/golang/protobuf/proto" 16 17 core "github.com/v2fly/v2ray-core/v5" 18 "github.com/v2fly/v2ray-core/v5/common" 19 v2net "github.com/v2fly/v2ray-core/v5/common/net" 20 "github.com/v2fly/v2ray-core/v5/common/session" 21 "github.com/v2fly/v2ray-core/v5/common/signal/done" 22 "github.com/v2fly/v2ray-core/v5/common/task" 23 "github.com/v2fly/v2ray-core/v5/features/extension" 24 "github.com/v2fly/v2ray-core/v5/features/outbound" 25 "github.com/v2fly/v2ray-core/v5/transport/internet/tagged" 26 ) 27 28 type Observer struct { 29 config *Config 30 ctx context.Context 31 32 statusLock sync.Mutex 33 status []*OutboundStatus 34 35 finished *done.Instance 36 37 ohm outbound.Manager 38 } 39 40 func (o *Observer) GetObservation(ctx context.Context) (proto.Message, error) { 41 return &ObservationResult{Status: o.status}, nil 42 } 43 44 func (o *Observer) Type() interface{} { 45 return extension.ObservatoryType() 46 } 47 48 func (o *Observer) Start() error { 49 if o.config != nil && len(o.config.SubjectSelector) != 0 { 50 o.finished = done.New() 51 go o.background() 52 } 53 return nil 54 } 55 56 func (o *Observer) Close() error { 57 if o.finished != nil { 58 return o.finished.Close() 59 } 60 return nil 61 } 62 63 func (o *Observer) background() { 64 for !o.finished.Done() { 65 hs, ok := o.ohm.(outbound.HandlerSelector) 66 if !ok { 67 newError("outbound.Manager is not a HandlerSelector").WriteToLog() 68 return 69 } 70 71 outbounds := hs.Select(o.config.SubjectSelector) 72 sort.Strings(outbounds) 73 74 o.updateStatus(outbounds) 75 76 slept := false 77 for _, v := range outbounds { 78 result := o.probe(v) 79 o.updateStatusForResult(v, &result) 80 if o.finished.Done() { 81 return 82 } 83 sleepTime := time.Second * 10 84 if o.config.ProbeInterval != 0 { 85 sleepTime = time.Duration(o.config.ProbeInterval) 86 } 87 time.Sleep(sleepTime) 88 slept = true 89 } 90 if !slept { 91 sleepTime := time.Second * 10 92 if o.config.ProbeInterval != 0 { 93 sleepTime = time.Duration(o.config.ProbeInterval) 94 } 95 time.Sleep(sleepTime) 96 } 97 } 98 } 99 100 func (o *Observer) updateStatus(outbounds []string) { 101 o.statusLock.Lock() 102 defer o.statusLock.Unlock() 103 // TODO should remove old inbound that is removed 104 _ = outbounds 105 } 106 107 func (o *Observer) probe(outbound string) ProbeResult { 108 errorCollectorForRequest := newErrorCollector() 109 110 httpTransport := http.Transport{ 111 Proxy: func(*http.Request) (*url.URL, error) { 112 return nil, nil 113 }, 114 DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) { 115 var connection net.Conn 116 taskErr := task.Run(ctx, func() error { 117 // MUST use V2Fly's built in context system 118 dest, err := v2net.ParseDestination(network + ":" + addr) 119 if err != nil { 120 return newError("cannot understand address").Base(err) 121 } 122 trackedCtx := session.TrackedConnectionError(o.ctx, errorCollectorForRequest) 123 conn, err := tagged.Dialer(trackedCtx, dest, outbound) 124 if err != nil { 125 return newError("cannot dial remote address ", dest).Base(err) 126 } 127 connection = conn 128 return nil 129 }) 130 if taskErr != nil { 131 return nil, newError("cannot finish connection").Base(taskErr) 132 } 133 return connection, nil 134 }, 135 TLSHandshakeTimeout: time.Second * 5, 136 } 137 httpClient := &http.Client{ 138 Transport: &httpTransport, 139 CheckRedirect: func(req *http.Request, via []*http.Request) error { 140 return http.ErrUseLastResponse 141 }, 142 Jar: nil, 143 Timeout: time.Second * 5, 144 } 145 var GETTime time.Duration 146 err := task.Run(o.ctx, func() error { 147 startTime := time.Now() 148 probeURL := "https://api.v2fly.org/checkConnection.svgz" 149 if o.config.ProbeUrl != "" { 150 probeURL = o.config.ProbeUrl 151 } 152 response, err := httpClient.Get(probeURL) 153 if err != nil { 154 return newError("outbound failed to relay connection").Base(err) 155 } 156 if response.Body != nil { 157 response.Body.Close() 158 } 159 endTime := time.Now() 160 GETTime = endTime.Sub(startTime) 161 return nil 162 }) 163 if err != nil { 164 fullerr := newError("underlying connection failed").Base(errorCollectorForRequest.UnderlyingError()) 165 fullerr = newError("with outbound handler report").Base(fullerr) 166 fullerr = newError("GET request failed:", err).Base(fullerr) 167 fullerr = newError("the outbound ", outbound, " is dead:").Base(fullerr) 168 fullerr = fullerr.AtInfo() 169 fullerr.WriteToLog() 170 return ProbeResult{Alive: false, LastErrorReason: fullerr.Error()} 171 } 172 newError("the outbound ", outbound, " is alive:", GETTime.Seconds()).AtInfo().WriteToLog() 173 return ProbeResult{Alive: true, Delay: GETTime.Milliseconds()} 174 } 175 176 func (o *Observer) updateStatusForResult(outbound string, result *ProbeResult) { 177 o.statusLock.Lock() 178 defer o.statusLock.Unlock() 179 var status *OutboundStatus 180 if location := o.findStatusLocationLockHolderOnly(outbound); location != -1 { 181 status = o.status[location] 182 } else { 183 status = &OutboundStatus{} 184 o.status = append(o.status, status) 185 } 186 187 status.LastTryTime = time.Now().Unix() 188 status.OutboundTag = outbound 189 status.Alive = result.Alive 190 if result.Alive { 191 status.Delay = result.Delay 192 status.LastSeenTime = status.LastTryTime 193 status.LastErrorReason = "" 194 } else { 195 status.LastErrorReason = result.LastErrorReason 196 status.Delay = 99999999 197 } 198 } 199 200 func (o *Observer) findStatusLocationLockHolderOnly(outbound string) int { 201 for i, v := range o.status { 202 if v.OutboundTag == outbound { 203 return i 204 } 205 } 206 return -1 207 } 208 209 func New(ctx context.Context, config *Config) (*Observer, error) { 210 var outboundManager outbound.Manager 211 err := core.RequireFeatures(ctx, func(om outbound.Manager) { 212 outboundManager = om 213 }) 214 if err != nil { 215 return nil, newError("Cannot get depended features").Base(err) 216 } 217 return &Observer{ 218 config: config, 219 ctx: ctx, 220 ohm: outboundManager, 221 }, nil 222 } 223 224 func init() { 225 common.Must(common.RegisterConfig((*Config)(nil), func(ctx context.Context, config interface{}) (interface{}, error) { 226 return New(ctx, config.(*Config)) 227 })) 228 }