diff --git a/astro/cmd.go b/astro/cmd.go index b77c8a4..8f99e9c 100644 --- a/astro/cmd.go +++ b/astro/cmd.go @@ -7,6 +7,7 @@ import ( ) var isFormat bool +var jieqi string func init() { Cmd.PersistentFlags().Float64Var(&lon, "lon", -273, "经度,WGS84坐标系") @@ -168,6 +169,61 @@ var CmdStar = &cobra.Command{ }, } +var CmdJieqi = &cobra.Command{ + Use: "jq", + Short: "节气计算", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + fmt.Println("请输入年份或节气") + return + } + year := args[0] + if year == "" { + year = time.Now().Format("2006") + } + year = year[:4] + fmt.Println("年份: ", year) + /* + var jqname = map[string]int{ + "春分": 0, + "清明": 15, + "谷雨": 30, + "立夏": 45, + "小满": 60, + "芒种": 75, + "夏至": 90, + "小暑": 105, + "大暑": 120, + "立秋": 135, + "处暑": 150, + "白露": 165, + "秋分": 180, + "寒露": 195, + "霜降": 210, + "立冬": 225, + "小雪": 240, + "大雪": 255, + "冬至": 270, + "小寒": 285, + "大寒": 300, + "立春": 315, + "雨水": 330, + "惊蛰": 345, + } + if jieqi != "" { + if v, ok := jqname[jieqi]; !ok { + fmt.Println("节气名错误") + return + } else { + fmt.Println("节气名: ", jieqi) + fmt.Println("时间: ", calendar.JieQi(year, v)) + } + } + + */ + }, +} + func CliLoadLonLatHeight() bool { if city != "" { if !GetFromCity(city) { diff --git a/astro/jieqi.go b/astro/jieqi.go new file mode 100644 index 0000000..60a9d48 --- /dev/null +++ b/astro/jieqi.go @@ -0,0 +1 @@ +package astro diff --git a/cert/cert.go b/cert/cert.go index e46ce52..0e0daea 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -105,9 +105,9 @@ func ParseCert(data []byte, pwd string) { switch n := priv.(type) { case *rsa.PrivateKey: starlog.Green("这是一个RSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Size()) - starlog.Green("私钥长度:%d\n", n.N.BitLen()) - starlog.Green("私钥指数:%d\n", n.E) + starlog.Green("公钥位数:%d\n", n.Size()) + starlog.Green("公钥长度:%d\n", n.N.BitLen()) + starlog.Green("公钥指数:%d\n", n.E) starlog.Green("私钥系数:%d\n", n.D) starlog.Green("私钥质数p:%d\n", n.Primes[0]) starlog.Green("私钥质数q:%d\n", n.Primes[1]) @@ -116,8 +116,8 @@ func ParseCert(data []byte, pwd string) { starlog.Green("私钥系数qInv:%d\n", n.Precomputed.Qinv) case *ecdsa.PrivateKey: starlog.Green("这是一个ECDSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) - starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name) + starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize) + starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name) starlog.Green("私钥长度:%d\n", n.Params().BitSize) starlog.Green("私钥系数:%d\n", n.D) starlog.Green("私钥公钥X:%d\n", n.PublicKey.X) @@ -237,9 +237,9 @@ func ParseCert(data []byte, pwd string) { switch n := priv.(type) { case *rsa.PrivateKey: starlog.Green("这是一个RSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Size()) - starlog.Green("私钥长度:%d\n", n.N.BitLen()) - starlog.Green("私钥指数:%d\n", n.E) + starlog.Green("公钥位数:%d\n", n.Size()) + starlog.Green("公钥长度:%d\n", n.N.BitLen()) + starlog.Green("公钥指数:%d\n", n.E) starlog.Green("私钥系数:%d\n", n.D) starlog.Green("私钥质数p:%d\n", n.Primes[0]) starlog.Green("私钥质数q:%d\n", n.Primes[1]) @@ -378,9 +378,9 @@ func ParseCert(data []byte, pwd string) { switch n := priv.(type) { case *rsa.PrivateKey: starlog.Green("这是一个RSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Size()) - starlog.Green("私钥长度:%d\n", n.N.BitLen()) - starlog.Green("私钥指数:%d\n", n.E) + starlog.Green("公钥位数:%d\n", n.Size()) + starlog.Green("公钥长度:%d\n", n.N.BitLen()) + starlog.Green("公钥指数:%d\n", n.E) starlog.Green("私钥系数:%d\n", n.D) starlog.Green("私钥质数p:%d\n", n.Primes[0]) starlog.Green("私钥质数q:%d\n", n.Primes[1]) @@ -640,7 +640,7 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { switch n := priv.(type) { case *rsa.PrivateKey: starlog.Green("这是一个RSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Size()) + starlog.Green("公钥位数:%d\n", n.Size()) case *ecdsa.PrivateKey: starlog.Green("这是一个ECDSA私钥\n") starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) @@ -760,8 +760,8 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { case *rsa.PrivateKey: common = append(common, n) starlog.Green("这是一个RSA私钥\n") - starlog.Green("私钥位数:%d\n", n.Size()) - starlog.Green("私钥长度:%d\n", n.N.BitLen()) + starlog.Green("公钥位数:%d\n", n.Size()) + starlog.Green("公钥长度:%d\n", n.N.BitLen()) case *ecdsa.PrivateKey: common = append(common, n) starlog.Green("这是一个ECDSA私钥\n") diff --git a/go.mod b/go.mod index cb1ac09..bda6a28 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/emersion/go-smtp v0.20.2 github.com/florianl/go-nfqueue/v2 v2.0.0 + github.com/gdamore/tcell/v2 v2.7.1 github.com/go-acme/lego/v4 v4.16.1 github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 @@ -29,6 +30,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 github.com/miekg/dns v1.1.58 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 + github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 github.com/shirou/gopsutil/v4 v4.24.10 github.com/spf13/cobra v1.8.0 github.com/things-go/go-socks5 v0.0.5 @@ -56,6 +58,7 @@ require ( github.com/cpu/goacmedns v0.1.1 // indirect github.com/ebitengine/purego v0.8.1 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect + github.com/gdamore/encoding v1.0.0 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/goccy/go-json v0.10.2 // indirect @@ -71,6 +74,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/kr/fs v0.1.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mdlayher/netlink v1.7.2 // indirect diff --git a/go.sum b/go.sum index 447c3ab..df3ad71 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,10 @@ github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVR github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo= github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk= +github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= +github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ= github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE= github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U= @@ -131,10 +135,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= @@ -159,7 +166,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE= +github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= diff --git a/hash/hash.go b/hash/hash.go index a04fe32..efe6355 100644 --- a/hash/hash.go +++ b/hash/hash.go @@ -29,7 +29,7 @@ var Cmd = &cobra.Command{ var cumethod, method []string var result = make(map[string]string) var err error - cumethod = []string{"md5", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} + cumethod = []string{"md5", "crc32a", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} if ok, _ := this.Flags().GetBool("all"); ok { method = cumethod } else { @@ -79,6 +79,7 @@ func init() { Cmd.Flags().BoolP("file", "f", false, "对指定文件进行校验") Cmd.Flags().BoolP("md5", "m", false, "进行MD5校验(默认)") Cmd.Flags().BoolP("crc32", "c", false, "进行CRC32校验") + Cmd.Flags().Bool("crc32a", false, "进行CRC32A校验") Cmd.Flags().BoolP("sha512", "s", false, "进行SHA512校验") Cmd.Flags().Bool("sha384", false, "进行SHA384校验") Cmd.Flags().Bool("sha256", false, "进行SHA256校验") @@ -98,10 +99,9 @@ func init() { func FileSumAll(filepath string, key string, method []string, shell func(float64)) (map[string]string, error) { result := make(map[string]string) methods := make(map[string]hash.Hash) - var iscrc bool if len(method) == 0 { - method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} + method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32a", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} } fp, err := os.Open(filepath) defer fp.Close() @@ -115,6 +115,7 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64 sum256 := sha256.New() sum224 := sha256.New224() sum1 := sha1.New() + crc32a := crc32.New(crc32.MakeTable(0x04C11DB7)) crcsum := crc32.NewIEEE() md5sum := md5.New() md4sum := md4.New() @@ -130,8 +131,10 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64 switch v { case "md5": methods["md5"] = md5sum + case "crc32a": + methods["crc32a"] = crc32a case "crc32": - iscrc = true + methods["crc32"] = crcsum case "sha1": methods["sha1"] = sum1 case "sha224": @@ -179,31 +182,25 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64 for _, v := range methods { v.Write(buf[0:n]) } - if iscrc { - crcsum.Write(buf[0:n]) - } } for k, v := range methods { result[k] = hex.EncodeToString(v.Sum(nil)) } - if iscrc { - result["crc32"] = hex.EncodeToString(crcsum.Sum(nil)) - } return result, nil } func SumAll(data []byte, key string, method []string) (map[string][]byte, error) { result := make(map[string][]byte) methods := make(map[string]hash.Hash) - var iscrc bool if len(method) == 0 { - method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} + method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32a", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"} } sum512 := sha512.New() sum384 := sha512.New384() sum256 := sha256.New() sum224 := sha256.New224() sum1 := sha1.New() + crc32a := crc32.New(crc32.MakeTable(0x04C11DB7)) crcsum := crc32.NewIEEE() md5sum := md5.New() md4sum := md4.New() @@ -219,8 +216,10 @@ func SumAll(data []byte, key string, method []string) (map[string][]byte, error) switch v { case "md5": methods["md5"] = md5sum + case "crc32a": + methods["crc32a"] = crc32a case "crc32": - iscrc = true + methods["crc32"] = crcsum case "sha1": methods["sha1"] = sum1 case "sha224": @@ -254,15 +253,8 @@ func SumAll(data []byte, key string, method []string) (map[string][]byte, error) for _, v := range methods { v.Write(data) } - if iscrc { - crcsum.Write(data) - } - for k, v := range methods { result[k] = v.Sum(nil) } - if iscrc { - result["crc32"] = crcsum.Sum(nil) - } return result, nil } diff --git a/keygen/keygen.go b/keygen/keygen.go index 7434a3c..825763a 100644 --- a/keygen/keygen.go +++ b/keygen/keygen.go @@ -4,6 +4,7 @@ import ( "b612.me/starcrypto" "b612.me/staros" "crypto" + "crypto/ecdh" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -67,7 +68,7 @@ func (k *KeyGen) Gen() error { if err != nil { return err } - case "ecdsa", "ecdh": + case "ecdsa": var cr elliptic.Curve switch k.Bits { case 224: @@ -90,6 +91,33 @@ func (k *KeyGen) Gen() error { if err != nil { return err } + case "x25519": + priv, err = ecdh.X25519().GenerateKey(rand.Reader) + if err != nil { + return err + } + pub = priv.(*ecdh.PrivateKey).Public() + case "ecdh": + switch k.Bits { + case 256: + priv, err = ecdh.P256().GenerateKey(rand.Reader) + if err != nil { + return err + } + pub = priv.(*ecdh.PrivateKey).Public() + case 384: + priv, err = ecdh.P384().GenerateKey(rand.Reader) + if err != nil { + return err + } + pub = priv.(*ecdh.PrivateKey).Public() + case 521: + priv, err = ecdh.P521().GenerateKey(rand.Reader) + if err != nil { + return err + } + pub = priv.(*ecdh.PrivateKey).Public() + } default: return errors.New("invalid key type,only support rsa,ecdsa") } diff --git a/main.go b/main.go index 27f82f8..a28863b 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ import ( "b612.me/apps/b612/merge" "b612.me/apps/b612/mget" "b612.me/apps/b612/net" + "b612.me/apps/b612/nmon" "b612.me/apps/b612/rmt" "b612.me/apps/b612/search" "b612.me/apps/b612/smtpclient" @@ -55,7 +56,7 @@ func init() { base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd, ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd, calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd, smtpclient.Cmd, - cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd) + cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd) } func main() { diff --git a/mget/wget.go b/mget/wget.go index 072b6c9..e5d1ac0 100644 --- a/mget/wget.go +++ b/mget/wget.go @@ -259,7 +259,10 @@ func (w *Mget) Run() error { } if !w.NoWriteRedo { if len(r) == 0 { - return os.Remove(w.Tareget + ".bgrd") + if staros.Exists(w.Tareget + ".bgrd") { + return os.Remove(w.Tareget + ".bgrd") + } + return nil } return w.Redo.Save() } diff --git a/net/cmd.go b/net/cmd.go index 468b223..7daa1ee 100644 --- a/net/cmd.go +++ b/net/cmd.go @@ -8,6 +8,8 @@ import ( "b612.me/starlog" "fmt" "github.com/spf13/cobra" + "os" + "os/signal" "time" ) @@ -36,6 +38,7 @@ var natt NatThroughs var scanip ScanIP var scanport ScanPort +var monitorip Monitor func init() { CmdNatPClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address") @@ -60,7 +63,7 @@ func init() { CmdNetTrace.Flags().IntVarP(&timeout, "timeout", "t", 800, "超时时间,单位毫秒") CmdNetTrace.Flags().IntVarP(&maxHop, "max-hop", "m", 32, "最大跳数") CmdNetTrace.Flags().BoolVarP(&disableIpInfo, "disable-ipinfo", "D", false, "禁用ip信息查询") - CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "0.0.0.0", "绑定地址") + CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "", "绑定地址") CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点") Cmd.AddCommand(CmdNetTrace, cmdSSHJar) @@ -106,6 +109,17 @@ func init() { CmdScanPort.Flags().IntVarP(&scanport.Retry, "retry", "r", 2, "重试次数") Cmd.AddCommand(CmdScanPort) + CmdMonitorIP.Flags().StringSliceVarP(&monitorip.IPs, "ip", "i", []string{}, "扫描IP地址列表") + CmdMonitorIP.Flags().IntVarP(&monitorip.Port, "port", "p", 80, "TCP模式扫描端口") + CmdMonitorIP.Flags().IntVarP(&monitorip.Timeout, "timeout", "t", 1200, "超时时间,毫秒") + CmdMonitorIP.Flags().IntVarP(&monitorip.Interval, "interval", "I", 1, "扫描间隔,秒") + CmdMonitorIP.Flags().IntVarP(&monitorip.Threads, "threads", "m", 100, "最大线程数") + CmdMonitorIP.Flags().StringVarP(&monitorip.Log, "log", "l", "", "日志文件地址") + CmdMonitorIP.Flags().StringVarP(&monitorip.ScanType, "type", "T", "icmp", "扫描类型") + CmdMonitorIP.Flags().IntVarP(&monitorip.Retry, "retry", "r", 1, "重试次数") + CmdMonitorIP.Flags().BoolVarP(&monitorip.WithHostname, "with-hostname", "H", false, "显示主机名") + Cmd.AddCommand(CmdMonitorIP) + Cmd.AddCommand(tcpkill.Cmd, tcping.Cmd, tcm.Cmd) } @@ -221,6 +235,30 @@ var CmdScanIP = &cobra.Command{ }, } +var CmdMonitorIP = &cobra.Command{ + Use: "monitorip", + Short: "监控IP", + Run: func(cmd *cobra.Command, args []string) { + if len(monitorip.IPs) == 0 { + cmd.Help() + return + } + monitorip.status = make(map[string]bool) + monitorip.stopChan = make(chan struct{}) + sig := make(chan os.Signal) + signal.Notify(sig, os.Interrupt, os.Kill) + go func() { + err := monitorip.Start() + if err != nil { + starlog.Errorln(err) + os.Exit(1) + } + }() + <-sig + monitorip.Stop() + }, +} + var CmdScanPort = &cobra.Command{ Use: "scanport", Short: "扫描端口", diff --git a/net/icmp.go b/net/icmp.go new file mode 100644 index 0000000..67267aa --- /dev/null +++ b/net/icmp.go @@ -0,0 +1,183 @@ +package net + +import ( + "fmt" + "golang.org/x/net/icmp" + "golang.org/x/net/ipv4" + "golang.org/x/net/ipv6" + "net" + "sync" + "sync/atomic" + "time" +) + +const ( + readBufferSize = 1500 + maxSeq = 0xffff +) + +type Pinger struct { + icmpID int + conn4 *icmp.PacketConn + conn6 *icmp.PacketConn + connOnce4 sync.Once + connOnce6 sync.Once + connErr4 error + connErr6 error + sequenceNum4 uint32 + sequenceNum6 uint32 + pending sync.Map + closed chan struct{} +} + +type pendingKey struct { + proto string + seq int +} + +func NewPinger(icmpID int) *Pinger { + return &Pinger{ + icmpID: icmpID & 0xffff, + closed: make(chan struct{}), + } +} + +func (p *Pinger) Close() error { + close(p.closed) + var err error + if p.conn4 != nil { + if e := p.conn4.Close(); e != nil { + err = e + } + } + if p.conn6 != nil { + if e := p.conn6.Close(); e != nil { + err = e + } + } + return err +} + +func (p *Pinger) Ping(ip string, timeout time.Duration) error { + var conn *icmp.PacketConn + var proto string + var sequence *uint32 + dest, err := net.ResolveIPAddr("ip", ip) + if err != nil { + return fmt.Errorf("resolve IP address error: %w", err) + } + if dest.IP.To4() != nil { + // IPv4处理 + p.connOnce4.Do(func() { + p.conn4, p.connErr4 = icmp.ListenPacket("ip4:icmp", "0.0.0.0") + if p.connErr4 == nil { + go p.receiveLoop(p.conn4, "ip4") + } + }) + if p.connErr4 != nil { + return fmt.Errorf("ICMPv4 connection error: %w", p.connErr4) + } + conn = p.conn4 + proto = "ip4" + sequence = &p.sequenceNum4 + } else { + // IPv6处理 + p.connOnce6.Do(func() { + p.conn6, p.connErr6 = icmp.ListenPacket("ip6:ipv6-icmp", "::") + if p.connErr6 == nil { + go p.receiveLoop(p.conn6, "ip6") + } + }) + if p.connErr6 != nil { + return fmt.Errorf("ICMPv6 connection error: %w", p.connErr6) + } + conn = p.conn6 + proto = "ip6" + sequence = &p.sequenceNum6 + } + + seq := int(atomic.AddUint32(sequence, 1) & maxSeq) + key := pendingKey{proto, seq} + resultChan := make(chan struct{}) + p.pending.Store(key, resultChan) + defer p.pending.Delete(key) + + var msgType icmp.Type + if proto == "ip4" { + msgType = ipv4.ICMPTypeEcho + } else { + msgType = ipv6.ICMPTypeEchoRequest + } + + msg := &icmp.Message{ + Type: msgType, + Code: 0, + Body: &icmp.Echo{ + ID: p.icmpID, + Seq: seq, + Data: []byte("HELLO-PING"), + }, + } + + packet, err := msg.Marshal(nil) + if err != nil { + return fmt.Errorf("marshal error: %w", err) + } + + if _, err := conn.WriteTo(packet, dest); err != nil { + return fmt.Errorf("write error: %w", err) + } + + select { + case <-resultChan: + return nil + case <-time.After(timeout): + return fmt.Errorf("timeout") + case <-p.closed: + return fmt.Errorf("pinger closed") + } +} + +func (p *Pinger) receiveLoop(conn *icmp.PacketConn, proto string) { + buffer := make([]byte, readBufferSize) + for { + select { + case <-p.closed: + return + default: + n, _, err := conn.ReadFrom(buffer) + if err != nil { + continue + } + + var expectedType icmp.Type + var protocol int + if proto == "ip4" { + expectedType = ipv4.ICMPTypeEchoReply + protocol = 1 // ICMPv4协议号 + } else { + expectedType = ipv6.ICMPTypeEchoReply + protocol = 58 // ICMPv6协议号 + } + + msg, err := icmp.ParseMessage(protocol, buffer[:n]) + if err != nil { + continue + } + + if msg.Type != expectedType { + continue + } + + echo, ok := msg.Body.(*icmp.Echo) + if !ok || echo.ID != p.icmpID { + continue + } + + key := pendingKey{proto, echo.Seq} + if ch, exists := p.pending.LoadAndDelete(key); exists { + close(ch.(chan struct{})) + } + } + } +} diff --git a/net/monitorip.go b/net/monitorip.go new file mode 100644 index 0000000..5a2e228 --- /dev/null +++ b/net/monitorip.go @@ -0,0 +1,193 @@ +package net + +import ( + "b612.me/apps/b612/netforward" + "b612.me/starlog" + "fmt" + "net" + "sync" + "time" +) + +type Monitor struct { + IPs []string + Port int + ScanType string + Timeout int + Interval int + Log string + Retry int + Threads int + WithHostname bool + pinger *Pinger + status map[string]bool + statusLock sync.RWMutex + stopChan chan struct{} +} + +func NewMonitor(ips []string, scanType string) *Monitor { + return &Monitor{ + IPs: ips, + ScanType: scanType, + status: make(map[string]bool), + stopChan: make(chan struct{}), + Timeout: 1000, + Interval: 1, + Retry: 1, + Threads: 50, + } +} + +func (m *Monitor) Start() error { + if m.Log != "" { + starlog.SetLogFile(m.Log, starlog.Std, true) + } + m.pinger = NewPinger(1127) + // Initialize status + m.statusLock.Lock() + for _, ip := range m.IPs { + m.status[ip] = false // Initial state as down + } + m.statusLock.Unlock() + + ticker := time.NewTicker(time.Duration(m.Interval) * time.Second) + defer ticker.Stop() + m.checkAllIPs() + m.displayStatus() + for { + select { + case <-ticker.C: + m.checkAllIPs() + m.displayStatus() + case <-m.stopChan: + return nil + } + } +} + +func (m *Monitor) Stop() { + close(m.stopChan) +} + +func (m *Monitor) checkAllIPs() { + var wg sync.WaitGroup + sem := make(chan struct{}, m.Threads) + + for _, ip := range m.IPs { + wg.Add(1) + sem <- struct{}{} + + go func(ip string) { + defer func() { + <-sem + wg.Done() + }() + + currentStatus := m.checkIP(ip) + m.updateStatus(ip, currentStatus) + }(ip) + } + wg.Wait() +} + +func (m *Monitor) checkIP(ip string) bool { + for i := 0; i < m.Retry+1; i++ { + var success bool + var err error + + switch m.ScanType { + case "icmp": + err = m.pinger.Ping(ip, time.Duration(m.Timeout)*time.Millisecond) + success = err == nil + case "tcp": + dialer := net.Dialer{ + Timeout: time.Duration(m.Timeout) * time.Millisecond, + Control: netforward.ControlSetReUseAddr, + } + conn, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, m.Port)) + if err == nil { + conn.Close() + success = true + } + } + + if success { + return true + } + + if i < m.Retry { + time.Sleep(time.Duration(m.Timeout) * time.Millisecond / 2) + } + } + return false +} + +func (m *Monitor) updateStatus(ip string, current bool) { + m.statusLock.Lock() + defer m.statusLock.Unlock() + + previous := m.status[ip] + if current != previous { + m.logStatusChange(ip, previous, current) + m.status[ip] = current + } +} + +func (m *Monitor) logStatusChange(ip string, from, to bool) { + statusToStr := func(s bool) string { + if s { + return "UP" + } + return "DOWN" + } + + var hostname string + if m.WithHostname { + names, err := net.LookupAddr(ip) + if err == nil && len(names) > 0 { + hostname = names[0] + } + } + + starlog.Infof("[Status Change] %s (%s): %s → %s\n", + ip, + hostname, + statusToStr(from), + statusToStr(to), + ) +} + +func (m *Monitor) displayStatus() { + fmt.Print("\033[H\033[2J") // Clear screen + fmt.Printf("Monitoring Status (%s)\n", time.Now().Format("2006-01-02 15:04:05")) + fmt.Println("======================================") + + m.statusLock.RLock() + defer m.statusLock.RUnlock() + + for _, ip := range m.IPs { + status := m.status[ip] + statusStr := "DOWN" + if status { + statusStr = "UP" + } + + var hostname string + if m.WithHostname { + names, err := net.LookupAddr(ip) + if err == nil && len(names) > 0 { + hostname = names[0] + } + } + + fmt.Printf("%-15s", ip) + if m.WithHostname { + fmt.Printf(" (%s)", hostname) + } + if statusStr == "UP" { + starlog.Green(": [%s]\n", statusStr) + } else { + starlog.Red(": [%s]\n", statusStr) + } + } +} diff --git a/net/scanip.go b/net/scanip.go index d24edfb..0fd1c0c 100644 --- a/net/scanip.go +++ b/net/scanip.go @@ -4,7 +4,6 @@ import ( "b612.me/apps/b612/netforward" "b612.me/stario" "b612.me/starlog" - "b612.me/starnet" "fmt" "math" "net" @@ -153,6 +152,7 @@ func (s *ScanIP) ICMP() error { } }() idx := 0 + pinger := NewPinger(1127) for { ip := firstIP.String() if ip == lastIP.String() { @@ -166,7 +166,7 @@ func (s *ScanIP) ICMP() error { }() defer wg.Done() for i := 0; i < s.Retry+1; i++ { - _, err := starnet.Ping(ip, idx, time.Duration(s.Timeout)*time.Millisecond) + err := pinger.Ping(ip, time.Duration(s.Timeout)*time.Millisecond) if err == nil { atomic.AddInt32(&count, 1) if s.WithHostname { diff --git a/net/sshjar.go b/net/sshjar.go index d71034f..d483bf9 100644 --- a/net/sshjar.go +++ b/net/sshjar.go @@ -9,6 +9,8 @@ import ( "fmt" "github.com/spf13/cobra" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" + "math/rand" "net" "os" "strings" @@ -23,6 +25,9 @@ var ( curlUrl string serverVersion string curlArg []string + passwds []string + allowAny bool + logPath string ) func init() { @@ -31,6 +36,10 @@ func init() { cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码") cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件") cmdSSHJar.Flags().StringVarP(&serverVersion, "version", "v", "SSH-2.0-OpenSSH_8.0", "SSH版本") + cmdSSHJar.Flags().StringVarP(&curlUrl, "curl", "c", "", "Curl URL") + cmdSSHJar.Flags().StringSliceVarP(&passwds, "allow-passwds", "P", nil, "密码列表,格式:[用户名]:[密码]") + cmdSSHJar.Flags().BoolVarP(&allowAny, "allow-any", "A", false, "允许任意密码登录") + cmdSSHJar.Flags().StringVarP(&logPath, "log", "L", "", "日志文件") } var cmdSSHJar = &cobra.Command{ @@ -38,11 +47,41 @@ var cmdSSHJar = &cobra.Command{ Short: "SSH蜜罐", Long: "SSH蜜罐", Run: func(cmd *cobra.Command, args []string) { - runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, serverVersion) + var mypwds [][]string + for _, v := range passwds { + args := strings.SplitN(v, ":", 2) + if len(args) == 2 { + mypwds = append(mypwds, args) + } + } + runSSHHoneyJar(SSHJar{ + listenAddr: listenAddr, + keyFile: keyFile, + keyPasswd: KeyPasswd, + outpath: outpath, + logpath: logPath, + version: serverVersion, + passwds: mypwds, + allowAny: allowAny, + }) }, } -func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) { +type SSHJar struct { + listenAddr string + keyFile string + keyPasswd string + outpath string + logpath string + version string + passwds [][]string + allowAny bool +} + +func runSSHHoneyJar(jar SSHJar) { + if jar.logpath != "" { + starlog.SetLogFile(jar.logpath, starlog.Std, true) + } var f *os.File var err error if outpath != "" { @@ -56,10 +95,10 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) { defer f.Close() defer conn.Flush() config := &ssh.ServerConfig{ - ServerVersion: version, + ServerVersion: jar.version, // 密码验证回调函数 PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { - starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass)) + starlog.Infof("Login attempt from %s with %s by %s\n", c.RemoteAddr(), c.User(), string(pass)) data := []string{time.Now().Format("2006-01-02 15:04:05"), c.RemoteAddr().String(), c.User(), string(pass)} if f != nil { conn.Write(data) @@ -83,9 +122,30 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) { } }() } + perm := &ssh.Permissions{ + Extensions: map[string]string{ + "user": c.User(), + "passwd": string(pass), + }, + } + if jar.allowAny { + return perm, nil + } + for _, v := range jar.passwds { + if c.User() == v[0] && string(pass) == v[1] { + return perm, nil + } + } return nil, fmt.Errorf("password rejected for %q", c.User()) }, PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + if jar.allowAny { + return &ssh.Permissions{ + Extensions: map[string]string{ + "user": conn.User(), + }, + }, nil + } return nil, fmt.Errorf("public key rejected for %q", conn.User()) }, } @@ -132,9 +192,179 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) { } starlog.Infof("New connection from %s\n", conn.RemoteAddr()) go func(conn net.Conn) { - ssh.NewServerConn(conn, config) - conn.Close() + sConn, chans, reqs, err := ssh.NewServerConn(conn, config) + if err != nil { + starlog.Errorf("SSH handshake failed: %v\n", err) + return + } + defer sConn.Close() + defer starlog.Noticef("Connection from %s closed\n", sConn.RemoteAddr()) + go ssh.DiscardRequests(reqs) + for newChannel := range chans { + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + continue + } + channel, requests, err := newChannel.Accept() + if err != nil { + starlog.Errorf("Failed to accept channel: %v", err) + continue + } + go handleSession(channel, requests, sConn) + } }(conn) } } + +func handleSession(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) { + defer channel.Close() + term := terminal.NewTerminal(channel, "$ ") // 设置 shell 提示符 + term.AutoCompleteCallback = nil // 禁用自动补全 + for req := range requests { + switch req.Type { + case "pty-req": + // 接受伪终端请求(攻击者希望获得交互式体验) + req.Reply(true, nil) + term.SetSize( // 简单设置终端尺寸 + 24, // 行 + 80, // 列 + ) + case "shell": + req.Reply(true, nil) + go func() { + for { + line, err := term.ReadLine() + if err != nil { + break + } + starlog.Infof("[%s %s] Command: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], line) + time.Sleep(time.Millisecond * 200) + term.Write([]byte(FakeCommand(line))) + } + }() + case "exec": + // 处理非交互式命令(如 ssh user@host 'ls -l') + var payload struct{ Command string } + ssh.Unmarshal(req.Payload, &payload) + req.Reply(true, nil) + + // 记录并返回假输出 + starlog.Infof("[%s %s] Exec: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], payload.Command) + term.Write([]byte(FakeCommand(payload.Command))) + channel.Close() + default: + req.Reply(false, nil) + } + } +} + +func FakeCommand(cmd string) string { + // 按命令类型分级模拟 + switch { + //---------------- 系统信息探测类 ---------------- + case strings.Contains(cmd, "uname -a"): + return "Linux core 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64 GNU/Linux\n\n" + + case strings.Contains(cmd, "cat /etc/os-release"): + return `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)" +NAME="Debian GNU/Linux" +VERSION_ID="11" +VERSION="11 (bullseye)" +VERSION_CODENAME=bullseye +ID=debian +HOME_URL="https://www.debian.org/" +SUPPORT_URL="https://www.debian.org/support" +BUG_REPORT_URL="https://bugs.debian.org/" +` + + case strings.Contains(cmd, "free -h"): + return ` total used free shared buff/cache available +Mem: 1022Gi 12Gi 3.0Gi 1.0Gi 47Gi 480Gi +Swap: 0B 0B 0B +` + + //---------------- 敏感文件诱导类 ---------------- + case strings.Contains(cmd, "ls /home"): + return "admin backup devops secret\n" + + case strings.Contains(cmd, "ls /var/log"): + return `auth.log apache2 payment_system.log database_backup.log +` + case strings.Contains(cmd, "ls"): + return "password.txt\n" + + case strings.Contains(cmd, "cat /etc/passwd"): + return `root:x:0:0:root:/root:/bin/bash +admin:x:1000:1000:,,,:/home/admin:/bin/bash +mysql:x:106:113:MySQL Server,,,:/nonexistent:/bin/false +core:x:0:0:,,,:/root:/bin/bash +` + + //---------------- 网络配置诱导类 ---------------- + case strings.Contains(cmd, "ifconfig") || strings.Contains(cmd, "ip addr"): + return `eth0: flags=4163 mtu 1500 + inet 192.168.1.105 netmask 255.255.255.0 broadcast 192.168.1.255 + inet6 fe80::250:56ff:fec0:8888 prefixlen 64 scopeid 0x20 + ether 00:50:56:c0:88:88 txqueuelen 1000 (Ethernet) + RX packets 123456 bytes 123456789 (117.7 MiB) + RX errors 0 dropped 0 overruns 0 frame 0 + TX packets 98765 bytes 9876543 (9.4 MiB) + TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 + +lo: flags=73 mtu 65536 + inet 127.0.0.1 netmask 255.0.0.0 + loop txqueuelen 1000 (Local Loopback) +` + + case strings.Contains(cmd, "netstat -antp"): + return `Active Internet connections (servers and established) +Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name +tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd +tcp 0 0 192.168.1.105:5432 203.0.113.5:43892 ESTABLISHED 5678/postgres +tcp6 0 0 :::8080 :::* LISTEN 91011/java +` + + //---------------- 凭证钓鱼类 ---------------- + case strings.Contains(cmd, "mysql -u root -p"): + return "ERROR 1045 (28000): Access denied\n" + + case strings.Contains(cmd, "sudo -l"): + return `Matching Defaults entries for admin on this host: + env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin + +User admin may run the following commands on honeypot: + (ALL) NOPASSWD: /usr/bin/vim /etc/shadow +` + + //---------------- 进程服务类 ---------------- + case strings.Contains(cmd, "ps aux"): + return `USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +root 1 0.0 0.1 169020 13184 ? Ss May01 0:12 /sbin/init +admin 1234 0.3 2.1 1123456 178912 ? Sl May01 12:34 /opt/payment_system/payment_processor --debug +` + + //---------------- 定制化陷阱 ---------------- + case strings.Contains(cmd, "find / -name *.db"): + return `/var/lib/mysql/transactions.db +/home/backup/internal_users.db +` + + case strings.Contains(cmd, "curl"): + return ` + +404 Not Found + +

Not Found

+

The requested URL was not found on this server.

+ +` + + default: + // 模糊响应增加真实感 + if rand.Intn(100) > 70 { // 30%概率返回"command not found" + return "sh: command not found: " + strings.Fields(cmd)[0] + "\n" + } + return "\n" + } +} diff --git a/net/sshjar_test.go b/net/sshjar_test.go index e0a000b..12daa04 100644 --- a/net/sshjar_test.go +++ b/net/sshjar_test.go @@ -3,5 +3,5 @@ package net import "testing" func TestSSHJar(t *testing.T) { - runSSHHoneyJar("127.0.0.1:22", "", "", "./test.csv", "") + //runSSHHoneyJar("127.0.0.1:22", "", "", "./test.csv", "") } diff --git a/net/trace.go b/net/trace.go index 702ce6f..675997d 100644 --- a/net/trace.go +++ b/net/trace.go @@ -52,6 +52,7 @@ func Traceroute(address string, bindaddr string, dns string, maxHops int, timeou } traceroute(address, bindaddr, maxHops, timeout, ipinfoAddr, hideIncorrect) } + func traceroute(address string, bindaddr string, maxHops int, timeout time.Duration, ipinfoAddr string, hideIncorrect bool) { ipinfo := net.ParseIP(address) if ipinfo == nil { @@ -59,26 +60,46 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat return } - var echoType icmp.Type = ipv4.ICMPTypeEcho - var exceededType icmp.Type = ipv4.ICMPTypeTimeExceeded - var replyType icmp.Type = ipv4.ICMPTypeEchoReply - var proto = 1 - var network = "ip4:icmp" - var resolveIP = "ip4" - if ipinfo.To4() == nil { - network = "ip6:ipv6-icmp" - resolveIP = "ip6" + var ( + echoType icmp.Type + exceededType icmp.Type + replyType icmp.Type + unreachType icmp.Type + proto int + network string + resolveIP string + isIPv4 bool + ) + + if ipinfo.To4() != nil { + echoType = ipv4.ICMPTypeEcho + exceededType = ipv4.ICMPTypeTimeExceeded + replyType = ipv4.ICMPTypeEchoReply + unreachType = ipv4.ICMPTypeDestinationUnreachable + proto = 1 + network = "ip4:icmp" + resolveIP = "ip4" + isIPv4 = true + } else { echoType = ipv6.ICMPTypeEchoRequest exceededType = ipv6.ICMPTypeTimeExceeded replyType = ipv6.ICMPTypeEchoReply + unreachType = ipv6.ICMPTypeDestinationUnreachable proto = 58 + network = "ip6:ipv6-icmp" + resolveIP = "ip6" + isIPv4 = false } if bindaddr == "" { bindaddr = "0.0.0.0" + if !isIPv4 { + bindaddr = "::" + } } + c, err := icmp.ListenPacket(network, bindaddr) if err != nil { - fmt.Println(err) + starlog.Errorln("监听失败:", err) return } defer c.Close() @@ -90,121 +111,179 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat if timeout == 0 { timeout = time.Second * 3 } + exitfor: - for i := 1; i <= maxHops; i++ { - retry := 0 - doRetry: + for ttl := 1; ttl <= maxHops; ttl++ { + if atomic.LoadInt32(&firstTargetHop) <= int32(ttl) { + return + } + dst, err := net.ResolveIPAddr(resolveIP, address) if err != nil { - starlog.Errorln("IP地址解析失败:", address, err) + starlog.Errorln("解析失败:", address, err) return } - if atomic.LoadInt32(&firstTargetHop) <= int32(i) { - return - } - m := icmp.Message{ + + // 构造ICMP报文 + msg := icmp.Message{ Type: echoType, Code: 0, Body: &icmp.Echo{ - ID: i, Seq: i, + ID: ttl, // 使用TTL作为ID + Seq: ttl, // 使用TTL作为序列号 Data: []byte("B612.ME-ROUTER-TRACE"), }, } - b, err := m.Marshal(nil) + msgBytes, err := msg.Marshal(nil) if err != nil { - fmt.Printf("%d\tMarshal error: %v\n", i, err) + starlog.Warningf("%d\t封包失败: %v\n", ttl, err) continue } + // 设置TTL/HopLimit if network == "ip4:icmp" { - if err := c.IPv4PacketConn().SetTTL(i); err != nil { - fmt.Printf("%d\tSetTTL error: %v\n", i, err) + if err := c.IPv4PacketConn().SetTTL(ttl); err != nil { + starlog.Warningf("%d\t设置TTL失败: %v\n", ttl, err) continue } } else { - if err := c.IPv6PacketConn().SetHopLimit(i); err != nil { - fmt.Printf("%d\tSetHopLimit error: %v\n", i, err) + if err := c.IPv6PacketConn().SetHopLimit(ttl); err != nil { + starlog.Warningf("%d\t设置HopLimit失败: %v\n", ttl, err) continue } } - start := time.Now() - n, err := c.WriteTo(b, dst) - if err != nil { - fmt.Printf("%d\tWriteTo error: %v\n", i, err) - continue - } else if n != len(b) { - fmt.Printf("%d\tWrite Short: %v Expected: %v\n", i, n, len(b)) + startTime := time.Now() + if _, err := c.WriteTo(msgBytes, dst); err != nil { + starlog.Warningf("%d\t发送失败: %v\n", ttl, err) continue } - now := time.Now() - exitrecheck: - for { - reply := make([]byte, 1500) - err = c.SetReadDeadline(time.Now().Add(timeout)) - if err != nil { - fmt.Printf("%d\tSetReadDeadline error: %v\n", i, err) - break - } - n, peer, err := c.ReadFrom(reply) - if err != nil { - fmt.Printf("%d\tReadFrom error: %v\n", i, err) - break - } - duration := time.Since(start) + // 接收响应处理 + timeoutCh := time.After(timeout) + responsesReceived := 0 - rm, err := icmp.ParseMessage(proto, reply[:n]) - if err != nil { - fmt.Printf("%d\tParseMessage error: %v\n", i, err) - break - } - - switch rm.Type { - case exceededType: - fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr)) - break exitrecheck - case replyType: - fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr)) - if peer.String() == dst.String() { - break exitfor - } - case ipv4.ICMPTypeEcho, ipv6.ICMPTypeEchoRequest: - if time.Now().Sub(now).Seconds() > timeout.Seconds() { - if retry < 1 { - retry++ - goto doRetry - } - if !hideIncorrect { - fmt.Printf("%d\tInvalid Echo Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr)) - } - break exitrecheck - } - case ipv4.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeDestinationUnreachable: - if time.Now().Sub(now).Seconds() > timeout.Seconds() { - if retry < 1 { - retry++ - goto doRetry - } - if !hideIncorrect { - fmt.Printf("%d\tInvalid DstInv Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr)) - } - break exitrecheck + recvLoop: + for responsesReceived < 3 { + select { + case <-timeoutCh: + if responsesReceived == 0 { + fmt.Printf("%d\t*\n", ttl) } + break recvLoop default: - if time.Now().Sub(now).Seconds() > timeout.Seconds() { - if retry < 1 { - retry++ - goto doRetry + reply := make([]byte, 1500) + if err := c.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { + break recvLoop + } + + n, peer, err := c.ReadFrom(reply) + if err != nil { + if neterr, ok := err.(net.Error); ok && neterr.Timeout() { + continue } - if !hideIncorrect { - fmt.Printf("%d\tgot %+v from %v (%s) %s\n", i, rm.Type, peer, duration, GetIPInfo(peer.String(), ipinfoAddr)) + starlog.Debugf("%d\t接收错误: %v", ttl, err) + continue + } + + // 解析响应 + rm, err := icmp.ParseMessage(proto, reply[:n]) + if err != nil { + starlog.Debugf("%d\t解析错误: %v", ttl, err) + continue + } + + // 验证响应匹配 + if match := checkResponseMatch(rm, ttl, peer.String() == dst.String(), isIPv4, exceededType, replyType, unreachType); match { + duration := time.Since(startTime) + fmt.Printf("%d\t%s\t%s\t%s\n", + ttl, + peer, + duration.Round(time.Millisecond), + GetIPInfo(peer.String(), ipinfoAddr), + ) + responsesReceived++ + + if peer.String() == dst.String() { + atomic.StoreInt32(&firstTargetHop, int32(ttl)) + break exitfor } - break exitrecheck } } } + } +} +func checkResponseMatch(rm *icmp.Message, ttl int, isFinal bool, isIPv4 bool, + exceededType, replyType, unreachType icmp.Type) bool { + + switch { + case rm.Type == exceededType: + if body, ok := rm.Body.(*icmp.TimeExceeded); ok { + return validateOriginalPacket(body.Data, ttl, isIPv4) + } + + case rm.Type == replyType: + if isFinal { + if body, ok := rm.Body.(*icmp.Echo); ok { + return body.ID == ttl && body.Seq == ttl + } + } + return false + + case rm.Type == unreachType: + if body, ok := rm.Body.(*icmp.DstUnreach); ok { + return validateOriginalPacket(body.Data, ttl, isIPv4) + } + } + return false +} + +func validateOriginalPacket(data []byte, ttl int, isIPv4 bool) bool { + var ( + proto byte + header []byte + ) + + if isIPv4 { + if len(data) < 20+8 { + return false + } + ihl := data[0] & 0x0F + if ihl < 5 { + return false + } + proto = data[9] + header = data[:ihl*4] + } else { + if len(data) < 40+8 { + return false + } + proto = data[6] + header = data[:40] + } + + if proto != 1 && proto != 58 { + return false + } + + payload := data[len(header):] + if len(payload) < 8 { + return false + } + + if isIPv4 { + return payload[0] == 8 && // ICMP Echo Request + payload[4] == byte(ttl>>8) && + payload[5] == byte(ttl) && + payload[6] == byte(ttl>>8) && + payload[7] == byte(ttl) + } else { + return payload[0] == 128 && // ICMPv6 Echo Request + payload[4] == byte(ttl>>8) && + payload[5] == byte(ttl) && + payload[6] == byte(ttl>>8) && + payload[7] == byte(ttl) } } @@ -213,16 +292,23 @@ func GetIPInfo(ip string, addr string) string { return "" } uri := strings.ReplaceAll(addr, "{ip}", ip) - res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3))) + res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET", + starnet.WithTimeout(time.Second*2), + starnet.WithDialTimeout(time.Second*3))) + if err != nil { - return "获取IP信息失败:" + err.Error() + return "IP信息获取失败" } + var ipinfo IPInfo - err = res.Body().Unmarshal(&ipinfo) - if err != nil { - return "解析IP信息失败:" + err.Error() + if err := res.Body().Unmarshal(&ipinfo); err != nil { + return "IP信息解析失败" } - return fmt.Sprintf("%s %s %s %s %s", ipinfo.CountryName, ipinfo.RegionName, ipinfo.CityName, ipinfo.OwnerDomain, ipinfo.ISP) + + return fmt.Sprintf("%s %s %s", + ipinfo.CountryName, + ipinfo.RegionName, + ipinfo.ISP) } type IPInfo struct { diff --git a/nmon/cpu.go b/nmon/cpu.go deleted file mode 100644 index 610dfdc..0000000 --- a/nmon/cpu.go +++ /dev/null @@ -1,28 +0,0 @@ -package nmon - -import ( - "fmt" - "github.com/shirou/gopsutil/v4/cpu" - "time" -) - -func Cpu() { - go func() { - flat, err := cpu.Percent(time.Second, false) - if err != nil { - return - } - fmt.Println(flat) - }() - flat, err := cpu.Percent(time.Second, true) - if err != nil { - return - } - c := 0.0000 - for _, v := range flat { - c += v - } - fmt.Println(flat) - fmt.Println(c / float64(len(flat))) - time.Sleep(time.Millisecond * 200) -} diff --git a/nmon/cpu_test.go b/nmon/cpu_test.go deleted file mode 100644 index a3b00e5..0000000 --- a/nmon/cpu_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package nmon - -import "testing" - -func TestCpu(t *testing.T) { - Cpu() -} diff --git a/nmon/mon.go b/nmon/mon.go new file mode 100644 index 0000000..16a2186 --- /dev/null +++ b/nmon/mon.go @@ -0,0 +1,409 @@ +package nmon + +import ( + "fmt" + "os" + "sync" + "time" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/load" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/net" + "github.com/spf13/cobra" +) + +var ( + refreshInterval time.Duration + savePath string + app *tview.Application + showCores bool + noTUI bool + diskIOHistory = make(map[string]disk.IOCountersStat) + netStats = make(map[string]*NetStat) + fileMutex sync.Mutex +) + +type NetStat struct { + LastBytesSent uint64 + LastBytesRecv uint64 + LastTime time.Time + CurrentSentRate float64 + CurrentRecvRate float64 +} + +var Cmd = &cobra.Command{ + Use: "nmon", + Short: "System Monitoring Tool", + Run: func(cmd *cobra.Command, args []string) { + if noTUI { + runTextMode() + return + } + startTUI() + }, +} + +func init() { + Cmd.Flags().BoolVarP(&noTUI, "no-tui", "t", false, "Run in text mode") + Cmd.Flags().BoolVarP(&showCores, "cores", "c", false, "Show per-core CPU usage") + Cmd.Flags().DurationVarP(&refreshInterval, "interval", "i", time.Second, "Refresh interval") + Cmd.Flags().StringVarP(&savePath, "save", "s", "", "Save monitoring data to file (nmon format)") +} + +func main() { + if err := Cmd.Execute(); err != nil { + fmt.Println(err) + } +} + +func createClickableTextView(title string) *tview.TextView { + view := tview.NewTextView().SetDynamicColors(true) + view.SetBorder(true).SetTitle(title) + return view +} + +func startTUI() { + app = tview.NewApplication() + flex := tview.NewFlex().SetDirection(tview.FlexRow) + + cpuView := tview.NewTextView().SetDynamicColors(true) + memView := tview.NewTextView().SetDynamicColors(true) + netView := tview.NewTextView().SetDynamicColors(true) + diskView := tview.NewTextView().SetDynamicColors(true) + + // 动态布局更新函数 + ioStats, _ := disk.IOCounters() + updateLayout := func() { + flex.Clear() + cpuLines := 2 // 固定基础2行(CPU Load + Load Avg) + if showCores { + perCPU, _ := cpu.Percent(0, true) + cpuLines = 2 + len(perCPU) + } + flex.AddItem(cpuView, cpuLines, 1, false) + flex.AddItem(memView, 3, 1, false) + flex.AddItem(diskView, len(ioStats), 1, false) + flex.AddItem(netView, 3, 1, false) + } + + // 初始化数据 + updateLayout() + + app.SetMouseCapture(func(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) { + // 示例:打印点击坐标 + if action == tview.MouseLeftClick { + showCores = !showCores + app.QueueUpdateDraw(updateLayout) + } + return event, action + }) + + go updateLoop(cpuView, memView, netView, diskView) + + app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if event.Key() == tcell.KeyCtrlC { + app.Stop() + } + return event + }) + + if err := app.SetRoot(flex, true).Run(); err != nil { + panic(err) + } +} + +func updateLoop(cpuView, memView, netView, diskView *tview.TextView) { + var lastSave time.Time + for { + app.QueueUpdateDraw(func() { + updateCPU(cpuView) + updateMemory(memView) + updateNetwork(netView) + updateDiskIO(diskView) + }) + + if savePath != "" && time.Since(lastSave) > time.Minute { + go saveNMONData() + lastSave = time.Now() + } + + time.Sleep(refreshInterval) + } +} + +func updateCPU(view *tview.TextView) { + percent, _ := cpu.Percent(time.Second, false) + loadAvg, _ := load.Avg() + + text := fmt.Sprintf("[red]CPU Load: [white]%.2f%%\n[red]Load Avg: [white]%.2f, %.2f, %.2f", + percent[0], + loadAvg.Load1, + loadAvg.Load5, + loadAvg.Load15) + + if showCores { + perCPU, _ := cpu.Percent(time.Second, true) + for i, p := range perCPU { + text += fmt.Sprintf("\nCore %d: [green]%s[white] %.2f%%", + i+1, + progressBar(p, 20), + p) + } + } + view.SetText(text) +} + +func updateMemory(view *tview.TextView) { + memStat, _ := mem.VirtualMemory() + text := fmt.Sprintf("[red]Memory: [white]%.2f%% Used (%.2f GB / %.2f GB)", + memStat.UsedPercent, + float64(memStat.Used)/1024/1024/1024, + float64(memStat.Total)/1024/1024/1024) + view.SetText(text + "\n" + progressBar(memStat.UsedPercent, 50)) +} + +func updateNetwork(view *tview.TextView) { + interfaces, _ := net.IOCounters(true) + now := time.Now() + var totalSent, totalRecv float64 + + for _, iface := range interfaces { + if iface.Name == "lo" { + continue + } + + stat, exists := netStats[iface.Name] + if !exists { + stat = &NetStat{ + LastBytesSent: iface.BytesSent, + LastBytesRecv: iface.BytesRecv, + LastTime: now, + } + netStats[iface.Name] = stat + continue + } + + timeDiff := now.Sub(stat.LastTime).Seconds() + sentDiff := float64(iface.BytesSent - stat.LastBytesSent) + recvDiff := float64(iface.BytesRecv - stat.LastBytesRecv) + + stat.CurrentSentRate = sentDiff / timeDiff + stat.CurrentRecvRate = recvDiff / timeDiff + stat.LastBytesSent = iface.BytesSent + stat.LastBytesRecv = iface.BytesRecv + stat.LastTime = now + + totalSent += stat.CurrentSentRate + totalRecv += stat.CurrentRecvRate + } + + view.SetText(fmt.Sprintf("[red]Network: [white]↑%s ↓%s", + formatSpeed(totalSent), + formatSpeed(totalRecv))) +} + +func updateDiskIO(view *tview.TextView) { + ioStats, _ := disk.IOCounters() + text := "[red]Disk I/O:\n" + + for name, io := range ioStats { + last, exists := diskIOHistory[name] + if exists { + interval := float64(refreshInterval.Seconds()) + readSpeed := float64(io.ReadBytes-last.ReadBytes) / interval + writeSpeed := float64(io.WriteBytes-last.WriteBytes) / interval + + text += fmt.Sprintf(" %-8s R:%s W:%s\n", + name, + formatSpeed(readSpeed), + formatSpeed(writeSpeed)) + } + diskIOHistory[name] = io + } + view.SetText(text) +} + +func progressBar(percent float64, width int) string { + filled := int(percent / 100 * float64(width)) + bar := "[green]" + for i := 0; i < width; i++ { + if i < filled { + bar += "■" + } else { + bar += "□" + } + } + return bar + "[white]" +} + +func formatSpeed(speed float64) string { + const ( + KB = 1024 + MB = KB * 1024 + ) + + switch { + case speed >= MB: + return fmt.Sprintf("%7.3f MB/s", speed/MB) + case speed >= KB: + return fmt.Sprintf("%7.3f KB/s", speed/KB) + default: + return fmt.Sprintf("%7.3f B/s", speed) + } +} + +func saveNMONData() { + fileMutex.Lock() + defer fileMutex.Unlock() + + f, err := os.OpenFile(savePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return + } + defer f.Close() + + now := time.Now().Format("2006-01-02 15:04:05") + cpuPercent, _ := cpu.Percent(0, false) + memStat, _ := mem.VirtualMemory() + ioStats, _ := disk.IOCounters() + + fmt.Fprintf(f, "%s,CPU_ALL,%.2f\n", now, cpuPercent[0]) + fmt.Fprintf(f, "%s,MEM,%.2f,%.2f\n", now, memStat.UsedPercent, memStat.Available) + + for name, io := range ioStats { + fmt.Fprintf(f, "%s,DISKIO,%s,%d,%d\n", + now, name, io.ReadBytes, io.WriteBytes) + } +} + +// 文本模式相关函数 +func runTextMode() { + + ticker := time.NewTicker(refreshInterval) + defer ticker.Stop() + + for { + fmt.Println(getCPUInfo()) + fmt.Println(getMemoryInfo()) + fmt.Println(getNetworkInfo()) + fmt.Println(getDiskIOInfo()) + + if savePath != "" { + go saveNMONData() + } + <-ticker.C + + } +} + +// 数据获取函数 +func getCPUInfo() string { + + percent, _ := cpu.Percent(time.Second, false) + loadAvg, _ := load.Avg() + fmt.Print("\033[H\033[2J") + fmt.Println("=== System Monitor ===") + text := fmt.Sprintf("[CPU]\nLoad: %.2f%% Avg: %.2f, %.2f, %.2f", + percent[0], + loadAvg.Load1, + loadAvg.Load5, + loadAvg.Load15) + + if showCores { + perCPU, _ := cpu.Percent(time.Second, true) + for i, p := range perCPU { + text += fmt.Sprintf("\nCore %d: %s %.2f%%", + i+1, + textProgressBar(p, 20), + p) + } + } + return text +} + +func getMemoryInfo() string { + memStat, _ := mem.VirtualMemory() + return fmt.Sprintf("[Memory]\nUsed: %.2f%% (%.2fG/%.2fG)\n%s", + memStat.UsedPercent, + float64(memStat.Used)/1024/1024/1024, + float64(memStat.Total)/1024/1024/1024, + textProgressBar(memStat.UsedPercent, 50)) +} + +func getNetworkInfo() string { + interfaces, _ := net.IOCounters(true) + now := time.Now() + var sent, recv float64 + + for _, iface := range interfaces { + if iface.Name == "lo" { + continue + } + + stat, exists := netStats[iface.Name] + if !exists { + stat = &NetStat{ + LastBytesSent: iface.BytesSent, + LastBytesRecv: iface.BytesRecv, + LastTime: now, + } + netStats[iface.Name] = stat + continue + } + + timeDiff := now.Sub(stat.LastTime).Seconds() + sentDiff := float64(iface.BytesSent - stat.LastBytesSent) + recvDiff := float64(iface.BytesRecv - stat.LastBytesRecv) + + stat.CurrentSentRate = sentDiff / timeDiff + stat.CurrentRecvRate = recvDiff / timeDiff + stat.LastBytesSent = iface.BytesSent + stat.LastBytesRecv = iface.BytesRecv + stat.LastTime = now + + sent += stat.CurrentSentRate + recv += stat.CurrentRecvRate + } + + return fmt.Sprintf("[Network]\nUpload: %s\nDownload: %s", + formatSpeed(sent), + formatSpeed(recv)) +} + +func getDiskIOInfo() string { + ioStats, _ := disk.IOCounters() + text := "[Disk I/O]" + + for name, io := range ioStats { + last, exists := diskIOHistory[name] + if exists { + interval := float64(refreshInterval.Seconds()) + read := float64(io.ReadBytes-last.ReadBytes) / interval + write := float64(io.WriteBytes-last.WriteBytes) / interval + + text += fmt.Sprintf("\n%-8s Read: %s Write: %s", + name, + formatSpeed(read), + formatSpeed(write)) + } + diskIOHistory[name] = io + } + return text +} + +func textProgressBar(percent float64, width int) string { + filled := int(percent / 100 * float64(width)) + bar := "" + for i := 0; i < width; i++ { + if i < filled { + bar += "■" + } else { + bar += "□" + } + } + return bar +} diff --git a/tcm/tcpmonitor_unix.go b/tcm/tcpmonitor_unix.go index 50ff6ef..a3f00f6 100644 --- a/tcm/tcpmonitor_unix.go +++ b/tcm/tcpmonitor_unix.go @@ -845,7 +845,7 @@ func sendIPv6(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) err return err } addr := syscall.SockaddrInet6{ - Port: dPort, + Port: 0, Addr: [16]byte(dstNetIP.To16()), } return syscall.Sendto(fd, buffer.Bytes(), 0, &addr) diff --git a/tcpkill/tcpkill_unix.go b/tcpkill/tcpkill_unix.go index bbc7371..5c1f7e0 100644 --- a/tcpkill/tcpkill_unix.go +++ b/tcpkill/tcpkill_unix.go @@ -365,7 +365,7 @@ func sendIPv6(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) err return err } addr := syscall.SockaddrInet6{ - Port: dPort, + Port: 0, Addr: [16]byte(dstNetIP.To16()), } return syscall.Sendto(fd, buffer.Bytes(), 0, &addr) diff --git a/tls/cert_test.go b/tls/cert_test.go index 05a6c79..d13885c 100644 --- a/tls/cert_test.go +++ b/tls/cert_test.go @@ -3,5 +3,5 @@ package tls import "testing" func TestCert(t *testing.T) { - showTls("139.199.163.65:443", true, "") + //showTls("139.199.163.65:443", true, "") } diff --git a/version/version.go b/version/version.go index 907f54c..3d8b6d0 100644 --- a/version/version.go +++ b/version/version.go @@ -1,3 +1,3 @@ package version -var Version = "2.1.0.beta.16" +var Version = "2.1.0.beta.17"