From d203c53c0b317060d7920ffac5a9cd04f55932b2 Mon Sep 17 00:00:00 2001 From: igoogolx <27353191+igoogolx@users.noreply.github.com> Date: Sat, 11 Jan 2025 17:52:30 +0800 Subject: [PATCH] v1.25.0 (#59) * refactor(api/traffic): merge now and traffic * feat(api): dns statistic * feat(api): dns statistic * chore: upgrade dashboard --- api/routes/dns.go | 61 ++++++++++++++++++++++++++++++++++ api/routes/routes.go | 1 + api/routes/traffic.go | 69 ++++++++------------------------------- internal/dns/client.go | 10 ++++++ internal/dns/statistic.go | 47 ++++++++++++++++++++++++++ scripts/generate.go | 2 +- 6 files changed, 133 insertions(+), 57 deletions(-) create mode 100644 api/routes/dns.go create mode 100644 internal/dns/statistic.go diff --git a/api/routes/dns.go b/api/routes/dns.go new file mode 100644 index 0000000..5f8e9c0 --- /dev/null +++ b/api/routes/dns.go @@ -0,0 +1,61 @@ +package routes + +import ( + "bytes" + "encoding/json" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" + "github.com/gorilla/websocket" + "github.com/igoogolx/itun2socks/internal/dns" + "net/http" + "time" +) + +func dnsRouter() http.Handler { + r := chi.NewRouter() + r.Get("/statistic", getDnsStatistic) + return r +} + +type Dns struct { + Success int32 `json:"success"` + Fail int32 `json:"fail"` +} + +func getDnsStatistic(w http.ResponseWriter, r *http.Request) { + var wsConn *websocket.Conn + if websocket.IsWebSocketUpgrade(r) { + var err error + wsConn, err = upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + } + + if wsConn == nil { + w.Header().Set("Content-Type", "application/json") + render.Status(r, http.StatusOK) + } + + tick := time.NewTicker(time.Second) + defer tick.Stop() + buf := &bytes.Buffer{} + var err error + for range tick.C { + buf.Reset() + if err := json.NewEncoder(buf).Encode(dns.GetStatistic()); err != nil { + break + } + + if wsConn == nil { + _, err = w.Write(buf.Bytes()) + w.(http.Flusher).Flush() + } else { + err = wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()) + } + + if err != nil { + break + } + } +} diff --git a/api/routes/routes.go b/api/routes/routes.go index 8a31480..5751761 100644 --- a/api/routes/routes.go +++ b/api/routes/routes.go @@ -90,6 +90,7 @@ func Start(addr string, secret string) error { r.Mount("/manager", managerRouter()) r.Mount("/is-admin", isAdminRouter()) r.Mount("/heartbeat", heartbeatRouter()) + r.Mount("/dns", dnsRouter()) }) go FileServer(r) err := http.ListenAndServe(addr, r) diff --git a/api/routes/traffic.go b/api/routes/traffic.go index d6d46f6..3edcb63 100644 --- a/api/routes/traffic.go +++ b/api/routes/traffic.go @@ -9,7 +9,6 @@ import ( "github.com/igoogolx/itun2socks/internal/constants" "github.com/igoogolx/itun2socks/internal/tunnel/statistic" "net/http" - "strconv" "time" ) @@ -23,8 +22,7 @@ var ( func trafficRouter() http.Handler { r := chi.NewRouter() - r.Get("/now", getNow) - r.Get("/total", getTotal) + r.Get("/", getTraffic) return r } @@ -33,12 +31,17 @@ type TrafficItem struct { Down int64 `json:"download"` } -type Traffic struct { +type Speed struct { Proxy TrafficItem `json:"proxy"` Direct TrafficItem `json:"direct"` } -func getNow(w http.ResponseWriter, r *http.Request) { +type Traffic struct { + Speed Speed `json:"speed"` + Total statistic.Total `json:"total"` +} + +func getTraffic(w http.ResponseWriter, r *http.Request) { var wsConn *websocket.Conn if websocket.IsWebSocketUpgrade(r) { var err error @@ -63,8 +66,11 @@ func getNow(w http.ResponseWriter, r *http.Request) { proxyUp, proxyDown := t.Now(constants.PolicyProxy) directUp, directDown := t.Now(constants.PolicyDirect) if err := json.NewEncoder(buf).Encode(Traffic{ - TrafficItem{proxyUp, proxyDown}, - TrafficItem{directUp, directDown}, + Speed: Speed{ + TrafficItem{proxyUp, proxyDown}, + TrafficItem{directUp, directDown}, + }, + Total: *t.GetTotal(), }); err != nil { break } @@ -81,52 +87,3 @@ func getNow(w http.ResponseWriter, r *http.Request) { } } } - -func getTotal(w http.ResponseWriter, r *http.Request) { - if !websocket.IsWebSocketUpgrade(r) { - snapshot := statistic.DefaultManager.Connections() - render.JSON(w, r, snapshot) - return - } - - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - - intervalStr := r.URL.Query().Get("interval") - interval := 1000 - if intervalStr != "" { - t, err := strconv.Atoi(intervalStr) - if err != nil { - render.Status(r, http.StatusBadRequest) - render.JSON(w, r, ErrBadRequest) - return - } - - interval = t - } - - buf := &bytes.Buffer{} - sendTotal := func() error { - buf.Reset() - total := statistic.DefaultManager.GetTotal() - if err := json.NewEncoder(buf).Encode(total); err != nil { - return err - } - - return conn.WriteMessage(websocket.TextMessage, buf.Bytes()) - } - - if err := sendTotal(); err != nil { - return - } - - tick := time.NewTicker(time.Millisecond * time.Duration(interval)) - defer tick.Stop() - for range tick.C { - if err := sendTotal(); err != nil { - break - } - } -} diff --git a/internal/dns/client.go b/internal/dns/client.go index f28b0f7..54d254b 100644 --- a/internal/dns/client.go +++ b/internal/dns/client.go @@ -101,6 +101,7 @@ func Handle(dnsMessage *D.Msg, metadata *constant.Metadata) (*D.Msg, error) { defer mux.RUnlock() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() + var err error start := time.Now() question, qType, err := getDnsQuestion(dnsMessage) @@ -117,6 +118,15 @@ func Handle(dnsMessage *D.Msg, metadata *constant.Metadata) (*D.Msg, error) { if err != nil { return nil, fmt.Errorf("fail to get dns resolver, err: %v, question: %v", err, question) } + + defer func() { + if err != nil { + countFailQuery(dnsRule.GetPolicy()) + } else { + countSuccessQuery(dnsRule.GetPolicy()) + } + }() + res, err := dnsMap[dnsRule.GetPolicy()].ExchangeContext(ctx, dnsMessage) if err != nil { return nil, fmt.Errorf("fail to exchange dns message, err: %v, question: %v", err, question) diff --git a/internal/dns/statistic.go b/internal/dns/statistic.go new file mode 100644 index 0000000..89940aa --- /dev/null +++ b/internal/dns/statistic.go @@ -0,0 +1,47 @@ +package dns + +import ( + "github.com/igoogolx/itun2socks/internal/constants" + "go.uber.org/atomic" +) + +type StatisticItem struct { + Success *atomic.Int32 `json:"success"` + Fail *atomic.Int32 `json:"fail"` +} + +type Statistic struct { + Proxy StatisticItem `json:"proxy"` + Direct StatisticItem `json:"direct"` +} + +var static = Statistic{ + Proxy: StatisticItem{ + Success: atomic.NewInt32(0), + Fail: atomic.NewInt32(0), + }, + Direct: StatisticItem{ + Success: atomic.NewInt32(0), + Fail: atomic.NewInt32(0), + }, +} + +func countSuccessQuery(policy constants.Policy) { + if policy == constants.PolicyDirect { + static.Direct.Success.Inc() + } else if policy == constants.PolicyProxy { + static.Proxy.Success.Inc() + } +} + +func countFailQuery(policy constants.Policy) { + if policy == constants.PolicyDirect { + static.Direct.Fail.Inc() + } else if policy == constants.PolicyProxy { + static.Proxy.Fail.Inc() + } +} + +func GetStatistic() Statistic { + return static +} diff --git a/scripts/generate.go b/scripts/generate.go index b11749e..531e96f 100644 --- a/scripts/generate.go +++ b/scripts/generate.go @@ -13,7 +13,7 @@ import ( func main() { download("https://github.com/igoogolx/lux-rules/releases/download/v2.4.0/rules.tar.gz", filepath.Join("internal", "cfg", "distribution", "ruleEngine", "rules.tar.gz")) - download("https://github.com/igoogolx/lux-client/releases/download/v1.11.0/dist-ui.tar.gz", filepath.Join("api", "routes", "dist.tar.gz")) + download("https://github.com/igoogolx/lux-client/releases/download/v1.11.2/dist-ui.tar.gz", filepath.Join("api", "routes", "dist.tar.gz")) } func download(url string, outputPath string) {