diff --git a/README.md b/README.md index 8fb9977..4d44c01 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ kube-tools memory -P 8888 -S 10240 ```shell kube-tools memory -P 8888 -S 10240 ``` +### 4. metrics +```shell +kube-tools metric -P 8080 +``` ## Contribution If you find any issues or have any improvement suggestions, feel free to raise an issue or submit a pull request. We welcome your contributions! diff --git a/cmd/root.go b/cmd/root.go index 7a5f885..7ebd5e3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,8 +22,10 @@ import ( "github.com/wonderivan/logger" "os" + "kube-tools/pkg/metric" "kube-tools/pkg/scan_port" "kube-tools/pkg/simulate_memory" + "kube-tools/pkg/traceroute" ) // 定义根命令 @@ -42,6 +44,8 @@ func Execute() { // 注册子端口扫描子命令到root根命令 rootCmd.AddCommand(scan_port.ExecutePortScan()) rootCmd.AddCommand(simulate_memory.ExecuteSimulateRaw()) + rootCmd.AddCommand(metric.ExecuteMetric()) + rootCmd.AddCommand(traceroute.ExecuteTraceroute()) if err := rootCmd.Execute(); err != nil { logger.Error(err) os.Exit(1) diff --git a/go.mod b/go.mod index 3efe2e0..3a27f26 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,23 @@ go 1.20 require ( github.com/Ullaakut/nmap/v3 v3.0.2 + github.com/prometheus/client_golang v1.16.0 github.com/spf13/cobra v1.7.0 github.com/wonderivan/logger v1.0.0 + golang.org/x/net v0.10.0 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/sync v0.1.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.11.0 // indirect + google.golang.org/protobuf v1.31.0 // indirect ) diff --git a/pkg/metric/metric.go b/pkg/metric/metric.go new file mode 100644 index 0000000..89406a5 --- /dev/null +++ b/pkg/metric/metric.go @@ -0,0 +1,84 @@ +/* +Copyright 2023 QKP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metric + +import ( + "fmt" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/spf13/cobra" + "net/http" + "time" +) + +// 定义全局metric指标变量 +var ( + // Counter 是一个特殊的指标,用于跟踪事件计数器,例如请求的数量、错误的数量等 + opsProcessed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "kube_tools_processed_ops_total", + Help: "Total number of processed ops.", + }) + opsFailed = promauto.NewCounter(prometheus.CounterOpts{ + Name: "kube_tools_failed_ops_total", + Help: "Total number of failed ops.", + }) + // Histogram选项。它有多个字段,用于设置Histogram的属性,如桶的数量、桶的边界等。这个结构体通常用于创建指标的Histogram类型 + opsDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{ + Name: "kube_tools_duration_seconds", + Help: "Duration of ops in seconds.", + }, []string{"method", "host", "client", "path", "status"}) +) + +// ExecuteMetric 定义端口扫描执行器 +func ExecuteMetric() *cobra.Command { + // metric 定义端口扫描子命令 + var metric = &cobra.Command{ + Use: "metric", + Short: "expose metric information for testing.", + Long: "expose metric information for testing.", + Run: startServer, + } + // 初始化命令 + metric.Flags().StringP("port", "P", "8080", "ports to scan") + return metric +} + +func startServer(cmd *cobra.Command, args []string) { + port, _ := cmd.Flags().GetString("port") + http.Handle("/metrics", promhttp.Handler()) + http.HandleFunc("/", metricHandler) + http.ListenAndServe(":"+port, nil) +} + +func metricHandler(w http.ResponseWriter, r *http.Request) { + start := time.Now() + // 模拟判断请求是否成功的逻辑,这里我们随机模拟 + isSuccess := (time.Now().UnixNano() % 2) == 0 + if isSuccess { + w.WriteHeader(http.StatusOK) + w.Write([]byte("Success")) + opsProcessed.Inc() + } else { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Fail")) + opsFailed.Inc() + } + duration := time.Since(start).Seconds() + // 记录HTTP请求耗时的指标 + opsDuration.WithLabelValues(r.Method, r.Host, r.RemoteAddr, r.URL.Path, fmt.Sprintf("%d", http.StatusOK)).Observe(duration) +} diff --git a/pkg/traceroute/traceroute.go b/pkg/traceroute/traceroute.go new file mode 100644 index 0000000..061ad1b --- /dev/null +++ b/pkg/traceroute/traceroute.go @@ -0,0 +1,132 @@ +/* +Copyright 2023 QKP Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package traceroute + +import ( + "fmt" + "github.com/spf13/cobra" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "log" + "net" + "os" + "time" +) + +// ExecuteTraceroute 定义端口扫描执行器 +func ExecuteTraceroute() *cobra.Command { + // traceroute 定义网络链路跟踪命令 + var traceroute = &cobra.Command{ + Use: "traceroute", + Short: "traceroute the network with icmp protocol.", + Long: "traceroute the network with icmp protocol.", + Run: runner, + } + // 初始化命令 + traceroute.Flags().StringP("host", "H", "localhost", "host to scan") + return traceroute +} + +// runner 扫描端口 +func runner(cmd *cobra.Command, args []string) { + host, _ := cmd.Flags().GetString("host") + + ips, err := net.LookupIP(host) + if err != nil { + log.Fatal(err) + } + var dst net.IPAddr + for _, ip := range ips { + if ip.To4() != nil { + dst.IP = ip + fmt.Printf("using %v for tracing an IP packet route to %s\n", dst.IP, host) + break + } + } + if dst.IP == nil { + log.Fatal("no A record found") + } + + c, err := net.ListenPacket("ip4:1", "0.0.0.0") // ICMP for IPv4 + if err != nil { + log.Fatal(err) + } + defer c.Close() + p := ipv4.NewPacketConn(c) + + if err := p.SetControlMessage(ipv4.FlagTTL|ipv4.FlagSrc|ipv4.FlagDst|ipv4.FlagInterface, true); err != nil { + log.Fatal(err) + } + wm := icmp.Message{ + Type: ipv4.ICMPTypeEcho, Code: 0, + Body: &icmp.Echo{ + ID: os.Getpid() & 0xffff, + Data: []byte("HELLO-R-U-THERE"), + }, + } + + rb := make([]byte, 1500) + for i := 1; i <= 64; i++ { // up to 64 hops + wm.Body.(*icmp.Echo).Seq = i + wb, err := wm.Marshal(nil) + if err != nil { + log.Fatal(err) + } + if err := p.SetTTL(i); err != nil { + log.Fatal(err) + } + + // In the real world usually there are several + // multiple traffic-engineered paths for each hop. + // You may need to probe a few times to each hop. + begin := time.Now() + if _, err := p.WriteTo(wb, nil, &dst); err != nil { + log.Fatal(err) + } + if err := p.SetReadDeadline(time.Now().Add(3 * time.Second)); err != nil { + log.Fatal(err) + } + n, cm, peer, err := p.ReadFrom(rb) + if err != nil { + if err, ok := err.(net.Error); ok && err.Timeout() { + fmt.Printf("%v\t*\n", i) + continue + } + log.Fatal(err) + } + rm, err := icmp.ParseMessage(1, rb[:n]) + if err != nil { + log.Fatal(err) + } + rtt := time.Since(begin) + + // In the real world you need to determine whether the + // received message is yours using ControlMessage.Src, + // ControlMessage.Dst, icmp.Echo.ID and icmp.Echo.Seq. + switch rm.Type { + case ipv4.ICMPTypeTimeExceeded: + names, _ := net.LookupAddr(peer.String()) + fmt.Printf("%d\t%v %+v %v\n\t%+v\n", i, peer, names, rtt, cm) + case ipv4.ICMPTypeEchoReply: + names, _ := net.LookupAddr(peer.String()) + fmt.Printf("%d\t%v %+v %v\n\t%+v\n", i, peer, names, rtt, cm) + return + default: + log.Printf("unknown ICMP message: %+v\n", rm) + } + } +}