From 4fd9c37944330a530e0e55c7f9347f404e0571cd Mon Sep 17 00:00:00 2001 From: starainrt Date: Sun, 15 Sep 2024 15:27:50 +0800 Subject: [PATCH] update --- cert/ca.go | 12 + cert/cert.go | 82 ++++-- cert/cmd.go | 71 +++-- cert/csr.go | 57 ++-- dns/dns.go | 1 + go.mod | 67 +++-- go.sum | 179 ++++++++++-- httpserver/cmd.go | 63 +++++ httpserver/goserver_test.go | 10 + httpserver/server.go | 465 ++++++++++++++----------------- httpserver/template.html | 478 ++++++++++++++++++++++++++++++++ keygen/cmd.go | 7 + main.go | 5 +- mget/cmd.go | 130 +++++++++ mget/process.go | 99 +++++++ mget/range.go | 84 ++++++ mget/range_test.go | 30 ++ mget/redo.go | 138 ++++++++++ mget/util.go | 141 ++++++++++ mget/wget.go | 526 ++++++++++++++++++++++++++++++++++++ mget/wget_test.go | 35 +++ net/cmd.go | 1 + net/natt.go | 167 ++++++++++-- net/sshjar.go | 2 +- net/trace.go | 5 +- netforward/cmd.go | 1 + netforward/forward.go | 53 +++- whois/cmd.go | 267 +++++++++++++++++- 28 files changed, 2767 insertions(+), 409 deletions(-) create mode 100644 httpserver/goserver_test.go create mode 100644 httpserver/template.html create mode 100644 mget/cmd.go create mode 100644 mget/process.go create mode 100644 mget/range.go create mode 100644 mget/range_test.go create mode 100644 mget/redo.go create mode 100644 mget/util.go create mode 100644 mget/wget.go create mode 100644 mget/wget_test.go diff --git a/cert/ca.go b/cert/ca.go index f07eeee..e9af752 100644 --- a/cert/ca.go +++ b/cert/ca.go @@ -50,3 +50,15 @@ func LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Cert } return caKey, cert, nil } + +func LoadPriv(caKeyPath, KeyPwd string) (crypto.PrivateKey, error) { + caKeyBytes, err := os.ReadFile(caKeyPath) + if err != nil { + return nil, err + } + caKey, err := starcrypto.DecodePrivateKey(caKeyBytes, KeyPwd) + if err != nil { + return nil, err + } + return caKey, nil +} diff --git a/cert/cert.go b/cert/cert.go index e757eab..e46ce52 100644 --- a/cert/cert.go +++ b/cert/cert.go @@ -15,6 +15,7 @@ import ( "fmt" "golang.org/x/crypto/ssh" "os" + "reflect" "software.sslmate.com/src/go-pkcs12" "strings" ) @@ -125,7 +126,7 @@ func ParseCert(data []byte, pwd string) { starlog.Green("这是一个DSA私钥\n") starlog.Green("私钥系数:%d\n", n.X) starlog.Green("私钥公钥Y:%d\n", n.Y) - case *ed25519.PrivateKey: + case *ed25519.PrivateKey, ed25519.PrivateKey: starlog.Green("这是一个ED25519私钥\n") case *ecdh.PrivateKey: starlog.Green("这是一个ECDH私钥\n") @@ -208,7 +209,7 @@ func ParseCert(data []byte, pwd string) { starlog.Green("公钥公钥Y:%d\n", n.Y) case *ecdh.PublicKey: starlog.Green("公钥算法为ECDH\n") - case *ed25519.PublicKey: + case *ed25519.PublicKey, ed25519.PublicKey: starlog.Green("公钥算法为ED25519\n") default: starlog.Green("未知公钥类型\n") @@ -257,12 +258,18 @@ func ParseCert(data []byte, pwd string) { starlog.Green("这是一个DSA私钥\n") starlog.Green("私钥系数:%d\n", n.X) starlog.Green("私钥公钥Y:%d\n", n.Y) + case ed25519.PrivateKey: + starlog.Green("这是一个ED25519私钥\n") + sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public()) + starlog.Green("公钥:%s\n", string(sshPub)) case *ed25519.PrivateKey: starlog.Green("这是一个ED25519私钥\n") - case *ecdh.PrivateKey: + sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public()) + starlog.Green("公钥:%s\n", string(sshPub)) + case ecdh.PrivateKey: starlog.Green("这是一个ECDH私钥\n") default: - starlog.Green("未知私钥类型\n") + starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n)) } continue @@ -392,12 +399,18 @@ func ParseCert(data []byte, pwd string) { starlog.Green("这是一个DSA私钥\n") starlog.Green("私钥系数:%d\n", n.X) starlog.Green("私钥公钥Y:%d\n", n.Y) + case ed25519.PrivateKey: + starlog.Green("这是一个ED25519私钥\n") + sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public()) + starlog.Green("公钥:%s\n", string(sshPub)) case *ed25519.PrivateKey: starlog.Green("这是一个ED25519私钥\n") - case *ecdh.PrivateKey: + sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public()) + starlog.Green("公钥:%s\n", string(sshPub)) + case ecdh.PrivateKey: starlog.Green("这是一个ECDH私钥\n") default: - starlog.Green("未知私钥类型\n") + starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n)) } continue case "OPENSSH PUBLIC KEY": @@ -410,7 +423,7 @@ func ParseCert(data []byte, pwd string) { starlog.Green("公钥算法:%s\n", pub.Type()) continue default: - starlog.Infof("未知证书文件类型\n") + starlog.Infof("不支持的证书文件类型:%v\n", reflect.TypeOf(block)) } } } @@ -546,9 +559,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) case *dsa.PrivateKey: starlog.Green("这是一个DSA私钥\n") - case *ed25519.PrivateKey: + case ed25519.PrivateKey, *ed25519.PrivateKey: starlog.Green("这是一个ED25519私钥\n") - case *ecdh.PrivateKey: + case ecdh.PrivateKey, *ecdh.PrivateKey: starlog.Green("这是一个ECDH私钥\n") default: starlog.Green("未知私钥类型\n") @@ -633,9 +646,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) case *dsa.PrivateKey: starlog.Green("这是一个DSA私钥\n") - case *ed25519.PrivateKey: + case ed25519.PrivateKey, *ed25519.PrivateKey: starlog.Green("这是一个ED25519私钥\n") - case *ecdh.PrivateKey: + case ecdh.PrivateKey: starlog.Green("这是一个ECDH私钥\n") default: starlog.Green("未知私钥类型\n") @@ -756,10 +769,10 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { case *dsa.PrivateKey: common = append(common, n) starlog.Green("这是一个DSA私钥\n") - case *ed25519.PrivateKey: + case ed25519.PrivateKey, *ed25519.PrivateKey: common = append(common, n) starlog.Green("这是一个ED25519私钥\n") - case *ecdh.PrivateKey: + case ecdh.PrivateKey: common = append(common, n) starlog.Green("这是一个ECDH私钥\n") default: @@ -777,28 +790,42 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e if err != nil { return err } + fmt.Println(len(keys)) for _, v := range keys { if v == nil { continue } switch n := v.(type) { - case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: - data, err = x509.MarshalPKCS8PrivateKey(n) + case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey, ecdh.PrivateKey: + var key interface{} = n + if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) { + fmt.Println("1") + key = *(n.(*ed25519.PrivateKey)) + } + fmt.Println("2") + data, err = x509.MarshalPKCS8PrivateKey(key) if err != nil { return err } + fmt.Println("3") var block *pem.Block if newPwd != "" { block, err = x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", data, []byte(newPwd), x509.PEMCipherAES256) + if err != nil { + return err + } } else { block = &pem.Block{Type: "PRIVATE KEY", Bytes: data} } + fmt.Println("4") err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(block), 0644) if err != nil { + fmt.Println("5") return err } else { starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8") } + fmt.Println("6") case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey: data, err = x509.MarshalPKIXPublicKey(n) if err != nil { @@ -810,6 +837,8 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e } else { starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8") } + default: + return fmt.Errorf("未知的密钥类型:%v", reflect.TypeOf(n)) } } return nil @@ -865,8 +894,11 @@ func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName strin continue } switch n := v.(type) { - case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: + case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: priv = n + if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) { + priv = *(n.(*ed25519.PrivateKey)) + } break } } @@ -927,7 +959,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error { } else { starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx)) } - case *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: + case *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey: data, err = x509.MarshalPKCS8PrivateKey(n) if err != nil { return err @@ -938,7 +970,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error { } else { starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key") } - case *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey: + case *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey: data, err = x509.MarshalPKIXPublicKey(n) if err != nil { return err @@ -966,11 +998,15 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string) } var block *pem.Block switch n := v.(type) { - case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: - if newPwd != "" { - block, err = ssh.MarshalPrivateKey(n, "") + case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: + var key interface{} = n + if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) { + key = *(n.(*ed25519.PrivateKey)) + } + if newPwd == "" { + block, err = ssh.MarshalPrivateKey(key, "") } else { - block, err = ssh.MarshalPrivateKeyWithPassphrase(n, "", []byte(newPwd)) + block, err = ssh.MarshalPrivateKeyWithPassphrase(key, "", []byte(newPwd)) } if err != nil { return err @@ -981,7 +1017,7 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string) } else { starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".openssh") } - case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey: + case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey: sk, err := ssh.NewPublicKey(n) if err != nil { return err diff --git a/cert/cmd.go b/cert/cmd.go index de60390..84268d3 100644 --- a/cert/cmd.go +++ b/cert/cmd.go @@ -4,6 +4,7 @@ import ( "b612.me/starcrypto" "b612.me/stario" "b612.me/starlog" + "crypto" "crypto/x509" "fmt" "github.com/spf13/cobra" @@ -64,12 +65,6 @@ var CmdCsr = &cobra.Command{ if dnsName == nil { dnsName = stario.MessageBox("请输入dns名称,用逗号分割:", "").MustSliceString(",") } - if startStr == "" { - startStr = stario.MessageBox("请输入开始时间:", "").MustString() - } - if endStr == "" { - endStr = stario.MessageBox("请输入结束时间:", "").MustString() - } } start, err = time.Parse(time.RFC3339, startStr) if err != nil { @@ -81,7 +76,12 @@ var CmdCsr = &cobra.Command{ starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err) os.Exit(1) } - csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName, start, end, isCa, maxPathLenZero, maxPathLen)) + key, err := LoadPriv(caKey, caKeyPwd) + if err != nil { + starlog.Errorln("加载Key错误", err) + os.Exit(1) + } + csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName), key) err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644) if err != nil { starlog.Errorln("保存csr文件错误", err) @@ -112,10 +112,21 @@ var CmdGen = &cobra.Command{ starlog.Errorln("证书公钥不能为空") os.Exit(1) } - caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd) - if err != nil { - starlog.Errorln("加载CA错误", err) - os.Exit(1) + var caKeyRaw crypto.PrivateKey + var caCertRaw *x509.Certificate + var err error + if !isCa { + caKeyRaw, caCertRaw, err = LoadCA(caKey, caCert, caKeyPwd) + if err != nil { + starlog.Errorln("加载CA错误", err) + os.Exit(1) + } + } else { + caKeyRaw, err = LoadPriv(caKey, caKeyPwd) + if err != nil { + starlog.Errorln("加载CA错误", err) + os.Exit(1) + } } csrRaw, err := LoadCsr(csr) if err != nil { @@ -132,7 +143,18 @@ var CmdGen = &cobra.Command{ starlog.Errorln("解析公钥错误", err) os.Exit(1) } - cert, err := MakeCert(caKeyRaw, caCertRaw, csrRaw, pubKeyRaw) + certReq := &x509.Certificate{ + Subject: csrRaw.Subject, + IsCA: isCa, + NotBefore: start, + NotAfter: end, + MaxPathLen: maxPathLen, + MaxPathLenZero: maxPathLenZero, + } + if isCa { + caCertRaw = certReq + } + cert, err := MakeCert(caKeyRaw, caCertRaw, certReq, pubKeyRaw) if err != nil { starlog.Errorln("生成证书错误", err) os.Exit(1) @@ -171,19 +193,21 @@ var CmdParse = &cobra.Command{ func init() { Cmd.AddCommand(CmdCsr) CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式") - CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家") - CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份") - CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市") + CmdCsr.Flags().StringVarP(&country, "country", "c", "CN", "国家") + CmdCsr.Flags().StringVarP(&province, "province", "p", "B612", "省份") + CmdCsr.Flags().StringVarP(&city, "city", "t", "B612", "城市") CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织") CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位") - CmdCsr.Flags().StringVarP(&name, "name", "n", "", "通用名称") + CmdCsr.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称") CmdCsr.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称") - CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00") - CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00") CmdCsr.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹") - CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA") - CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0") - CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度") + CmdCsr.Flags().StringVarP(&caKey, "secret-key", "k", "", "加密私钥") + CmdCsr.Flags().StringVarP(&caKeyPwd, "secret-key-passwd", "K", "", "加密私钥的密码") + //CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA") + //CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00") + //CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00") + //CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0") + //CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度") CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥") CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书") @@ -191,6 +215,11 @@ func init() { CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥") CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹") CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码") + CmdGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA") + CmdGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00") + CmdGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00") + CmdGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0") + CmdGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度") Cmd.AddCommand(CmdGen) CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码") diff --git a/cert/csr.go b/cert/csr.go index 90c3a6c..0d41d1e 100644 --- a/cert/csr.go +++ b/cert/csr.go @@ -1,17 +1,16 @@ package cert import ( + "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" - "math/big" "net" "os" - "time" ) -func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string, start, end time.Time, isCa bool, maxPathLenZero bool, maxPathLen int) *x509.Certificate { +func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string) *x509.CertificateRequest { var trueDNS []string var trueIp []net.IP for _, v := range dnsName { @@ -22,15 +21,17 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s } trueIp = append(trueIp, ip) } - ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment - eku := x509.ExtKeyUsageServerAuth - if isCa { - ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature - eku = x509.ExtKeyUsageAny - } - return &x509.Certificate{ - Version: 3, - SerialNumber: big.NewInt(time.Now().Unix()), + /* + ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment + eku := x509.ExtKeyUsageServerAuth + if isCa { + ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature + eku = x509.ExtKeyUsageAny + } + */ + return &x509.CertificateRequest{ + Version: 3, + //SerialNumber: big.NewInt(time.Now().Unix()), Subject: pkix.Name{ Country: s2s(country), Province: s2s(province), @@ -39,23 +40,27 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s OrganizationalUnit: s2s(orgUnit), CommonName: name, }, - DNSNames: trueDNS, - IPAddresses: trueIp, - NotBefore: start, - NotAfter: end, - BasicConstraintsValid: true, - IsCA: isCa, - MaxPathLen: maxPathLen, - MaxPathLenZero: maxPathLenZero, - KeyUsage: ku, - ExtKeyUsage: []x509.ExtKeyUsage{eku}, + DNSNames: trueDNS, + IPAddresses: trueIp, + //NotBefore: start, + //NotAfter: end, + //BasicConstraintsValid: true, + //IsCA: isCa, + //MaxPathLen: maxPathLen, + //MaxPathLenZero: maxPathLenZero, + //KeyUsage: ku, + //ExtKeyUsage: []x509.ExtKeyUsage{eku}, } } -func outputCsr(csr *x509.Certificate) []byte { +func outputCsr(csr *x509.CertificateRequest, priv interface{}) []byte { + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csr, priv) + if err != nil { + return nil + } return pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", - Bytes: csr.Raw, + Bytes: csrBytes, }) } @@ -66,7 +71,7 @@ func s2s(str string) []string { return []string{str} } -func LoadCsr(csrPath string) (*x509.Certificate, error) { +func LoadCsr(csrPath string) (*x509.CertificateRequest, error) { csrBytes, err := os.ReadFile(csrPath) if err != nil { return nil, err @@ -75,7 +80,7 @@ func LoadCsr(csrPath string) (*x509.Certificate, error) { if block == nil || block.Type != "CERTIFICATE REQUEST" { return nil, errors.New("Failed to decode PEM block containing the certificate") } - cert, err := x509.ParseCertificate(block.Bytes) + cert, err := x509.ParseCertificateRequest(block.Bytes) if err != nil { return nil, err } diff --git a/dns/dns.go b/dns/dns.go index 4899978..392d237 100644 --- a/dns/dns.go +++ b/dns/dns.go @@ -151,6 +151,7 @@ func (c *DoHClient) Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time // No need to use hreq.URL.Query() hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil) + hreq.Header.Set("User-Agent", "B612 DoH Client") hreq.Header.Add("Accept", DoHMediaType) resp, err := c.cli.Do(hreq) if err != nil { diff --git a/go.mod b/go.mod index f5b23e3..a07de55 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,21 @@ module b612.me/apps/b612 -go 1.21 +go 1.21.2 -toolchain go1.21.2 +toolchain go1.22.4 require ( - b612.me/notify v1.2.5 + b612.me/notify v1.2.6 + b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 b612.me/starcrypto v0.0.5 - b612.me/stario v0.0.9 - b612.me/starlog v1.3.3 - b612.me/starnet v0.1.8 - b612.me/staros v1.1.7 + b612.me/stario v0.0.10 + b612.me/starlog v1.3.4 + b612.me/starmap v1.2.4 + b612.me/starnet v0.2.1 + b612.me/staros v1.1.8 b612.me/starssh v0.0.2 b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd - b612.me/wincmd v0.0.3 + b612.me/wincmd v0.0.4 github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/emersion/go-smtp v0.20.2 @@ -21,32 +23,61 @@ require ( github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 + github.com/huin/goupnp v1.3.0 github.com/inconshreveable/mousetrap v1.1.0 - github.com/likexian/whois v1.15.1 github.com/miekg/dns v1.1.58 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/spf13/cobra v1.8.0 github.com/things-go/go-socks5 v0.0.5 - golang.org/x/crypto v0.21.0 - golang.org/x/net v0.21.0 + github.com/vbauerster/mpb/v8 v8.8.3 + golang.org/x/crypto v0.26.0 + golang.org/x/net v0.28.0 + golang.org/x/sys v0.24.0 software.sslmate.com/src/go-pkcs12 v0.4.0 - ) require ( - b612.me/starmap v1.2.4 // indirect b612.me/win32api v0.0.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect + github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/cloudflare/cloudflare-go v0.86.0 // indirect + github.com/cpu/goacmedns v0.1.1 // indirect github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect github.com/go-jose/go-jose/v4 v4.0.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/jlaffaye/ftp v0.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + 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/mattn/go-runewidth v0.0.16 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect github.com/pkg/sftp v1.13.4 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect golang.org/x/image v0.6.0 // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/sys v0.18.0 // indirect - golang.org/x/term v0.18.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/tools v0.17.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.5.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 8bf2fa7..fae4fd6 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,66 @@ -b612.me/notify v1.2.5 h1:fASpzi8YAo78g6jKnefzfbsQz0nGNYFbClB2Bylj+MA= b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI= +b612.me/notify v1.2.6 h1:fY+0ccP6cJCDvnfRilmPlDK+J8xTYBpYNwf7jaC2IIE= +b612.me/notify v1.2.6/go.mod h1:awcFq3bvbkf3hdviUtOW16Io0IEJXkNPgno7IRe7B9g= +b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 h1:+eXeVqkoi4s9sNoY9eGt4ieSbr1+Deos8fC8wMfOCNI= +b612.me/sdk/whois v0.0.0-20240816133027-129514a15991/go.mod h1:PB9QpUoQEip0MB3st8H5hmnDTcDsR0RGV0BfpUr5XDg= b612.me/starcrypto v0.0.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0= b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4= b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0= -b612.me/stario v0.0.9 h1:bFDlejUJMwZ12a09snZJspQsOlkqpDAl9qKPEYOGWCk= b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw= -b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI= -b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE= +b612.me/stario v0.0.10 h1:+cIyiDCBCjUfodMJDp4FLs+2E1jo7YENkN+sMEe6550= +b612.me/stario v0.0.10/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk= +b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84= +b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk= b612.me/starmap v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E= b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc= -b612.me/starnet v0.1.8 h1:sTNytUFP38i2BFR9nha3lTSLYb7El3tvKpZplYCrhZk= b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y= -b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g= -b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40= +b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw= +b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI= +b612.me/staros v1.1.8 h1:5Bpuf9q2nH75S2ekmieJuH3Y8LTqg/voxXCOiMAC3kk= +b612.me/staros v1.1.8/go.mod h1:4KmokjKXFW5h1hbA4aIv5O+2FptVzBubCo7IPirfqm8= b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY= b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk= b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA= b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8= b612.me/win32api v0.0.2 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA= b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0= -b612.me/wincmd v0.0.3 h1:GYrkYnNun39yfNcA2+u0h4VW/BYbTrJK39QW4W1LCYA= -b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo= +b612.me/wincmd v0.0.4 h1:fv9p1V8mw2HdUjaoZBWZy0T41JftueyLxAuch1MgtdI= +b612.me/wincmd v0.0.4/go.mod h1:o3yPoE+DpVPHGKl/q1WT1C8OaIVwHEnpeNgMFqzlwD8= +github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY= +github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= +github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass= +github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI= +github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw= +github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= +github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= @@ -34,36 +69,88 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1X github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4= github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= 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= github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8= github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss= github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E= github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE= +github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= +github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= +github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE= github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM= -github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU= -github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +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/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +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/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4= github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +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= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= @@ -71,55 +158,86 @@ github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyh github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ= github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8= github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ= +github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ= +github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -127,17 +245,32 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k= diff --git a/httpserver/cmd.go b/httpserver/cmd.go index 53e6414..88b413b 100644 --- a/httpserver/cmd.go +++ b/httpserver/cmd.go @@ -5,9 +5,12 @@ import ( "b612.me/staros" "context" "encoding/json" + "fmt" "github.com/spf13/cobra" "os" "os/signal" + "regexp" + "strconv" "strings" ) @@ -15,6 +18,7 @@ var s HttpServer var daemon bool var hooks string +var speedlimit string func init() { Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify") @@ -34,8 +38,54 @@ func init() { Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址") Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址") Cmd.Flags().BoolVarP(&s.httpDebug, "debug", "D", false, "开启调试模式") + Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/") + Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password") + Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致") + Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速,如1M,意思是1MB/s") + Cmd.Flags().Bool("daeapplied", false, "") + Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址") + Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址") + Cmd.Flags().MarkHidden("daeapplied") + +} +func parseSpeedString(speedString string) (uint64, error) { + // 定义单位及其对应的字节值 + unitMultipliers := map[string]int{ + "b": 1, "": 1, + "k": 1024, "kb": 1024, "kib": 1024, + "m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024, + "g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024, + "t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024, + } + + // 正则表达式匹配速度的格式 + re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`) + matches := re.FindStringSubmatch(strings.ToLower(speedString)) + if matches == nil { + return 0, fmt.Errorf("invalid speed string format") + } + + // 解析数值部分 + value, err := strconv.ParseFloat(matches[1], 64) + if err != nil { + return 0, fmt.Errorf("invalid numeric value") + } + + // 获取单位部分 + unit := matches[2] + if unit == "" { + unit = "b" + } + + // 根据单位计算最终的字节每秒值 + multiplier, ok := unitMultipliers[unit] + if !ok { + return 0, fmt.Errorf("invalid unit in speed string") + } + + return uint64(value * float64(multiplier)), nil } var Cmd = &cobra.Command{ @@ -43,6 +93,19 @@ var Cmd = &cobra.Command{ Short: "HTTP文件服务器(HTTP File Browser Server)", Long: `HTTP文件服务器(HTTP File Browser Server)`, Run: func(cmd *cobra.Command, args []string) { + if s.logpath != "" && starlog.GetWriter() == nil { + starlog.SetLogFile(s.logpath, starlog.Std, true) + } + if speedlimit != "" { + speed, err := parseSpeedString(speedlimit) + if err != nil { + starlog.Criticalln("Speed Limit Error:", err) + os.Exit(1) + } + s.speedlimit = speed + starlog.Infoln("Speed Limit:(user in):\t", speedlimit) + starlog.Infoln("Speed Limit (bytes/s):\t", speed) + } if hooks != "" { if !staros.Exists(hooks) { starlog.Criticalln("hook file not exists") diff --git a/httpserver/goserver_test.go b/httpserver/goserver_test.go new file mode 100644 index 0000000..eb77908 --- /dev/null +++ b/httpserver/goserver_test.go @@ -0,0 +1,10 @@ +package httpserver + +import ( + "net/http" + "testing" +) + +func TestHttpServer(t *testing.T) { + http.ListenAndServe(":89", http.FileServer(http.Dir(`./`))) +} diff --git a/httpserver/server.go b/httpserver/server.go index 4e7d79e..a773e84 100644 --- a/httpserver/server.go +++ b/httpserver/server.go @@ -8,8 +8,10 @@ import ( "context" _ "embed" "encoding/base64" + "encoding/json" "errors" "fmt" + "html/template" "io" "io/ioutil" "math" @@ -22,7 +24,7 @@ import ( "time" ) -var version = "2.1.0" +var version = "2.1.0.b11" type HttpServerCfgs func(cfg *HttpServerCfg) @@ -45,6 +47,14 @@ type HttpServerCfg struct { ctx context.Context hooks []ServerHook httpDebug bool + noListPath []string + listPwd map[string]string + listSameForFile bool + // speed limit means xx bytes/s + speedlimit uint64 + + background string + mobildBackground string } type ServerHook struct { @@ -67,219 +77,8 @@ var jquery []byte //go:embed upload.html var uploadPage []byte -var htmlTitle string = ` - - - - B612 Http Server %s - - - -
-

B612 Http Server - %s

-

%s

%s - - - - - - - - - - ` - -var htmlTail = ` -
NameModifiedSizeType
-
-
-	

B612.Me © Apache 2.0 License

-
-
- - - -` +//go:embed template.html +var templateHtml []byte func WithHooks(hooks []ServerHook) HttpServerCfgs { return func(cfg *HttpServerCfg) { @@ -333,7 +132,7 @@ func (h *HttpServer) Run(ctx context.Context) error { server.Shutdown(ctx) } }() - if h.logpath != "" { + if h.logpath != "" && starlog.GetWriter() == nil { starlog.SetLogFile(h.logpath, starlog.Std, true) } netcards, err := net.Interfaces() @@ -401,7 +200,14 @@ func (h *HttpServer) Page403(w http.ResponseWriter) { return } } - w.Write([]byte(`B612 Http Server

403 Forbidden


`)) + w.Write([]byte(` + + 403 Forbidden + +

403 Forbidden

+
B612 HTTP SERVER
+ + `)) } func (h *HttpServer) Page401(w http.ResponseWriter) { @@ -575,26 +381,25 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) { } func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) { - var rangeStart, rangeEnd int64 - rangeStart, rangeEnd = -1, -1 - for k, v := range r.Header { - if strings.ToLower(k) == "range" { - if strings.Contains(v[0], "bytes=") { - v[0] = strings.Replace(v[0], "bytes=", "", -1) - } else { - continue - } - data := strings.Split(v[0], "-") - if len(data) == 0 { - break - } - rangeStart, _ = strconv.ParseInt(data[0], 10, 64) - if len(data) > 1 { - rangeEnd, _ = strconv.ParseInt(data[1], 10, 64) - } - //w.WriteHeader(206) //206 支持断点续传 - break - } + var rangeStart, rangeEnd int64 = -1, -1 + ranges := r.Header.Get("Range") + if ranges == "" { + return rangeStart, rangeEnd + } + if !strings.Contains(ranges, "bytes=") { + return rangeStart, rangeEnd + } + ranges = strings.TrimPrefix(ranges, "bytes=") + data := strings.Split(ranges, "-") + if len(data) == 0 { + return rangeStart, rangeEnd + } + rangeStart, _ = strconv.ParseInt(data[0], 10, 64) + if len(data) > 1 { + rangeEnd, _ = strconv.ParseInt(data[1], 10, 64) + } + if rangeEnd == 0 { + rangeEnd = -1 } return rangeStart, rangeEnd } @@ -630,16 +435,19 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat if _, ok := h.willHook(fullpath); ok { return nil } - w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10)) start, end := h.CalcRange(r) if start != -1 { if end == -1 { + w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10)) w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10)) //w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10)) } else { + w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10)) w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10)) //w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10)) } + } else { + w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10)) } } } @@ -666,11 +474,57 @@ func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) { func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error { if staros.IsFolder(fullpath) { + if len(h.listPwd) != 0 { + for k, v := range h.listPwd { + if strings.HasPrefix(r.URL.Path, k) { + if r.URL.Query().Get("list") == v { + return h.getFolder(log, w, r, fullpath) + } + } + } + } + if len(h.noListPath) != 0 { + for _, v := range h.noListPath { + if strings.HasPrefix(r.URL.Path, v) { + h.Page403(w) + return nil + } + } + } return h.getFolder(log, w, r, fullpath) } + if !h.listSameForFile { + return h.getFile(log, w, r, fullpath) + } + + if len(h.listPwd) != 0 { + for k, v := range h.listPwd { + if strings.HasPrefix(r.URL.Path, k) { + if r.URL.Query().Get("list") == v { + return h.getFile(log, w, r, fullpath) + } + } + } + } + if len(h.noListPath) != 0 { + for _, v := range h.noListPath { + if strings.HasPrefix(r.URL.Path, v) { + h.Page403(w) + return nil + } + } + } return h.getFile(log, w, r, fullpath) } +type FileData struct { + Attr string `json:"attr"` + Name string `json:"name"` + Modified string `json:"modified"` + Size int64 `json:"size"` + Type string `json:"type"` +} + func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error { dir, err := ioutil.ReadDir(fullpath) if err != nil { @@ -688,14 +542,24 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r if h.uploadFolder != "" { upload = `Upload Web Page Is Openned!` } - w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, "Index of "+r.URL.Path, upload))) + attr := r.URL.Query().Get("list") + if attr != "" { + attr = "/?list=" + attr + } + var fdatas = make([]FileData, 0, len(dir)+1) + if r.URL.Path != "/" { p := r.URL.Path if p[len(p)-1:] != "/" { p += "/" } - w.Write([]byte(fmt.Sprintf(`%s%s%s%s`, - p+"..", "../", "-", "-", "上层文件夹"))) + fdatas = append(fdatas, FileData{ + Attr: p + ".." + attr, + Name: "..", + Modified: "-", + Size: -1, + Type: "上层文件夹", + }) } if r.URL.Path == "/" { r.URL.Path = "" @@ -706,23 +570,104 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r for _, v := range dir { if v.Name() != "." || v.Name() != ".." { if !v.IsDir() { - w.Write([]byte(fmt.Sprintf(`%s%s%s%s`, - r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name())))) + fdatas = append(fdatas, FileData{ + Name: v.Name(), + Attr: r.URL.Path + "/" + v.Name(), + Modified: v.ModTime().Format("2006-01-02 15:04:05"), + Size: v.Size(), + Type: h.FileType(v.Name()), + }) } else { - w.Write([]byte(fmt.Sprintf(`%s%s%s%s`, - r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹"))) + fdatas = append(fdatas, FileData{ + Name: v.Name() + "/", + Attr: r.URL.Path + "/" + v.Name() + attr, + Modified: v.ModTime().Format("2006-01-02 15:04:05"), + Size: -1, + Type: "文件夹", + }) } } } - w.Write([]byte(htmlTail)) + tmpt, err := template.New("index").Parse(string(templateHtml)) + if err != nil { + log.Errorf("Parse Template Error:%v\n", err) + w.WriteHeader(502) + w.Write([]byte(`B612 Http Server

502 SERVER ERROR


`)) + return err + } + jData, err := json.Marshal(fdatas) + if err != nil { + log.Errorf("Json Marshal Failed:%v\n", err) + w.WriteHeader(502) + w.Write([]byte(`B612 Http Server

502 SERVER ERROR


`)) + return err + } + if r.URL.Path == "" { + r.URL.Path = "/" + } + var bk, mbk string + if h.background != "" { + bk = `background: url('` + h.background + `') no-repeat center center fixed;` + } + if h.mobildBackground != "" { + mbk = `background: url('` + h.mobildBackground + `') no-repeat center center fixed;` + } + if h.mobildBackground == "" && h.background != "" { + mbk = bk + } + err = tmpt.Execute(w, map[string]interface{}{ + "IdxTitle": r.URL.Path, + "Version": version, + "Idx": "Index of " + r.URL.Path, + "Upload": template.HTML(upload), + "Data": template.JS(jData), + "Photo": template.CSS(bk), + "MobilePhoto": template.CSS(mbk), + }) + if err != nil { + log.Errorf("Template Execute Failed:%v\n", err) + w.WriteHeader(502) + w.Write([]byte(`B612 Http Server

502 SERVER ERROR


`)) + return err + } return nil } +func (h *HttpServer) getSleepTime() time.Duration { + if h.speedlimit == 0 { + return 0 + } + return time.Nanosecond * time.Duration(16384*1000*1000*1000/h.speedlimit) / 2 + +} + func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error { if !staros.Exists(fullpath) { h.Page404(w) return errors.New("File Not Found! 404 ERROR") } + var lastCount int64 + var lastDate time.Time = time.Now() + var currentCount int64 + speedControl := func(count int) { + if h.speedlimit == 0 { + return + } + currentCount += int64(count) + for { + if time.Since(lastDate) < time.Second { + if uint64(currentCount-lastCount) > h.speedlimit { + time.Sleep(h.getSleepTime()) + } else { + break + } + } else { + lastDate = time.Now() + lastCount = currentCount + break + } + } + } //starlog.Debugln(r.Header) startRange, endRange := h.CalcRange(r) fp, err := os.Open(fullpath) @@ -756,10 +701,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * if !needCurl { w.WriteHeader(200) for { - buf := make([]byte, 1048576) + buf := make([]byte, 16384) n, err := fp.Read(buf) if n != 0 { - ns, err := w.Write(buf[0:n]) + speedControl(n) + ns, err := w.Write(buf[:n]) transferData += ns if err != nil { log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) @@ -770,7 +716,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * if err == io.EOF { break } - log.Errorln("Read File %s Failed:%v\n", fullpath, err) + log.Errorf("Read File %s Failed:%v\n", fullpath, err) return err } } @@ -784,13 +730,14 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * return err } b64 := base64.StdEncoding.EncodeToString(data) - req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{ - "data": b64, - "ip": r.RemoteAddr, - }), + req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url, "POST", + starnet.WithBytes(starnet.BuildPostForm(map[string]string{ + "data": b64, + "ip": r.RemoteAddr, + })), starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond))) - if err != nil || len(req.RecvData) == 0 { + if err != nil { w.Header().Set("Content-Length", strconv.Itoa(len(data))) w.WriteHeader(200) ns, err := w.Write(data) @@ -801,9 +748,10 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * } return nil } + recvData := req.Body().Bytes() w.WriteHeader(200) - w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData))) - ns, err := w.Write(req.RecvData) + w.Header().Set("Content-Length", strconv.Itoa(len(recvData))) + ns, err := w.Write(recvData) transferData += ns if err != nil { log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) @@ -816,7 +764,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * fp.Seek(int64(startRange), 0) count := startRange for { - buf := make([]byte, 1048576) + buf := make([]byte, 16384) n, err := fp.Read(buf) if err != nil { if err == io.EOF { @@ -825,8 +773,9 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err) return err } + speedControl(n) if endRange == -1 { - ns, err := w.Write(buf[0:n]) + ns, err := w.Write(buf[:n]) transferData += ns if err != nil { log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err) @@ -841,11 +790,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r * writeNum = int(endRange - count + 1) } ns, err := w.Write(buf[0:writeNum]) - transferData += ns if err != nil { log.Errorln("Transfer Error:", err) return err } + transferData += ns count += int64(n) } } diff --git a/httpserver/template.html b/httpserver/template.html new file mode 100644 index 0000000..f93be50 --- /dev/null +++ b/httpserver/template.html @@ -0,0 +1,478 @@ + + + + + B612 Http Server {{ .IdxTitle }} + + + +
+
+

B612 Http Server - {{ .Version }}

+
+

{{ .Idx }}

+ {{ .Upload }} +
+ +
+
+ + + + + + + + + + + +
NameModifiedSizeType
+
+
+

B612.Me © Apache 2.0 License

+
+ + + + + + + diff --git a/keygen/cmd.go b/keygen/cmd.go index 503fd66..aca9ed8 100644 --- a/keygen/cmd.go +++ b/keygen/cmd.go @@ -5,6 +5,7 @@ import ( "b612.me/starlog" "b612.me/staros" "crypto/ecdsa" + "crypto/ed25519" "crypto/rsa" "github.com/spf13/cobra" "os" @@ -138,6 +139,12 @@ var CmdPub = &cobra.Command{ case *ecdsa.PrivateKey: starlog.Infoln("found ecdsa private key") pub = n.Public() + case ed25519.PrivateKey: + starlog.Infoln("found ed25519 private key") + pub = n.Public() + case *ed25519.PrivateKey: + starlog.Infoln("found ed25519 private key") + pub = n.Public() default: starlog.Errorln("unknown private key type") os.Exit(1) diff --git a/main.go b/main.go index 79b9ca2..c984379 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,7 @@ import ( "b612.me/apps/b612/image" "b612.me/apps/b612/keygen" "b612.me/apps/b612/merge" + "b612.me/apps/b612/mget" "b612.me/apps/b612/net" "b612.me/apps/b612/rmt" "b612.me/apps/b612/search" @@ -41,7 +42,7 @@ import ( var cmdRoot = &cobra.Command{ Use: "b612", - Version: "2.1.0.beta.10", + Version: "2.1.0.beta.11", } func init() { @@ -50,7 +51,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) + cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd) } func main() { diff --git a/mget/cmd.go b/mget/cmd.go new file mode 100644 index 0000000..6e2288c --- /dev/null +++ b/mget/cmd.go @@ -0,0 +1,130 @@ +package mget + +import ( + "b612.me/stario" + "b612.me/starlog" + "fmt" + "github.com/spf13/cobra" + "os" + "os/signal" + "regexp" + "strconv" + "strings" + "time" +) + +var mg Mget + +var Cmd = &cobra.Command{ + Use: "mget", + Short: "多线程下载工具", + Long: `多线程下载工具`, + Run: Run, +} + +var headers []string +var ua string +var proxy string +var skipVerify bool +var speedcontrol string + +func init() { + Cmd.Flags().StringVarP(&mg.Tareget, "output", "o", "", "输出文件名") + Cmd.Flags().IntVarP(&mg.BufferSize, "buffer", "b", 8192, "缓冲区大小") + Cmd.Flags().IntVarP(&mg.Thread, "thread", "t", 8, "线程数") + Cmd.Flags().IntVarP(&mg.RedoRPO, "safe", "s", 1048576, "安全校验点") + Cmd.Flags().StringSliceVarP(&headers, "header", "H", []string{}, "自定义请求头,格式: key=value") + Cmd.Flags().StringVarP(&proxy, "proxy", "p", "", "代理地址") + Cmd.Flags().StringVarP(&ua, "user-agent", "U", "", "自定义User-Agent") + Cmd.Flags().BoolVarP(&skipVerify, "skip-verify", "k", false, "跳过SSL验证") + Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速,如1M,意思是1MB/s") +} + +func parseSpeedString(speedString string) (uint64, error) { + // 定义单位及其对应的字节值 + unitMultipliers := map[string]int{ + "b": 1, "": 1, + "k": 1024, "kb": 1024, "kib": 1024, + "m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024, + "g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024, + "t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024, + } + + // 正则表达式匹配速度的格式 + re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`) + matches := re.FindStringSubmatch(strings.ToLower(speedString)) + if matches == nil { + return 0, fmt.Errorf("invalid speed string format") + } + + // 解析数值部分 + value, err := strconv.ParseFloat(matches[1], 64) + if err != nil { + return 0, fmt.Errorf("invalid numeric value") + } + + // 获取单位部分 + unit := matches[2] + if unit == "" { + unit = "b" + } + + // 根据单位计算最终的字节每秒值 + multiplier, ok := unitMultipliers[unit] + if !ok { + return 0, fmt.Errorf("invalid unit in speed string") + } + + return uint64(value * float64(multiplier)), nil +} + +func Run(cmd *cobra.Command, args []string) { + if args == nil || len(args) == 0 { + starlog.Errorln("缺少URL参数") + os.Exit(1) + } + if speedcontrol != "" { + speed, err := parseSpeedString(speedcontrol) + if err != nil { + starlog.Criticalln("Speed Limit Error:", err) + os.Exit(1) + } + mg.speedlimit = int64(speed) + fmt.Printf("Max Speed Limit:(user in):\t%v\n", speedcontrol) + fmt.Printf("Max Speed Limit (bytes/s):\t%v bytes/sec\n", speed) + } + for _, v := range headers { + kv := strings.SplitN(v, "=", 2) + if len(kv) != 2 { + continue + } + mg.Setting.AddHeader(strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])) + } + if ua != "" { + mg.Setting.SetUserAgent(ua) + } + if proxy != "" { + mg.Setting.SetProxy(proxy) + } + if skipVerify { + mg.Setting.SetSkipTLSVerify(true) + } + mg.OriginUri = args[0] + sig := make(chan os.Signal) + signal.Notify(sig, os.Interrupt) + select { + case err := <-stario.WaitUntilFinished(mg.Run): + if err != nil { + starlog.Errorln(err) + os.Exit(2) + } + time.Sleep(time.Second) + return + case <-sig: + starlog.Infoln("User Interrupted") + mg.fn() + time.Sleep(time.Second) + mg.Redo.Save() + os.Exit(3) + } +} diff --git a/mget/process.go b/mget/process.go new file mode 100644 index 0000000..0e19c4c --- /dev/null +++ b/mget/process.go @@ -0,0 +1,99 @@ +package mget + +import ( + "fmt" + "github.com/vbauerster/mpb/v8" + "github.com/vbauerster/mpb/v8/decor" + "io" + "strings" + "time" +) + +func (m *Mget) processMiddleware(base mpb.BarFiller) mpb.BarFiller { + fn := func(w io.Writer, st decor.Statistics) error { + var res string + count := 0 + _, err := fmt.Fprintf(w, "\nFinished:%s Total Write:%d Speed:%v\n\n", m.Redo.FormatPercent(), m.Redo.Total(), m.Redo.FormatSpeed("MB")) + for k := range m.threads { + v := m.threads[len(m.threads)-1-k] + if v != nil { + count++ + res = fmt.Sprintf("Thread %v: %s %s\t", len(m.threads)-k, v.FormatSpeed("MB"), v.FormatPercent()) + res + if count%3 == 0 { + res = strings.TrimRight(res, "\t") + fmt.Fprintf(w, "%s\n", res) + res = "" + } + } + } + if res != "" { + res = strings.TrimRight(res, "\t") + fmt.Fprintf(w, "%s\n", res) + } + return err + } + if base == nil { + return mpb.BarFillerFunc(fn) + } + return mpb.BarFillerFunc(func(w io.Writer, st decor.Statistics) error { + err := fn(w, st) + if err != nil { + return err + } + return base.Fill(w, st) + }) +} + +func (w *Mget) Process() { + w.processEnable = true + defer func() { + w.processEnable = false + }() + fmt.Println() + p := mpb.New() + var filler mpb.BarFiller + filler = w.processMiddleware(filler) + bar := p.New(int64(w.ContentLength), + mpb.BarStyle().Rbound("|"), + mpb.BarExtender(filler, true), // all bars share same extender filler + mpb.PrependDecorators( + decor.Counters(decor.SizeB1024(0), "% .2f / % .2f"), + ), + mpb.AppendDecorators( + decor.EwmaETA(decor.ET_STYLE_GO, 30), + decor.Name(" ] "), + decor.EwmaSpeed(decor.SizeB1024(0), "% .2f ", 30), + ), + ) + defer p.Wait() + for { + last := w.Redo.Total() + lastTime := time.Now() + bar.SetCurrent(int64(w.Redo.Total())) + select { + case <-w.ctx.Done(): + bar.SetCurrent(int64(w.Redo.Total())) + if w.dynLength { + bar.SetTotal(int64(w.Redo.ContentLength), true) + } + bar.Abort(false) + return + case <-time.After(time.Second): + if !w.writeEnable { + bar.SetCurrent(int64(w.Redo.Total())) + if w.dynLength { + bar.SetTotal(int64(w.Redo.ContentLength), true) + } + bar.Abort(true) + return + } + now := w.Redo.Total() + bar.EwmaIncrInt64(int64(now-last), time.Since(lastTime)) + lastTime = time.Now() + last = now + if w.dynLength { + bar.SetTotal(int64(w.Redo.ContentLength), false) + } + } + } +} diff --git a/mget/range.go b/mget/range.go new file mode 100644 index 0000000..98f09f8 --- /dev/null +++ b/mget/range.go @@ -0,0 +1,84 @@ +package mget + +import "sort" + +type Range struct { + Min uint64 `json:"min"` + Max uint64 `json:"max"` +} +type SortRange []Range + +func (s SortRange) Len() int { return len(s) } +func (s SortRange) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s SortRange) Less(i, j int) bool { return s[i].Min < s[j].Min } + +func uniformRange(rg []Range) ([]Range, error) { + newRg := make([]Range, 0, len(rg)) + sort.Sort(SortRange(rg)) + var last *Range = nil + for _, v := range rg { + if last != nil && v.Min <= last.Max+1 { + if last.Max <= v.Max { + last.Max = v.Max + } + continue + } + newRg = append(newRg, v) + last = &newRg[len(newRg)-1] + } + return newRg, nil +} + +func singleSubRange(origin []Range, v Range) []Range { + newRg := make([]Range, 0) + sort.Sort(SortRange(origin)) + for i := 0; i < len(origin); i++ { + ori := origin[i] + res := make([]Range, 0) + shouldAdd := true + for j := 0; j < 1; j++ { + if v.Min <= ori.Min && v.Max >= ori.Max { + shouldAdd = false + break + } + if v.Max < ori.Min { + continue + } + if v.Min > ori.Max { + break + } + ur1 := Range{ + Min: ori.Min, + Max: v.Min - 1, + } + if v.Min == 0 { + ur1.Min = 1 + ur1.Max = 0 + } + ur2 := Range{ + Min: v.Max + 1, + Max: ori.Max, + } + if ur1.Max >= ur1.Min { + res = append(res, ur1) + } + if ur2.Max >= ur2.Min { + res = append(res, ur2) + } + } + if len(res) == 0 && shouldAdd { + res = append(res, ori) + } + newRg = append(newRg, res...) + } + return newRg +} + +func subRange(origin, rg []Range) []Range { + sort.Sort(SortRange(rg)) + sort.Sort(SortRange(origin)) + for _, v := range rg { + origin = singleSubRange(origin, v) + } + return origin +} diff --git a/mget/range_test.go b/mget/range_test.go new file mode 100644 index 0000000..81448b6 --- /dev/null +++ b/mget/range_test.go @@ -0,0 +1,30 @@ +package mget + +import ( + "fmt" + "reflect" + "testing" +) + +func TestRangePlus(t *testing.T) { + var r = Redo{ + ContentLength: 100, + rangeUpdated: true, + Range: []Range{ + {10, 12}, + {13, 20}, + {17, 19}, + {30, 80}, + {90, 97}, + }, + } + err := r.reform() + if err != nil { + t.Error(err) + } + if !reflect.DeepEqual(r.Range, []Range{{10, 20}, {30, 80}, {90, 97}}) { + t.Error("reform error") + } + fmt.Println(r.Range) + fmt.Println(r.ReverseRange()) +} diff --git a/mget/redo.go b/mget/redo.go new file mode 100644 index 0000000..dc60c92 --- /dev/null +++ b/mget/redo.go @@ -0,0 +1,138 @@ +package mget + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "sync" + "time" +) + +type Redo struct { + Is206 bool `json:"is_206"` + OriginUri string `json:"origin_uri"` + Date time.Time `json:"date"` + Filename string `json:"filename"` + ContentLength uint64 `json:"content_length"` + Range []Range `json:"range"` + rangeUpdated bool + lastUpdate time.Time + lastTotal uint64 + speed float64 + total uint64 + isRedo bool + sync.RWMutex +} + +func (r *Redo) CacheTotal() uint64 { + return r.total +} + +func (r *Redo) Total() uint64 { + var total uint64 + for { + r.RLock() + for _, v := range r.Range { + total += v.Max - v.Min + 1 + } + r.total = total + r.RUnlock() + if r.total > r.ContentLength && r.ContentLength > 0 { + r.reform() + continue + } + break + } + return total +} + +func (r *Redo) Update(start, end int) error { + if start < 0 || end < 0 || start > end { + return fmt.Errorf("invalid range: %d-%d", start, end) + } + r.Lock() + defer r.Unlock() + r.rangeUpdated = true + r.Range = append(r.Range, Range{uint64(start), uint64(end)}) + now := time.Now() + if now.Sub(r.lastUpdate) >= time.Millisecond*500 { + var total uint64 + for _, v := range r.Range { + total += v.Max - v.Min + 1 + } + r.total = total + r.speed = float64(total-r.lastTotal) / (float64(now.Sub(r.lastUpdate).Milliseconds()) / 1000.00) + r.lastTotal = total + r.lastUpdate = now + } + return nil +} + +func (r *Redo) Percent() float64 { + return float64(r.Total()) / float64(r.ContentLength) +} + +func (r *Redo) FormatPercent() string { + return fmt.Sprintf("%.2f%%", r.Percent()*100) +} + +func (r *Redo) FormatSpeed(unit string) string { + switch strings.ToLower(unit) { + case "kb": + return fmt.Sprintf("%.2f KB/s", r.speed/1024) + case "mb": + return fmt.Sprintf("%.2f MB/s", r.speed/1024/1024) + case "gb": + return fmt.Sprintf("%.2f GB/s", r.speed/1024/1024/1024) + default: + return fmt.Sprintf("%.2f B/s", r.speed) + } +} + +func (r *Redo) Speed() float64 { + return r.speed +} + +func (r *Redo) Save() error { + var err error + err = r.reform() + if err != nil { + return err + } + if r.Filename != "" { + data, err := json.Marshal(r) + if err != nil { + return err + } + r.Lock() + defer r.Unlock() + return os.WriteFile(r.Filename+".bgrd", data, 0644) + } + return nil +} + +func (r *Redo) reform() error { + r.Lock() + defer r.Unlock() + if !r.rangeUpdated { + return nil + } + tmp, err := r.uniformRange(r.Range) + if err != nil { + return err + } + r.Range = tmp + return nil +} + +func (r *Redo) uniformRange(rg []Range) ([]Range, error) { + return uniformRange(rg) +} + +func (r *Redo) ReverseRange() ([]Range, error) { + r.reform() + r.RLock() + defer r.RUnlock() + return r.uniformRange(subRange([]Range{{0, r.ContentLength - 1}}, r.Range)) +} diff --git a/mget/util.go b/mget/util.go new file mode 100644 index 0000000..dd378bf --- /dev/null +++ b/mget/util.go @@ -0,0 +1,141 @@ +package mget + +import ( + "b612.me/staros" + "context" + "fmt" + "io" + "net/http" + "os" + "path" + "regexp" + "runtime" + "sync/atomic" + "time" +) + +func parseContentRange(contentRange string) (start, end, total int64, err error) { + _, err = fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &total) + return +} + +func GetFileName(resp *http.Response) string { + fname := getFileName(resp) + var idx = 0 + for { + idx++ + if staros.Exists(fname) { + if staros.Exists(fname + ".bgrd") { + return fname + } + fname = fmt.Sprintf("%s.%d", fname, idx) + } else { + break + } + } + return fname +} + +func getFileName(resp *http.Response) string { + // 尝试从Content-Disposition头中提取文件名 + contentDisposition := resp.Header.Get("Content-Disposition") + if contentDisposition != "" { + // 使用正则表达式提取文件名 + re := regexp.MustCompile(`(?i)^attachment; filename="?(?P[^;"]+)`) + matches := re.FindStringSubmatch(contentDisposition) + if len(matches) > 1 { + // 提取命名的捕获组 + for i, name := range re.SubexpNames() { + if name == "filename" { + return matches[i] + } + } + } + } + // 提取路径中的最后一个元素作为文件名 + return path.Base(resp.Request.URL.Path) +} + +func IOWriter(stopCtx context.Context, ch chan Buffer, state *uint32, di *downloadinfo, reader io.ReadCloser, bufSize int, start *int64, end *int64) error { + defer reader.Close() + for { + buf := make([]byte, bufSize) + select { + case <-stopCtx.Done(): + return nil + default: + if atomic.LoadUint32(state) == 1 { + runtime.Gosched() + time.Sleep(time.Millisecond) + continue + } + n, err := reader.Read(buf) + if n > 0 { + ch <- Buffer{Data: buf[:n], Start: uint64(*start)} + *start += int64(n) + di.AddCurrent(int64(n)) + } + if *start >= *end { + return nil + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } + } +} + +func createFileWithSize(filename string, size int64) (*os.File, error) { + file, err := os.Create(filename) + if err != nil { + return nil, err + } + if size == 0 { + return file, nil + } + // 调整文件指针到指定大小位置 + if _, err = file.Seek(size-1, 0); err != nil { + return nil, err + } + + // 写入一个空字节,以确保文件达到所需大小 + if _, err = file.Write([]byte{0}); err != nil { + return nil, err + } + + return file, nil +} + +func CloneHeader(original http.Header) http.Header { + newHeader := make(http.Header) + for key, values := range original { + copiedValues := make([]string, len(values)) + copy(copiedValues, values) + newHeader[key] = copiedValues + } + return newHeader +} + +func CloneCookies(original []*http.Cookie) []*http.Cookie { + cloned := make([]*http.Cookie, len(original)) + for i, cookie := range original { + cloned[i] = &http.Cookie{ + Name: cookie.Name, + Value: cookie.Value, + Path: cookie.Path, + Domain: cookie.Domain, + Expires: cookie.Expires, + RawExpires: cookie.RawExpires, + MaxAge: cookie.MaxAge, + Secure: cookie.Secure, + HttpOnly: cookie.HttpOnly, + SameSite: cookie.SameSite, + Raw: cookie.Raw, + Unparsed: append([]string(nil), cookie.Unparsed...), + } + } + return cloned +} diff --git a/mget/wget.go b/mget/wget.go new file mode 100644 index 0000000..3f12c12 --- /dev/null +++ b/mget/wget.go @@ -0,0 +1,526 @@ +package mget + +import ( + "b612.me/stario" + "b612.me/starnet" + "b612.me/staros" + "context" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" +) + +type Mget struct { + Setting starnet.Request + Redo + //本地文件地址 + Tareget string + //本地文件大小 + TargetSize int64 + //redo文件最大丢数据量 + RedoRPO int + //单个buffer大小 + BufferSize int + //并发下载线程数 + dynLength bool + Thread int `json:"thread"` + tf *os.File + ch chan Buffer + ctx context.Context + fn context.CancelFunc + wg sync.WaitGroup + threads []*downloader + lastUndoInfo []Range + writeError error + writeEnable bool + processEnable bool + speedlimit int64 +} + +type Buffer struct { + Data []byte + Start uint64 +} + +func (w *Mget) Clone() *starnet.Request { + req := starnet.NewSimpleRequest(w.Setting.Uri(), w.Setting.Method()) + req.SetHeaders(CloneHeader(w.Setting.Headers())) + req.SetCookies(CloneCookies(w.Setting.Cookies())) + req.SetSkipTLSVerify(w.Setting.SkipTLSVerify()) + req.SetProxy(w.Setting.Proxy()) + return req +} + +func (w *Mget) IsUrl206() (*starnet.Response, bool, error) { + req := w.Clone() + req.SetHeader("Range", "bytes=0-") + res, err := req.Do() + if err != nil { + return nil, false, err + } + if res.StatusCode == 206 { + return res, true, nil + } + return res, false, nil +} + +func (w *Mget) prepareRun(res *starnet.Response, is206 bool) error { + var err error + + length := res.Header.Get("Content-Length") + if length == "" { + length = "0" + w.dynLength = true + is206 = false + } + w.TargetSize, err = strconv.ParseInt(length, 10, 64) + if err != nil { + return fmt.Errorf("parse content length error: %w", err) + } + if w.Tareget == "" { + w.Tareget = GetFileName(res.Response) + } + fmt.Println("Will write to:", w.Tareget) + fmt.Println("Size:", w.TargetSize) + fmt.Println("Is206:", is206) + w.Redo = Redo{ + Filename: w.Tareget, + ContentLength: uint64(w.TargetSize), + OriginUri: w.Setting.Uri(), + Date: time.Now(), + Is206: is206, + } + fmt.Println("Threads:", w.Thread) + if staros.Exists(w.Tareget + ".bgrd") { + fmt.Println("Found redo file, try to recover...") + var redo Redo + data, err := os.ReadFile(w.Tareget + ".bgrd") + if err != nil { + return fmt.Errorf("read redo file error: %w", err) + } + err = json.Unmarshal(data, &redo) + if err != nil { + return fmt.Errorf("unmarshal redo file error: %w", err) + } + redo.reform() + if redo.ContentLength != w.Redo.ContentLength { + fmt.Println("Content length not match, redo file may be invalid, ignore it") + return nil + } + if redo.OriginUri != w.Redo.OriginUri { + fmt.Println("Origin uri not match, redo file may be invalid, ignore it") + return nil + } + w.Redo = redo + w.Redo.isRedo = true + w.lastUndoInfo, err = w.Redo.ReverseRange() + if err != nil { + return fmt.Errorf("reverse redo range error: %w", err) + } + fmt.Println("Recover redo file success,process:", w.Redo.FormatPercent()) + } + return nil +} + +func (w *Mget) Run() error { + var err error + var res *starnet.Response + var is206 bool + w.ctx, w.fn = context.WithCancel(context.Background()) + w.ch = make(chan Buffer) + defer w.fn() + w.threads = make([]*downloader, w.Thread) + if w.Setting.Uri() == "" { + w.Setting = *starnet.NewSimpleRequest(w.OriginUri, "GET") + } + for { + res, is206, err = w.IsUrl206() + if err != nil { + return fmt.Errorf("check 206 error: %w", err) + } + err = w.prepareRun(res, is206) + if err != nil { + return fmt.Errorf("prepare run error: %w", err) + } + if res.StatusCode != 206 && res.StatusCode != 200 { + return fmt.Errorf("Server return %d", res.StatusCode) + } + if !is206 { + var di = &downloader{ + alive: true, + downloadinfo: &downloadinfo{ + Start: 0, + End: w.TargetSize - 1, + Size: w.TargetSize, + }, + } + w.threads[0] = di + state := uint32(0) + err = IOWriter(w.ctx, w.ch, &state, di.downloadinfo, res.Body().Reader(), w.BufferSize, &di.Start, &di.End) + di.alive = false + if err == nil { + return nil + } + continue + } else { + res.Body().Close() + } + break + } + go func() { + w.writeEnable = true + w.writeError = w.WriteServer() + w.writeEnable = false + }() + if w.TargetSize == 0 { + return nil + } + for i := 0; i < w.Thread; i++ { + w.wg.Add(1) + go w.dispatch(i) + } + go w.Process() + w.wg.Wait() + time.Sleep(2 * time.Microsecond) + for { + if w.writeEnable { + w.fn() + time.Sleep(time.Millisecond * 50) + continue + } + if w.writeError != nil { + err = w.Redo.Save() + return fmt.Errorf("write error: %w %v", w.writeError, err) + } + break + } + w.fn() + stario.WaitUntilTimeout(time.Second*2, + func(c chan struct{}) error { + for { + if w.processEnable { + time.Sleep(time.Millisecond * 50) + continue + } + return nil + } + }) + + r, err := w.ReverseRange() + if err != nil { + return err + } + if len(r) == 0 { + return os.Remove(w.Tareget + ".bgrd") + } + return w.Redo.Save() +} + +func (w *Mget) dispatch(idx int) error { + defer w.wg.Done() + var start, end int64 + if len(w.lastUndoInfo) == 0 { + count := w.TargetSize / int64(w.Thread) + start = count * int64(idx) + end = count*int64(idx+1) - 1 + if idx == w.Thread-1 { + end = w.TargetSize - 1 + } + } else { + w.Lock() + if len(w.lastUndoInfo) == 0 { + d := &downloader{} + w.threads[idx] = d + w.Unlock() + goto morejob + } + start = int64(w.lastUndoInfo[0].Min) + end = int64(w.lastUndoInfo[0].Max) + w.lastUndoInfo = w.lastUndoInfo[1:] + w.Unlock() + } + for { + req := w.Clone() + req.SetCookies(CloneCookies(w.Setting.Cookies())) + d := &downloader{ + Request: req, + ch: w.ch, + ctx: w.ctx, + bufferSize: w.BufferSize, + downloadinfo: &downloadinfo{ + Start: start, + End: end, + }, + } + w.threads[idx] = d + if err := d.Run(); err != nil { + fmt.Printf("thread %d error: %v\n", idx, err) + if d.Start >= d.End { + break + } + start = d.Start + end = d.End + continue + } + break + } +morejob: + for { + w.Lock() + if len(w.lastUndoInfo) > 0 { + w.threads[idx].Start = int64(w.lastUndoInfo[idx].Min) + w.threads[idx].End = int64(w.lastUndoInfo[idx].Max) + w.lastUndoInfo = w.lastUndoInfo[1:] + w.Unlock() + } else { + w.Unlock() + if !w.RequestNewTask(w.threads[idx]) { + break + } + } + for { + req := w.Clone() + req.SetCookies(CloneCookies(w.Setting.Cookies())) + d := &downloader{ + Request: req, + ch: w.ch, + ctx: w.ctx, + bufferSize: w.BufferSize, + downloadinfo: &downloadinfo{ + Start: w.threads[idx].Start, + End: w.threads[idx].End, + }, + } + w.threads[idx] = d + if err := d.Run(); err != nil { + fmt.Printf("thread %d error: %v\n", idx, err) + if d.Start >= d.End { + break + } + start = d.Start + end = d.End + continue + } + break + } + } + return nil +} + +func (w *Mget) getSleepTime() time.Duration { + if w.speedlimit == 0 { + return 0 + } + return time.Nanosecond * time.Duration(16384*1000*1000*1000/w.speedlimit) / 2 + +} +func (w *Mget) WriteServer() error { + var err error + defer w.fn() + if !w.isRedo { + w.tf, err = createFileWithSize(w.Tareget, w.TargetSize) + } else { + w.tf, err = os.OpenFile(w.Tareget, os.O_RDWR, 0666) + } + if err != nil { + return err + } + + lastUpdateRange := 0 + currentRange := 0 + + currentCount := int64(0) + lastDate := time.Now() + lastCount := int64(0) + speedControl := func(count int) { + if w.speedlimit == 0 { + return + } + currentCount += int64(count) + for { + if time.Since(lastDate) < time.Second { + if currentCount-lastCount > w.speedlimit { + time.Sleep(w.getSleepTime()) + } else { + break + } + } else { + lastDate = time.Now() + lastCount = currentCount + break + } + } + } + for { + select { + case <-w.ctx.Done(): + return nil + case b := <-w.ch: + n, err := w.tf.WriteAt(b.Data, int64(b.Start)) + if err != nil { + fmt.Println("write error:", err) + return err + } + speedControl(n) + if w.dynLength { + w.ContentLength += uint64(n) + } + currentRange += n + end := b.Start + uint64(n) - 1 + err = w.Update(int(b.Start), int(end)) + if err != nil { + return err + } + if currentRange-lastUpdateRange >= w.RedoRPO { + w.tf.Sync() + go w.Redo.Save() + lastUpdateRange = currentRange + } + } + } +} + +type downloader struct { + *starnet.Request + alive bool + ch chan Buffer + ctx context.Context + state uint32 + bufferSize int + *downloadinfo +} + +func (d *downloader) Run() error { + d.alive = true + defer func() { + d.alive = false + }() + d.SetHeader("Range", fmt.Sprintf("bytes=%d-%d", d.Start, d.End)) + res, err := d.Do() + if err != nil { + return err + } + if res.Header.Get("Content-Range") == "" { + return fmt.Errorf("server not support range") + } + start, end, _, err := parseContentRange(res.Header.Get("Content-Range")) + if d.Start != start { + return fmt.Errorf("server not support range") + } + d.End = end + d.downloadinfo = &downloadinfo{ + Start: d.Start, + End: d.End, + Size: d.End - d.Start + 1, + } + reader := res.Body().Reader() + return IOWriter(d.ctx, d.ch, &d.state, d.downloadinfo, reader, d.bufferSize, &d.Start, &d.End) +} + +func (w *Mget) RequestNewTask(task *downloader) bool { + //stop thhe world first + w.Lock() + defer w.Unlock() + defer func() { + for _, v := range w.threads { + if v != nil { + atomic.StoreUint32(&v.state, 0) + } + } + }() + var maxThread *downloader + for _, v := range w.threads { + if v != nil { + atomic.StoreUint32(&v.state, 1) + } + } + time.Sleep(time.Microsecond * 2) + + for _, v := range w.threads { + if v == nil { + continue + } + if maxThread == nil { + maxThread = v + continue + } + if v.End-v.Start > maxThread.End-maxThread.Start { + maxThread = v + } + } + if maxThread == nil || maxThread.End <= maxThread.Start { + return false + } + if (maxThread.End-maxThread.Start)/2 < int64(w.BufferSize*2) || (maxThread.End-maxThread.Start)/2 < 100*1024 { + return false + } + task.End = maxThread.End + maxThread.End = maxThread.Start + (maxThread.End-maxThread.Start)/2 + task.Start = maxThread.End + 1 + //fmt.Printf("thread got new task %d-%d\n", task.Start, task.End) + return true +} + +type downloadinfo struct { + Start int64 + End int64 + Size int64 + current int64 + lastCurrent int64 + lastTime time.Time + speed float64 +} + +func (d *downloadinfo) Current() int64 { + return d.current +} + +func (d *downloadinfo) Percent() float64 { + return float64(d.current) / float64(d.Size) +} + +func (d *downloadinfo) FormatPercent() string { + return fmt.Sprintf("%.2f%%", d.Percent()*100) +} + +func (d *downloadinfo) SetCurrent(info int64) { + d.current = info + now := time.Now() + if now.Sub(d.lastTime) >= time.Millisecond*500 { + d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00) + d.lastCurrent = d.current + d.lastTime = time.Now() + } +} + +func (d *downloadinfo) AddCurrent(info int64) { + d.current += info + now := time.Now() + if now.Sub(d.lastTime) >= time.Millisecond*500 { + d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00) + d.lastCurrent = d.current + d.lastTime = time.Now() + } +} + +func (d *downloadinfo) FormatSpeed(unit string) string { + switch strings.ToLower(unit) { + case "kb": + return fmt.Sprintf("%.2f KB/s", d.speed/1024) + case "mb": + return fmt.Sprintf("%.2f MB/s", d.speed/1024/1024) + case "gb": + return fmt.Sprintf("%.2f GB/s", d.speed/1024/1024/1024) + default: + return fmt.Sprintf("%.2f B/s", d.speed) + } +} + +func (d *downloadinfo) Speed() float64 { + return d.speed +} diff --git a/mget/wget_test.go b/mget/wget_test.go new file mode 100644 index 0000000..b8b5a6d --- /dev/null +++ b/mget/wget_test.go @@ -0,0 +1,35 @@ +package mget + +import ( + "b612.me/starnet" + "fmt" + "testing" +) + +func TestWget(t *testing.T) { + r := starnet.NewSimpleRequest("http://192.168.2.33:88/DJI_0746.MP4", "GET") + w := Mget{ + Setting: *r, + RedoRPO: 1048576, + BufferSize: 8192, + Thread: 8, + } + if err := w.Run(); err != nil { + t.Fatal(err) + } +} + +func TestSM(t *testing.T) { + a := map[string]string{ + "1": "1", + "2": "2", + } + modify(a) + fmt.Println(a) +} + +func modify(a map[string]string) { + b := make(map[string]string) + b = a + b["1"] = "3" +} diff --git a/net/cmd.go b/net/cmd.go index 2bd5b30..60356cd 100644 --- a/net/cmd.go +++ b/net/cmd.go @@ -81,6 +81,7 @@ func init() { CmdNatThrough.Flags().BoolVarP(&natt.AutoUPnP, "auto-upnp", "u", true, "自动UPnP") CmdNatThrough.Flags().IntVarP(&natt.WebPort, "web-port", "w", 8080, "web端口") CmdNatThrough.Flags().IntVarP(&natt.HealthCheckInterval, "health-check-interval", "H", 30, "健康检查间隔") + CmdNatThrough.Flags().StringVarP(&natt.Type, "type", "t", "tcp", "穿透协议,tcp或udp") Cmd.AddCommand(CmdNatThrough) CmdScanIP.Flags().StringVarP(&scanip.Host, "ip", "i", "", "扫描IP地址") diff --git a/net/natt.go b/net/natt.go index f9a99ec..303f1ca 100644 --- a/net/natt.go +++ b/net/natt.go @@ -3,6 +3,7 @@ package net import ( "b612.me/apps/b612/netforward" "b612.me/starlog" + "b612.me/starmap" "context" "encoding/binary" "encoding/json" @@ -27,6 +28,7 @@ type NatThroughs struct { STUN string Remote string HealthCheckInterval int + Type string } func (n *NatThroughs) Close() { @@ -48,19 +50,23 @@ func (n *NatThroughs) Parse(reqs []string) error { if n.STUN == "" { n.STUN = "turn.b612.me:3478" } + if n.Type == "" { + n.Type = "tcp" + } for _, v := range reqs { var req = NatThrough{ Forward: netforward.NetForward{ LocalAddr: "0.0.0.0", DialTimeout: time.Second * 5, - UDPTimeout: time.Second * 20, + UDPTimeout: time.Second * 30, KeepAlivePeriod: n.KeepAlivePeriod, KeepAliveIdel: n.KeepAliveIdel, KeepAliveCount: n.KeepAliveCount, UsingKeepAlive: true, EnableTCP: true, + EnableUDP: true, }, - Type: "tcp", + Type: n.Type, STUN: n.STUN, Remote: n.Remote, KeepAlivePeriod: n.KeepAlivePeriod, @@ -72,7 +78,6 @@ func (n *NatThroughs) Parse(reqs []string) error { strs := strings.Split(v, ",") switch len(strs) { case 1: - req.Type = "tcp" req.Forward.RemoteURI = strs[0] case 2: ipport := strings.Split(strs[0], ":") @@ -90,7 +95,6 @@ func (n *NatThroughs) Parse(reqs []string) error { } req.Forward.LocalPort = port } - req.Type = "tcp" req.Forward.RemoteURI = strs[1] case 3: ipport := strings.Split(strs[1], ":") @@ -108,7 +112,6 @@ func (n *NatThroughs) Parse(reqs []string) error { } req.Forward.LocalPort = port } - req.Type = "tcp" req.Forward.RemoteURI = strs[2] req.Name = strs[0] case 4: @@ -144,7 +147,7 @@ func (n *NatThroughs) Run() error { go func(v *NatThrough) { defer wg.Done() if err := v.Run(); err != nil { - starlog.Errorf("Failed to run forward: %v\n", err) + starlog.Errorf("Failed to run natThrough: %v\n", err) } v.HealthCheck() }(v) @@ -262,6 +265,7 @@ func (c *NatThrough) Run() error { c.Forward.EnableTCP = false c.Forward.EnableUDP = true } + starlog.Infof("NatThrough Type: %s\n", c.Type) starlog.Infof("Local Port: %d\n", c.Forward.LocalPort) starlog.Infof("Keepalive To: %s\n", c.Remote) starlog.Infof("Forward To: %s\n", c.Forward.RemoteURI) @@ -294,7 +298,7 @@ func (c *NatThrough) Run() error { } go func() { if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil { - starlog.Errorf("Failed to run forward: %v\n", err) + starlog.Errorf("Failed to run keepalive: %v\n", err) c.Forward.Close() c.stopFn() } @@ -319,6 +323,26 @@ func (c *NatThrough) Run() error { } func (c *NatThrough) HealthCheck() { + getIP := func(ip net.Addr) string { + switch ip.(type) { + case *net.TCPAddr: + return ip.(*net.TCPAddr).IP.String() + case *net.UDPAddr: + return ip.(*net.UDPAddr).IP.String() + default: + return "" + } + } + getPort := func(ip net.Addr) int { + switch ip.(type) { + case *net.TCPAddr: + return ip.(*net.TCPAddr).Port + case *net.UDPAddr: + return ip.(*net.UDPAddr).Port + default: + return 0 + } + } count := 0 if c.HealthCheckInterval == 0 { c.HealthCheckInterval = 30 @@ -330,16 +354,30 @@ func (c *NatThrough) HealthCheck() { case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)): } if c.Type == "udp" { - continue - } - conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2) - if err != nil { - starlog.Warningf("Health Check Fail: %v\n", err) - count++ + _, extIp, err := c.UdpKeppaliveSTUN(c.Forward.UdpListener(), c.STUN) + if err != nil { + count++ + starlog.Errorf("Health Check Error: %v\n", err) + continue + } + extUrl := fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp)) + if c.ExtUrl != extUrl { + count++ + } else { + count = 0 + } + starlog.Noticef("Health Check:Origin %s,Current %s\n", c.ExtUrl, extUrl) } else { - starlog.Infof("Health Check Ok\n") - conn.(*net.TCPConn).SetLinger(0) - conn.Close() + conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2) + if err != nil { + starlog.Warningf("Health Check Fail: %v\n", err) + count++ + } else { + count = 0 + starlog.Infof("Health Check Ok\n") + conn.(*net.TCPConn).SetLinger(0) + conn.Close() + } } if count >= 3 { count = 0 @@ -412,20 +450,20 @@ func (c *NatThrough) KeepAlive(localAddr string, localPort int) error { if err != nil { return err } - conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(localAddr), Port: localPort}, rmtUdpAddr) - if err != nil { - starlog.Errorf("Failed to dial remote: %v\n", err) + if c.Forward.UdpListener() == nil { time.Sleep(time.Second * 5) continue } - c.keepaliveConn = conn + c.keepaliveConn = c.Forward.UdpListener() for { - _, err = conn.Write([]byte("b612 tcp nat through")) + _, err = c.Forward.UdpListener().WriteTo([]byte("b612 udp nat through"), rmtUdpAddr) if err != nil { - conn.Close() + c.keepaliveConn.Close() starlog.Warningf("Failed to keepalive remote: %v\n", err) + time.Sleep(time.Second * 30) break } + starlog.Infof("UDP Keepalive Ok! %v\n", rmtUdpAddr.String()) time.Sleep(time.Second * 30) } } @@ -482,6 +520,7 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort conn.(*net.TCPConn).SetLinger(0) } if netType == "udp" { + conn, err = net.DialUDP(netType, &net.UDPAddr{IP: net.ParseIP(localip), Port: localPort}, stunAddr) if err != nil { return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err) @@ -551,6 +590,90 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort return innerAddr, outerAddr, nil } +func (c *NatThrough) UdpKeppaliveSTUN(conn *net.UDPConn, stunServer string) (net.Addr, net.Addr, error) { + // 替换为你的 TURN 服务器地址 + var target *starmap.StarStack + { + tmpConn, err := net.Dial("udp", stunServer) + if err != nil { + return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err) + } + if c.Forward.UdpHooks == nil { + c.Forward.UdpHooks = make(map[string]*starmap.StarStack) + } + if c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] == nil { + c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] = starmap.NewStarStack(16) + } + target = c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] + tmpConn.Close() + } + stunAddr, err := net.ResolveUDPAddr("udp", stunServer) + if err != nil { + return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err) + } + innerAddr := conn.LocalAddr() + + // Create STUN request + transactionID := make([]byte, 12) + rand.Read(transactionID) + stunRequest := make([]byte, 20) + binary.BigEndian.PutUint16(stunRequest[0:], 0x0001) // Message Type: Binding Request + binary.BigEndian.PutUint16(stunRequest[2:], 0x0000) // Message Length + copy(stunRequest[4:], []byte{0x21, 0x12, 0xa4, 0x42}) // Magic Cookie + copy(stunRequest[8:], transactionID) // Transaction ID + + _, err = conn.WriteToUDP(stunRequest, stunAddr) + if err != nil { + return nil, nil, fmt.Errorf("failed to send STUN request: %v", err) + } + + time.Sleep(time.Millisecond * 2500) + tmp, err := target.Pop() + if err != nil { + return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err) + } + buf := tmp.([]byte) + n := len(buf) + + // Parse STUN response + if n < 20 { + return nil, nil, fmt.Errorf("invalid STUN response") + } + + payload := buf[20:n] + var ip uint32 + var port uint16 + for len(payload) > 0 { + attrType := binary.BigEndian.Uint16(payload[0:]) + attrLen := binary.BigEndian.Uint16(payload[2:]) + if len(payload) < int(4+attrLen) { + return nil, nil, fmt.Errorf("invalid STUN attribute length") + } + + if attrType == 0x0001 || attrType == 0x0020 { + port = binary.BigEndian.Uint16(payload[6:]) + ip = binary.BigEndian.Uint32(payload[8:]) + if attrType == 0x0020 { + port ^= 0x2112 + ip ^= 0x2112a442 + } + break + } + payload = payload[4+attrLen:] + } + + if ip == 0 || port == 0 { + return nil, nil, fmt.Errorf("invalid STUN response") + } + + outerAddr := &net.UDPAddr{ + IP: net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)), + Port: int(port), + } + + return innerAddr, outerAddr, nil +} + func (c *NatThrough) GetMyOutIP() string { tmp, err := net.Dial("udp", "8.8.8.8:53") if err != nil { diff --git a/net/sshjar.go b/net/sshjar.go index 83e1e29..d71034f 100644 --- a/net/sshjar.go +++ b/net/sshjar.go @@ -79,7 +79,7 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) { data[args[0]] = args[1] } } - starnet.Curl(starnet.NewRequests(curlUrl, []byte(starnet.BuildQuery(data)), "POST")) + starnet.NewSimpleRequest(curlUrl, "POST").SetBodyDataBytes([]byte(starnet.BuildQuery(data))).Do() } }() } diff --git a/net/trace.go b/net/trace.go index e0ee1f7..702ce6f 100644 --- a/net/trace.go +++ b/net/trace.go @@ -4,7 +4,6 @@ import ( "b612.me/starlog" "b612.me/starnet" "context" - "encoding/json" "fmt" "golang.org/x/net/icmp" "golang.org/x/net/ipv4" @@ -214,12 +213,12 @@ func GetIPInfo(ip string, addr string) string { return "" } uri := strings.ReplaceAll(addr, "{ip}", ip) - res, err := starnet.Curl(starnet.NewRequests(uri, nil, "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() } var ipinfo IPInfo - err = json.Unmarshal(res.RecvData, &ipinfo) + err = res.Body().Unmarshal(&ipinfo) if err != nil { return "解析IP信息失败:" + err.Error() } diff --git a/netforward/cmd.go b/netforward/cmd.go index 0203ce5..47b9714 100644 --- a/netforward/cmd.go +++ b/netforward/cmd.go @@ -29,6 +29,7 @@ func init() { CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count") CmdNetforward.Flags().IntVarP(&f.UserTimeout, "user-timeout", "U", 0, "user timeout (milliseconds)") CmdNetforward.Flags().BoolVarP(&f.IgnoreEof, "ignore-eof", "E", false, "ignore eof") + CmdNetforward.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "verbose mode") } var CmdNetforward = &cobra.Command{ diff --git a/netforward/forward.go b/netforward/forward.go index 3698314..13ef3d4 100644 --- a/netforward/forward.go +++ b/netforward/forward.go @@ -3,6 +3,7 @@ package netforward import ( "b612.me/stario" "b612.me/starlog" + "b612.me/starmap" "context" "errors" "fmt" @@ -31,12 +32,19 @@ type NetForward struct { stopCtx context.Context stopFn context.CancelFunc running int32 + UdpHooks map[string]*starmap.StarStack KeepAlivePeriod int KeepAliveIdel int KeepAliveCount int UserTimeout int UsingKeepAlive bool + Verbose bool + udpListener *net.UDPConn +} + +func (n *NetForward) UdpListener() *net.UDPConn { + return n.udpListener } func (n *NetForward) Close() { @@ -194,17 +202,17 @@ type UDPConn struct { lastbeat int64 } -func (u UDPConn) Write(p []byte) (n int, err error) { +func (u *UDPConn) Write(p []byte) (n int, err error) { u.lastbeat = time.Now().Unix() return u.Conn.Write(p) } -func (u UDPConn) Read(p []byte) (n int, err error) { +func (u *UDPConn) Read(p []byte) (n int, err error) { u.lastbeat = time.Now().Unix() return u.Conn.Read(p) } -func (u UDPConn) Work(delay int) { +func (u *UDPConn) Work(delay int, verbose bool) { buf := make([]byte, 8192) for { if delay > 0 { @@ -216,7 +224,10 @@ func (u UDPConn) Work(delay int) { u.lastbeat = 0 return } - _, err = u.listen.Write(buf[0:count]) + if verbose { + fmt.Printf("U %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), u.Conn.RemoteAddr().String(), u.remoteAddr.String(), buf[0:count]) + } + _, err = u.listen.WriteTo(buf[0:count], u.remoteAddr) if err != nil { u.lastbeat = 0 return @@ -236,12 +247,13 @@ func (n *NetForward) runUDP() error { if err != nil { return err } + n.udpListener = listen starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) go func() { <-n.stopCtx.Done() listen.Close() }() - udpMap := make(map[string]UDPConn) + udpMap := make(map[string]*UDPConn) go func() { for { select { @@ -270,6 +282,22 @@ func (n *NetForward) runUDP() error { if err != nil || rmt.String() == n.RemoteURI { continue } + { + //hooks + if n.UdpHooks != nil { + if m, ok := n.UdpHooks[rmt.String()]; ok { + if m.Free() > 0 { + if n.Verbose { + starlog.Noticef("Hooked UDP Data %s ==> %s %X\n", rmt.String(), n.RemoteURI, buf[0:count]) + } else { + starlog.Noticef("Hooked UDP Data %s ==> %s\n", rmt.String(), n.RemoteURI) + } + m.Push(buf[0:count]) + continue + } + } + } + } go func(data []byte, rmt *net.UDPAddr) { log := starlog.Std.NewFlag() mu.Lock() @@ -282,20 +310,23 @@ func (n *NetForward) runUDP() error { mu.Unlock() return } - addr = UDPConn{ + addr = &UDPConn{ Conn: conn, remoteAddr: rmt, listen: listen, lastbeat: time.Now().Unix(), } udpMap[rmt.String()] = addr - go addr.Work(n.DelayMilSec) + go addr.Work(n.DelayMilSec, n.Verbose) log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI) } mu.Unlock() if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) { time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec)) } + if n.Verbose { + fmt.Printf("T %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), rmt.String(), n.RemoteURI, data) + } _, err := addr.Write(data) if err != nil { mu.Lock() @@ -308,6 +339,12 @@ func (n *NetForward) runUDP() error { } } +func (n *NetForward) showVerbose(toward, src, dst string, data []byte) { + if n.Verbose { + fmt.Printf("%s %v Recv Data %s ==> %s %X\n", toward, time.Now().Format("2006-01-02 15:04:05"), src, dst, data) + } +} + func (n *NetForward) copy(dst, src net.Conn) { var wg sync.WaitGroup wg.Add(2) @@ -324,6 +361,7 @@ func (n *NetForward) copy(dst, src net.Conn) { src.Close() return } + n.showVerbose("T", src.RemoteAddr().String(), dst.RemoteAddr().String(), bufsize[:count]) _, err = dst.Write(bufsize[:count]) if err != nil { src.Close() @@ -348,6 +386,7 @@ func (n *NetForward) copy(dst, src net.Conn) { dst.Close() return } + n.showVerbose("U", dst.RemoteAddr().String(), src.RemoteAddr().String(), bufsize[:count]) _, err = src.Write(bufsize[:count]) if err != nil { src.Close() diff --git a/whois/cmd.go b/whois/cmd.go index 7075660..5cf1724 100644 --- a/whois/cmd.go +++ b/whois/cmd.go @@ -1,13 +1,18 @@ package whois import ( + "b612.me/sdk/whois" + "b612.me/stario" "b612.me/starlog" "b612.me/staros" - "github.com/likexian/whois" + "bufio" + "fmt" "github.com/spf13/cobra" "golang.org/x/net/proxy" + "io" "os" "strings" + "sync" "time" ) @@ -16,13 +21,37 @@ var output string var whoisServer []string var socks5 string var socks5Auth string +var showFull bool +var hideFormat bool +var useDict bool +var hideExists bool +var hideNoExists bool +var thread int +var suffix string +var retries int func init() { Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间") Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹") Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器") Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080") - Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证,示例:username:password") + Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password") + Cmd.Flags().BoolVarP(&showFull, "full", "f", false, "显示完整信息") + Cmd.Flags().BoolVarP(&hideFormat, "hide-format", "g", false, "隐藏格式化信息") + + CmdExists.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间") + CmdExists.Flags().StringVarP(&output, "output", "o", "", "输出文件") + CmdExists.Flags().BoolVarP(&useDict, "use-dict", "d", false, "使用字典查询") + CmdExists.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器") + CmdExists.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080") + CmdExists.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password") + CmdExists.Flags().BoolVarP(&hideExists, "hide-exists", "e", false, "隐藏存在的域名") + CmdExists.Flags().BoolVarP(&hideNoExists, "hide-no-exists", "n", false, "隐藏不存在的域名") + CmdExists.Flags().IntVarP(&thread, "thread", "m", 10, "并发查询数") + CmdExists.Flags().StringVarP(&suffix, "suffix", "x", "", "域名后缀") + CmdExists.Flags().IntVarP(&retries, "retries", "r", 3, "重试次数") + + Cmd.AddCommand(CmdExists) } var Cmd = &cobra.Command{ @@ -35,7 +64,6 @@ var Cmd = &cobra.Command{ return } if !staros.Exists(output) { - cmd.Println("输出文件夹不存在,将使用标准输出") output = "" } c := whois.NewClient() @@ -70,9 +98,238 @@ var Cmd = &cobra.Command{ cmd.Println("-----------------------------------------------------") continue } - cmd.Println(data) + if !hideFormat && !data.Exists() { + fmt.Printf("域名:\t%s 不存在\n", data.Domain()) + } + if !hideFormat && data.Exists() { + fmt.Printf("域名名称:\t%s\n", data.Domain()) + fmt.Printf("注册商:\t%s\n", data.Registar()) + if data.HasRegisterDate() { + fmt.Printf("注册时间:\t%s\n", data.RegisterDate()) + } + if data.HasExpireDate() { + fmt.Printf("到期时间:\t%s\n", data.ExpireDate()) + } + if data.HasUpdateDate() { + fmt.Printf("更新时间:\t%s\n", data.UpdateDate()) + } + for _, v := range data.Status() { + fmt.Printf("域名状态:\t%s\n", v) + } + if data.IanaID() != "" { + fmt.Printf("IANA ID:\t%s\n", data.IanaID()) + } + if data.Dnssec() != "" { + fmt.Printf("DNSSEC:\t%s\n", data.Dnssec()) + } + isShowContact := false + if data.RegisterInfo().Name != "" { + fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name) + isShowContact = true + } + if data.RegisterInfo().State != "" { + fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State) + isShowContact = true + } + if data.RegisterInfo().Country != "" { + fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country) + isShowContact = true + } + if data.RegisterInfo().Email != "" { + fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email) + isShowContact = true + } + if !isShowContact { + if data.AdminInfo().Name != "" { + fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name) + isShowContact = true + } + if data.AdminInfo().State != "" { + fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State) + isShowContact = true + } + if data.AdminInfo().Country != "" { + fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country) + isShowContact = true + } + if data.AdminInfo().Email != "" { + fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email) + isShowContact = true + } + } + for _, v := range data.NsServers() { + fmt.Printf("NS服务器:\t%s\n", v) + } + for _, v := range data.NsIps() { + fmt.Printf("NS IP:\t%s\n", v) + } + } + if showFull { + fmt.Println("") + fmt.Println(data.RawData()) + } cmd.Println("-----------------------------------------------------") - os.WriteFile(output+"/"+v+".txt", []byte(data), 0644) + os.WriteFile(output+"/"+v+".txt", []byte(data.RawData()), 0644) } }, } + +var CmdExists = &cobra.Command{ + Use: "exists", + Short: "域名是否存在查询", + Long: "域名是否存在查询,可使用字典", + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.Help() + return + } + var of *os.File + var err error + if output != "" { + of, err = os.Create(output) + if err != nil { + starlog.Errorln("创建输出文件失败", err) + return + } + defer of.Close() + } + c := whois.NewClient() + if socks5 != "" { + var auth *proxy.Auth + if socks5Auth != "" { + up := strings.SplitN(socks5Auth, ":", 2) + if len(up) == 2 { + auth = &proxy.Auth{ + User: up[0], + Password: up[1], + } + } else { + starlog.Errorln("socks5认证格式错误") + return + } + } + s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct) + if err == nil { + c.SetDialer(s5Dial) + } else { + starlog.Errorln("socks5代理错误:", err) + return + } + } + c.SetTimeout(time.Second * time.Duration(timeout)) + var d domainList + if !useDict { + err = d.New("stdin", args) + } else { + err = d.New("file", args) + } + if err != nil { + starlog.Errorln("初始化域名列表失败", err) + return + } + if thread == 0 { + thread = 1 + } + var wg = stario.NewWaitGroup(thread) + fin := false + for !fin { + wg.Add(1) + go func() { + defer wg.Done() + domain, err := d.Next() + if err != nil { + if err == io.EOF { + fin = true + } + return + } + if suffix != "" { + domain = domain + "." + suffix + } + var data whois.Result + for i := 0; i < retries; i++ { + data, err = c.Whois(domain, whoisServer...) + if err != nil { + continue + } + break + } + if err != nil { + starlog.Errorln("查询失败:", domain, err) + return + } + if !hideExists && data.Exists() { + fmt.Println(domain, "已注册") + if of != nil { + of.WriteString(domain + " 已注册\n") + } + } + if !hideNoExists && !data.Exists() { + fmt.Println(domain, "不存在") + if of != nil { + of.WriteString(domain + " 不存在\n") + } + } + }() + } + wg.Wait() + }, +} + +type domainList struct { + typed string + args []string + idx int + mu sync.Mutex + f *os.File + scanner *bufio.Reader +} + +func (d *domainList) New(types string, args []string) error { + var err error + d.typed = types + d.args = args + switch d.typed { + case "file": + d.f, err = os.OpenFile(d.args[0], os.O_RDONLY, 0644) + if err != nil { + return err + } + d.scanner = bufio.NewReader(d.f) + case "stdin": + + } + return err +} + +func (d *domainList) Next() (string, error) { + d.mu.Lock() + defer d.mu.Unlock() + for { + switch d.typed { + case "file": + l, _, err := d.scanner.ReadLine() + if err == io.EOF { + if d.idx+1 >= len(d.args) { + d.f.Close() + return "", err + } + d.idx++ + d.f.Close() + d.f, err = os.OpenFile(d.args[d.idx], os.O_RDONLY, 0644) + if err != nil { + return "", err + } + d.scanner = bufio.NewReader(d.f) + continue + } + return strings.TrimSpace(string(l)), nil + case "stdin": + if d.idx >= len(d.args) { + return "", io.EOF + } + d.idx++ + return d.args[d.idx-1], nil + } + } +}