Compare commits

...

9 Commits

@ -0,0 +1,160 @@
package acme
import (
"b612.me/starcrypto"
"b612.me/starlog"
"fmt"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/challenge/http01"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/providers/dns/acmedns"
"github.com/go-acme/lego/v4/providers/dns/alidns"
"github.com/go-acme/lego/v4/providers/dns/azuredns"
"github.com/go-acme/lego/v4/providers/dns/cloudflare"
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
"os"
)
func run(a Acme) error {
// Create a user. New accounts need an email and private key to start.
if a.KeyPath != "" {
data, err := os.ReadFile(a.KeyPath)
if err != nil {
return fmt.Errorf("read key file error:%w", err)
}
a.key, err = starcrypto.DecodePrivateKey(data, "")
if err != nil {
return fmt.Errorf("decode key error:%w", err)
}
}
for _, req := range a.CertReqs {
starlog.Info("request cert for %v", req.Domains)
config := lego.NewConfig(&a)
// This CA URL is configured for a local dev instance of Boulder running in Docker in a VM.
config.CADirURL = "https://acme-v02.api.letsencrypt.org/directory"
switch req.KeyType {
case "rsa2048":
config.Certificate.KeyType = certcrypto.RSA2048
case "rsa4096":
config.Certificate.KeyType = certcrypto.RSA4096
case "rsa8192":
config.Certificate.KeyType = certcrypto.RSA8192
case "ec256":
config.Certificate.KeyType = certcrypto.EC256
case "ec384":
config.Certificate.KeyType = certcrypto.EC384
default:
config.Certificate.KeyType = certcrypto.EC384
}
// A client facilitates communication with the CA server.
client, err := lego.NewClient(config)
if err != nil {
starlog.Errorf("new client error:%v", err)
return fmt.Errorf("new client error:%w", err)
}
p := a.DnsPrivders[req.PrivderName]
switch p.Type {
case "http":
err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", p.KeyID))
if err != nil {
starlog.Errorf("set http provider error:%v", err)
return fmt.Errorf("set http provider error:%w", err)
}
err = client.Challenge.SetTLSALPN01Provider(tlsalpn01.NewProviderServer("", p.KeySecret))
if err != nil {
starlog.Errorf("set tlsalpn provider error:%v", err)
return fmt.Errorf("set tlsalpn provider error:%w", err)
}
case "tencent":
cfg := tencentcloud.NewDefaultConfig()
cfg.SecretID = p.KeyID
cfg.SecretKey = p.KeySecret
dnsSet, err := tencentcloud.NewDNSProviderConfig(cfg)
if err != nil {
starlog.Errorf("new dns provider error:%v", err)
return fmt.Errorf("new dns provider error:%w", err)
}
err = client.Challenge.SetDNS01Provider(dnsSet)
if err != nil {
starlog.Errorf("set dns provider error:%v", err)
return fmt.Errorf("set dns provider error:%w", err)
}
case "cloudflare":
cfg := cloudflare.NewDefaultConfig()
cfg.AuthKey = p.KeySecret
cfg.AuthEmail = p.KeyID
dnsSet, err := cloudflare.NewDNSProviderConfig(cfg)
if err != nil {
starlog.Errorf("new dns provider error:%v", err)
return fmt.Errorf("new dns provider error:%w", err)
}
err = client.Challenge.SetDNS01Provider(dnsSet)
if err != nil {
starlog.Errorf("set dns provider error:%v", err)
return fmt.Errorf("set dns provider error:%w", err)
}
case "alidns":
cfg := alidns.NewDefaultConfig()
cfg.APIKey = p.KeyID
cfg.SecretKey = p.KeySecret
dnsSet, err := alidns.NewDNSProviderConfig(cfg)
if err != nil {
starlog.Errorf("new dns provider error:%v", err)
return fmt.Errorf("new dns provider error:%w", err)
}
err = client.Challenge.SetDNS01Provider(dnsSet)
if err != nil {
starlog.Errorf("set dns provider error:%v", err)
return fmt.Errorf("set dns provider error:%w", err)
}
case "azure":
cfg := azuredns.NewDefaultConfig()
cfg.ClientID = p.KeyID
cfg.ClientSecret = p.KeySecret
dnsSet, err := azuredns.NewDNSProviderConfig(cfg)
if err != nil {
starlog.Errorf("new dns provider error:%v", err)
return fmt.Errorf("new dns provider error:%w", err)
}
err = client.Challenge.SetDNS01Provider(dnsSet)
if err != nil {
starlog.Errorf("set dns provider error:%v", err)
return fmt.Errorf("set dns provider error:%w", err)
}
default:
cfg, _ := acmedns.NewDNSProvider()
err = client.Challenge.SetDNS01Provider(cfg)
if err != nil {
starlog.Errorf("set dns provider error:%v", err)
return fmt.Errorf("set dns provider error:%w", err)
}
}
/*
// New users will need to register
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
if err != nil {
log.Fatal(err)
}
a.Registration = reg
request := certificate.ObtainRequest{
Domains: []string{"mydomain.com"},
Bundle: true,
}
certificates, err := client.Certificate.Obtain(request)
if err != nil {
log.Fatal(err)
}
// Each certificate comes back with the cert bytes, the bytes of the client's
// private key, and a certificate URL. SAVE THESE TO DISK.
fmt.Printf("%#v\n", certificates)
*/
}
return nil
}

@ -0,0 +1,43 @@
package acme
import (
"crypto"
"github.com/go-acme/lego/v4/registration"
)
type DnsProvider struct {
Name string
Type string
KeyID string
KeySecret string
}
type CertReq struct {
Domains []string
Type string
PrivderName string
KeyType string
SaveFolder string
SaveName string
}
// You'll need a user or account type that implements acme.User
type Acme struct {
Email string
KeyPath string
SaveFolder string
CertReqs map[string]CertReq
DnsPrivders map[string]DnsProvider
Registration *registration.Resource
key crypto.PrivateKey
}
func (u *Acme) GetEmail() string {
return u.Email
}
func (u Acme) GetRegistration() *registration.Resource {
return u.Registration
}
func (u *Acme) GetPrivateKey() crypto.PrivateKey {
return u.key
}

@ -50,3 +50,15 @@ func LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Cert
} }
return caKey, cert, nil 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
}

@ -13,12 +13,15 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/ssh"
"os" "os"
"reflect"
"software.sslmate.com/src/go-pkcs12" "software.sslmate.com/src/go-pkcs12"
"strings" "strings"
) )
func ParseCert(data []byte, pwd string) { func ParseCert(data []byte, pwd string) {
oriData := data
{ {
pems, err := pkcs12.ToPEM(data, pwd) pems, err := pkcs12.ToPEM(data, pwd)
if err == nil { if err == nil {
@ -123,7 +126,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n") starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X) starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y) starlog.Green("私钥公钥Y%d\n", n.Y)
case *ed25519.PrivateKey: case *ed25519.PrivateKey, ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n") starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey: case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n") starlog.Green("这是一个ECDH私钥\n")
@ -206,7 +209,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥公钥Y%d\n", n.Y) starlog.Green("公钥公钥Y%d\n", n.Y)
case *ecdh.PublicKey: case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n") starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey: case *ed25519.PublicKey, ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n") starlog.Green("公钥算法为ED25519\n")
default: default:
starlog.Green("未知公钥类型\n") starlog.Green("未知公钥类型\n")
@ -255,12 +258,18 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n") starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X) starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y) 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: case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n") 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") starlog.Green("这是一个ECDH私钥\n")
default: default:
starlog.Green("未知私钥类型\n") starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
} }
continue continue
@ -349,8 +358,72 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥长度:%d\n", n.Params().BitSize) starlog.Green("公钥长度:%d\n", n.Params().BitSize)
starlog.Green("公钥公钥X%d\n", n.X) starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y) starlog.Green("公钥公钥Y%d\n", n.Y)
case "OPENSSH PRIVATE KEY":
starlog.Infof("这是一个OpenSSH私钥文件\n")
var priv interface{}
var err error
if pwd == "" {
priv, err = ssh.ParseRawPrivateKey(oriData)
if err != nil {
starlog.Errorf("解析OPENSSH私钥错误%s\n", err)
continue
}
} else {
priv, err = ssh.ParseRawPrivateKeyWithPassphrase(oriData, []byte(pwd))
if err != nil {
starlog.Errorf("解析OPENSSH私钥错误%s\n", err)
continue
}
}
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
starlog.Green("私钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
starlog.Green("私钥系数dP%d\n", n.Precomputed.Dp)
starlog.Green("私钥系数dQ%d\n", n.Precomputed.Dq)
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
starlog.Green("私钥公钥Y%d\n", n.PublicKey.Y)
case *dsa.PrivateKey:
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")
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
starlog.Green("公钥:%s\n", string(sshPub))
case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
}
continue
case "OPENSSH PUBLIC KEY":
starlog.Infof("这是一个OPENSSH公钥文件\n")
pub, err := ssh.ParsePublicKey(block.Bytes)
if err != nil {
starlog.Errorf("解析公钥错误:%s\n", err)
continue
}
starlog.Green("公钥算法:%s\n", pub.Type())
continue
default: default:
starlog.Infof("未知证书文件类型\n") starlog.Infof("不支持的证书文件类型:%v\n", reflect.TypeOf(block))
} }
} }
} }
@ -427,6 +500,7 @@ func extKeyUsageToStrings(extKeyUsages []x509.ExtKeyUsage) string {
} }
func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) { func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
var oriData = data
var common []any var common []any
var certs []x509.Certificate var certs []x509.Certificate
{ {
@ -485,9 +559,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey: case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n") starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey: case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n") starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey: case ecdh.PrivateKey, *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n") starlog.Green("这是一个ECDH私钥\n")
default: default:
starlog.Green("未知私钥类型\n") starlog.Green("未知私钥类型\n")
@ -572,16 +646,15 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize) starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey: case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n") starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey: case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n") starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey: case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n") starlog.Green("这是一个ECDH私钥\n")
default: default:
starlog.Green("未知私钥类型\n") starlog.Green("未知私钥类型\n")
} }
common = append(common, priv) common = append(common, priv)
continue continue
case "PUBLIC KEY": case "PUBLIC KEY":
starlog.Infof("这是一个公钥文件\n") starlog.Infof("这是一个公钥文件\n")
pub, err := x509.ParsePKIXPublicKey(block.Bytes) pub, err := x509.ParsePKIXPublicKey(block.Bytes)
@ -666,33 +739,93 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("公钥公钥X%d\n", n.X) starlog.Green("公钥公钥X%d\n", n.X)
starlog.Green("公钥公钥Y%d\n", n.Y) starlog.Green("公钥公钥Y%d\n", n.Y)
common = append(common, n) common = append(common, n)
case "OPENSSH PRIVATE KEY":
starlog.Infof("这是一个OpenSSH私钥文件\n")
var priv interface{}
var err error
if pwd == "" {
priv, err = ssh.ParseRawPrivateKey(oriData)
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
} else {
priv, err = ssh.ParseRawPrivateKeyWithPassphrase(oriData, []byte(pwd))
if err != nil {
starlog.Errorf("解析私钥错误:%s\n", err)
continue
}
}
switch n := priv.(type) {
case *rsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个RSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Size())
starlog.Green("私钥长度:%d\n", n.N.BitLen())
case *ecdsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个DSA私钥\n")
case ed25519.PrivateKey, *ed25519.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ED25519私钥\n")
case ecdh.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
}
continue
default: default:
starlog.Infof("未知证书文件类型\n") starlog.Infof("未知证书文件类型\n")
} }
} }
} }
func Pkcs8(data []byte, pwd string, originName string, outpath string) error { func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd) keys, _, err := GetCert(data, pwd)
if err != nil { if err != nil {
return err return err
} }
fmt.Println(len(keys))
for _, v := range keys { for _, v := range keys {
if v == nil { if v == nil {
continue continue
} }
switch n := v.(type) { switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey: case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey, ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n) 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 { if err != nil {
return err return err
} }
err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: data}), 0644) 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 { if err != nil {
fmt.Println("5")
return err return err
} else { } else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8") starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8")
} }
fmt.Println("6")
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey: case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n) data, err = x509.MarshalPKIXPublicKey(n)
if err != nil { if err != nil {
@ -704,12 +837,14 @@ func Pkcs8(data []byte, pwd string, originName string, outpath string) error {
} else { } else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8") starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8")
} }
default:
return fmt.Errorf("未知的密钥类型:%v", reflect.TypeOf(n))
} }
} }
return nil return nil
} }
func Pkcs1(data []byte, pwd string, originName string, outpath string) error { func Pkcs1(data []byte, pwd, newPwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd) keys, _, err := GetCert(data, pwd)
if err != nil { if err != nil {
return err return err
@ -724,7 +859,13 @@ func Pkcs1(data []byte, pwd string, originName string, outpath string) error {
if err != nil { if err != nil {
return err return err
} }
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.pkcs1", outpath, originName, idx), pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: data}), 0644) var block *pem.Block
if newPwd != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, "RSA PRIVATE KEY", data, []byte(newPwd), x509.PEMCipherAES256)
} else {
block = &pem.Block{Type: "RSA PRIVATE KEY", Bytes: data}
}
err = os.WriteFile(fmt.Sprintf("%s/%s_%v.pkcs1", outpath, originName, idx), pem.EncodeToMemory(block), 0644)
if err != nil { if err != nil {
return err return err
} else { } else {
@ -753,8 +894,11 @@ func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName strin
continue continue
} }
switch n := v.(type) { 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 priv = n
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
priv = *(n.(*ed25519.PrivateKey))
}
break break
} }
} }
@ -815,7 +959,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else { } else {
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx)) 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) data, err = x509.MarshalPKCS8PrivateKey(n)
if err != nil { if err != nil {
return err return err
@ -826,7 +970,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else { } else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key") 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) data, err = x509.MarshalPKIXPublicKey(n)
if err != nil { if err != nil {
return err return err
@ -842,3 +986,50 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} }
return nil return nil
} }
func Openssh(data []byte, pwd, newPwd string, originName string, outpath string) error {
keys, _, err := GetCert(data, pwd)
if err != nil {
return err
}
for _, v := range keys {
if v == nil {
continue
}
var block *pem.Block
switch n := v.(type) {
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(key, "", []byte(newPwd))
}
if err != nil {
return err
}
err = os.WriteFile(outpath+"/"+originName+".openssh", pem.EncodeToMemory(block), 0644)
if err != nil {
return err
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".openssh")
}
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
sk, err := ssh.NewPublicKey(n)
if err != nil {
return err
}
data = ssh.MarshalAuthorizedKey(sk)
err = os.WriteFile(outpath+"/"+originName+".pub.openssh", data, 0644)
if err != nil {
return err
} else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.openssh")
}
}
}
return nil
}

@ -4,6 +4,7 @@ import (
"b612.me/starcrypto" "b612.me/starcrypto"
"b612.me/stario" "b612.me/stario"
"b612.me/starlog" "b612.me/starlog"
"crypto"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -64,12 +65,6 @@ var CmdCsr = &cobra.Command{
if dnsName == nil { if dnsName == nil {
dnsName = stario.MessageBox("请输入dns名称用逗号分割", "").MustSliceString(",") dnsName = stario.MessageBox("请输入dns名称用逗号分割", "").MustSliceString(",")
} }
if startStr == "" {
startStr = stario.MessageBox("请输入开始时间:", "").MustString()
}
if endStr == "" {
endStr = stario.MessageBox("请输入结束时间:", "").MustString()
}
} }
start, err = time.Parse(time.RFC3339, startStr) start, err = time.Parse(time.RFC3339, startStr)
if err != nil { if err != nil {
@ -81,7 +76,12 @@ var CmdCsr = &cobra.Command{
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err) starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1) 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) err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644)
if err != nil { if err != nil {
starlog.Errorln("保存csr文件错误", err) starlog.Errorln("保存csr文件错误", err)
@ -112,10 +112,21 @@ var CmdGen = &cobra.Command{
starlog.Errorln("证书公钥不能为空") starlog.Errorln("证书公钥不能为空")
os.Exit(1) os.Exit(1)
} }
caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd) var caKeyRaw crypto.PrivateKey
if err != nil { var caCertRaw *x509.Certificate
starlog.Errorln("加载CA错误", err) var err error
os.Exit(1) 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) csrRaw, err := LoadCsr(csr)
if err != nil { if err != nil {
@ -132,7 +143,18 @@ var CmdGen = &cobra.Command{
starlog.Errorln("解析公钥错误", err) starlog.Errorln("解析公钥错误", err)
os.Exit(1) 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 { if err != nil {
starlog.Errorln("生成证书错误", err) starlog.Errorln("生成证书错误", err)
os.Exit(1) os.Exit(1)
@ -171,19 +193,21 @@ var CmdParse = &cobra.Command{
func init() { func init() {
Cmd.AddCommand(CmdCsr) Cmd.AddCommand(CmdCsr)
CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式") CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家") CmdCsr.Flags().StringVarP(&country, "country", "c", "CN", "国家")
CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份") CmdCsr.Flags().StringVarP(&province, "province", "p", "B612", "省份")
CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市") CmdCsr.Flags().StringVarP(&city, "city", "t", "B612", "城市")
CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织") CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织")
CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位") 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().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().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA") CmdCsr.Flags().StringVarP(&caKey, "secret-key", "k", "", "加密私钥")
CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0") CmdCsr.Flags().StringVarP(&caKeyPwd, "secret-key-passwd", "K", "", "加密私钥的密码")
CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度") //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(&caKey, "caKey", "k", "", "CA私钥")
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书") CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
@ -191,25 +215,39 @@ func init() {
CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥") CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥")
CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹") CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码") 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) Cmd.AddCommand(CmdGen)
CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码") CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
Cmd.AddCommand(CmdParse) Cmd.AddCommand(CmdParse)
CmdPkcs8.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdPkcs8.Flags().StringVarP(&passwd, "passwd", "p", "", "解密密码")
CmdPkcs8.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹") CmdPkcs8.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
CmdPkcs8.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
Cmd.AddCommand(CmdPkcs8) Cmd.AddCommand(CmdPkcs8)
CmdPkcs1.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码") CmdPkcs1.Flags().StringVarP(&passwd, "passwd", "p", "", "解密密码")
CmdPkcs1.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹") CmdPkcs1.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
CmdPkcs1.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
Cmd.AddCommand(CmdPkcs1) Cmd.AddCommand(CmdPkcs1)
CmdPkcs12.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码") CmdPkcs12.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
CmdPkcs12.Flags().StringVarP(&enPasswd, "pfx-passwd", "P", "", "pfx加密密码") CmdPkcs12.Flags().StringVarP(&enPasswd, "pfx-passwd", "P", "", "pfx加密密码")
CmdPkcs12.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹") CmdPkcs12.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
Cmd.AddCommand(CmdPkcs12) Cmd.AddCommand(CmdPkcs12)
CmdBasic.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码") CmdBasic.Flags().StringVarP(&passwd, "passwd", "p", "", "解密密码")
CmdBasic.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹") CmdBasic.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
CmdBasic.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
Cmd.AddCommand(CmdBasic) Cmd.AddCommand(CmdBasic)
CmdOpenssh.Flags().StringVarP(&passwd, "passwd", "p", "", "解密密码")
CmdOpenssh.Flags().StringVarP(&savefolder, "savefolder", "s", ".", "保存文件夹")
CmdOpenssh.Flags().StringVarP(&enPasswd, "en-passwd", "P", "", "加密密码")
Cmd.AddCommand(CmdOpenssh)
} }
var CmdPkcs8 = &cobra.Command{ var CmdPkcs8 = &cobra.Command{
@ -227,7 +265,7 @@ var CmdPkcs8 = &cobra.Command{
starlog.Errorln("读取证书错误", err) starlog.Errorln("读取证书错误", err)
continue continue
} }
err = Pkcs8(data, passwd, filepath.Base(v), savefolder) err = Pkcs8(data, passwd, enPasswd, filepath.Base(v), savefolder)
if err != nil { if err != nil {
starlog.Errorln("pkcs8转换错误", err) starlog.Errorln("pkcs8转换错误", err)
continue continue
@ -252,7 +290,7 @@ var CmdPkcs1 = &cobra.Command{
starlog.Errorln("读取证书错误", err) starlog.Errorln("读取证书错误", err)
continue continue
} }
err = Pkcs1(data, passwd, filepath.Base(v), savefolder) err = Pkcs1(data, passwd, enPasswd, filepath.Base(v), savefolder)
if err != nil { if err != nil {
starlog.Errorln("pkcs1转换错误", err) starlog.Errorln("pkcs1转换错误", err)
continue continue
@ -321,3 +359,28 @@ var CmdBasic = &cobra.Command{
} }
}, },
} }
var CmdOpenssh = &cobra.Command{
Use: "openssh",
Short: "openssh转换",
Long: "openssh转换",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorln("请输入证书文件")
os.Exit(1)
}
for _, v := range args {
data, err := os.ReadFile(v)
if err != nil {
starlog.Errorln("读取证书错误", err)
continue
}
err = Openssh(data, passwd, enPasswd, filepath.Base(v), savefolder)
if err != nil {
starlog.Errorln("openssh转换错误", err)
continue
}
fmt.Println("\n-------" + v + "转换完毕---------\n")
}
},
}

@ -1,17 +1,16 @@
package cert package cert
import ( import (
"crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix" "crypto/x509/pkix"
"encoding/pem" "encoding/pem"
"errors" "errors"
"math/big"
"net" "net"
"os" "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 trueDNS []string
var trueIp []net.IP var trueIp []net.IP
for _, v := range dnsName { for _, v := range dnsName {
@ -22,15 +21,17 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
} }
trueIp = append(trueIp, ip) trueIp = append(trueIp, ip)
} }
ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment /*
eku := x509.ExtKeyUsageServerAuth ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
if isCa { eku := x509.ExtKeyUsageServerAuth
ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature if isCa {
eku = x509.ExtKeyUsageAny 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()), return &x509.CertificateRequest{
Version: 3,
//SerialNumber: big.NewInt(time.Now().Unix()),
Subject: pkix.Name{ Subject: pkix.Name{
Country: s2s(country), Country: s2s(country),
Province: s2s(province), Province: s2s(province),
@ -39,23 +40,27 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
OrganizationalUnit: s2s(orgUnit), OrganizationalUnit: s2s(orgUnit),
CommonName: name, CommonName: name,
}, },
DNSNames: trueDNS, DNSNames: trueDNS,
IPAddresses: trueIp, IPAddresses: trueIp,
NotBefore: start, //NotBefore: start,
NotAfter: end, //NotAfter: end,
BasicConstraintsValid: true, //BasicConstraintsValid: true,
IsCA: isCa, //IsCA: isCa,
MaxPathLen: maxPathLen, //MaxPathLen: maxPathLen,
MaxPathLenZero: maxPathLenZero, //MaxPathLenZero: maxPathLenZero,
KeyUsage: ku, //KeyUsage: ku,
ExtKeyUsage: []x509.ExtKeyUsage{eku}, //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{ return pem.EncodeToMemory(&pem.Block{
Type: "CERTIFICATE REQUEST", Type: "CERTIFICATE REQUEST",
Bytes: csr.Raw, Bytes: csrBytes,
}) })
} }
@ -66,7 +71,7 @@ func s2s(str string) []string {
return []string{str} return []string{str}
} }
func LoadCsr(csrPath string) (*x509.Certificate, error) { func LoadCsr(csrPath string) (*x509.CertificateRequest, error) {
csrBytes, err := os.ReadFile(csrPath) csrBytes, err := os.ReadFile(csrPath)
if err != nil { if err != nil {
return nil, err return nil, err
@ -75,7 +80,7 @@ func LoadCsr(csrPath string) (*x509.Certificate, error) {
if block == nil || block.Type != "CERTIFICATE REQUEST" { if block == nil || block.Type != "CERTIFICATE REQUEST" {
return nil, errors.New("Failed to decode PEM block containing the certificate") 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 { if err != nil {
return nil, err return nil, err
} }

@ -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() // No need to use hreq.URL.Query()
hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil) hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil)
hreq.Header.Set("User-Agent", "B612 DoH Client")
hreq.Header.Add("Accept", DoHMediaType) hreq.Header.Add("Accept", DoHMediaType)
resp, err := c.cli.Do(hreq) resp, err := c.cli.Do(hreq)
if err != nil { if err != nil {

@ -1,47 +1,83 @@
module b612.me/apps/b612 module b612.me/apps/b612
go 1.19 go 1.21.2
toolchain go1.22.4
require ( 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/starcrypto v0.0.5
b612.me/stario v0.0.9 b612.me/stario v0.0.10
b612.me/starlog v1.3.3 b612.me/starlog v1.3.4
b612.me/starnet v0.1.8 b612.me/starmap v1.2.4
b612.me/staros v1.1.7 b612.me/starnet v0.2.1
b612.me/staros v1.1.8
b612.me/starssh v0.0.2 b612.me/starssh v0.0.2
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd 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 v0.0.0-20231117061959-7cc037d33fb5
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
github.com/emersion/go-smtp v0.20.2 github.com/emersion/go-smtp v0.20.2
github.com/go-acme/lego/v4 v4.16.1
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 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/inconshreveable/mousetrap v1.1.0
github.com/likexian/whois v1.15.1
github.com/miekg/dns v1.1.58 github.com/miekg/dns v1.1.58
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/things-go/go-socks5 v0.0.5 github.com/things-go/go-socks5 v0.0.5
golang.org/x/crypto 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 software.sslmate.com/src/go-pkcs12 v0.4.0
) )
require ( require (
b612.me/starmap v1.2.4 // indirect
b612.me/win32api v0.0.2 // 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/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/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/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/pkg/sftp v1.13.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // 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/image v0.6.0 // indirect
golang.org/x/mod v0.14.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/net v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.18.0 // indirect golang.org/x/term v0.23.0 // indirect
golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.17.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
) )

184
go.sum

@ -1,29 +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.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.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4= b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0= 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/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI= b612.me/stario v0.0.10 h1:+cIyiDCBCjUfodMJDp4FLs+2E1jo7YENkN+sMEe6550=
b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE= 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 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc= 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/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g= b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40= 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 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk= 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 h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8= 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 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA=
b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0= 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.4 h1:fv9p1V8mw2HdUjaoZBWZy0T41JftueyLxAuch1MgtdI=
b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo= 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/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.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 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 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= 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= github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
@ -32,31 +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-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 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= 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 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss= 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 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE= 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 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
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 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 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 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 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 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE= 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 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0= 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 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY= 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 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 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 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/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/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
@ -64,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 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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/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.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 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 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ= 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= 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-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-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-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.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.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.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 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= 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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 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-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-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-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.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.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.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.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-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.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.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-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-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-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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.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.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.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-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.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.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.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.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.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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -120,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.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.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.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.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-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.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.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.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= 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-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 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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=

@ -12,4 +12,9 @@ authuser=b612
authpasswd=b612 authpasswd=b612
whiteip= whiteip=
blackip= blackip=
wanringpage= wanringpage=
ipfiltermode=3
filterxforward=
filterremoteaddr=
filtermustkey=
filterfile=

@ -75,7 +75,7 @@ var Cmd = &cobra.Command{
SkipSSLVerify: skipsslverify, SkipSSLVerify: skipsslverify,
Key: key, Key: key,
Cert: cert, Cert: cert,
XForwardMode: 1, IPFilterMode: 1,
} }
go func() { go func() {
sig := make(chan os.Signal) sig := make(chan os.Signal)

@ -1,35 +1,46 @@
package httpreverse package httpreverse
import ( import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/starlog"
"b612.me/staros/sysconf" "b612.me/staros/sysconf"
"bufio"
"errors" "errors"
"io"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/http/httputil"
"net/url" "net/url"
"os"
"strings" "strings"
"sync" "sync"
) )
type ReverseConfig struct { type ReverseConfig struct {
Name string Name string
Addr string Addr string
ReverseURL map[string]*url.URL ReverseURL map[string]*url.URL
Port int Port int
UsingSSL bool UsingSSL bool
Key string Key string
Cert string Cert string
Host string Host string
SkipSSLVerify bool SkipSSLVerify bool
InHeader [][2]string InHeader [][2]string
OutHeader [][2]string OutHeader [][2]string
Cookie [][3]string //[3]string should contains path::key::value Cookie [][3]string //[3]string should contains path::key::value
ReplaceList [][2]string ReplaceList [][2]string
ReplaceOnce bool ReplaceOnce bool
proxy map[string]*httputil.ReverseProxy proxy map[string]*rp.ReverseProxy
XForwardMode int //0=off 1=useremote 2=add IPFilterMode int //0=off 1=useremote 2=add 3=filter
httpmux http.ServeMux FilterXForward bool
httpserver http.Server FilterRemoteAddr bool
FilterMustKey string
FilterSetKey string
FilterFile string
httpmux http.ServeMux
httpserver http.Server
CIDR []*net.IPNet
basicAuthUser string basicAuthUser string
basicAuthPwd string basicAuthPwd string
@ -53,18 +64,54 @@ func Parse(path string) (HttpReverseServer, error) {
} }
for _, v := range ini.Data { for _, v := range ini.Data {
var ins = ReverseConfig{ var ins = ReverseConfig{
Name: v.Name, Name: v.Name,
Host: v.Get("host"), Host: v.Get("host"),
Addr: v.Get("addr"), Addr: v.Get("addr"),
Port: v.Int("port"), Port: v.Int("port"),
UsingSSL: v.Bool("enablessl"), UsingSSL: v.Bool("enablessl"),
Key: v.Get("key"), Key: v.Get("key"),
Cert: v.Get("cert"), Cert: v.Get("cert"),
ReplaceOnce: v.Bool("replaceonce"), ReplaceOnce: v.Bool("replaceonce"),
XForwardMode: v.Int("xforwardmode"), IPFilterMode: v.Int("ipfiltermode"),
basicAuthUser: v.Get("authuser"), FilterXForward: v.Bool("filterxforward"),
basicAuthPwd: v.Get("authpasswd"), FilterRemoteAddr: v.Bool("filterremoteaddr"),
warningpage: v.Get("warnpage"), FilterMustKey: v.Get("filtermustkey"),
FilterSetKey: v.Get("filtersetkey"),
FilterFile: v.Get("filterfile"),
basicAuthUser: v.Get("authuser"),
basicAuthPwd: v.Get("authpasswd"),
warningpage: v.Get("warnpage"),
}
if ins.IPFilterMode == 3 && ins.FilterFile != "" {
starlog.Infoln("IP Filter Mode 3, Load IP Filter File", ins.FilterFile)
f, err := os.Open(ins.FilterFile)
if err != nil {
return res, err
}
buf := bufio.NewReader(f)
count := 0
for {
line, err := buf.ReadString('\n')
if err != nil {
if err == io.EOF {
f.Close()
break
}
f.Close()
return res, err
}
line = strings.TrimSpace(line)
if !strings.Contains(line, "/") {
line += "/32" //todo区分IPV6
}
_, cidr, err := net.ParseCIDR(line)
if err != nil {
return res, err
}
ins.CIDR = append(ins.CIDR, cidr)
count++
}
starlog.Infoln("Load", count, "CIDR")
} }
if ins.warningpage != "" { if ins.warningpage != "" {
data, err := ioutil.ReadFile(ins.warningpage) data, err := ioutil.ReadFile(ins.warningpage)
@ -73,7 +120,7 @@ func Parse(path string) (HttpReverseServer, error) {
} }
ins.warnpagedata = data ins.warnpagedata = data
} }
ins.proxy = make(map[string]*httputil.ReverseProxy) ins.proxy = make(map[string]*rp.ReverseProxy)
ins.ReverseURL = make(map[string]*url.URL) ins.ReverseURL = make(map[string]*url.URL)
for _, reverse := range v.GetAll("reverse") { for _, reverse := range v.GetAll("reverse") {
kv := strings.SplitN(reverse, "::", 2) kv := strings.SplitN(reverse, "::", 2)

@ -2,9 +2,18 @@ package httpreverse
import ( import (
"fmt" "fmt"
"net"
"testing" "testing"
) )
func TestCIDR(t *testing.T) {
_, c, err := net.ParseCIDR("108.162.192.0/18")
if err != nil {
t.Fatal(err)
}
fmt.Println(c.Contains(net.ParseIP("108.162.245.124")))
}
func TestReverseParse(t *testing.T) { func TestReverseParse(t *testing.T) {
data, err := Parse("./cfg.ini") data, err := Parse("./cfg.ini")
if err != nil { if err != nil {

@ -0,0 +1,852 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// HTTP reverse proxy handler
package rp
import (
"context"
"errors"
"fmt"
"io"
"log"
"mime"
"net"
"net/http"
"net/http/httptrace"
"net/textproto"
"net/url"
"strings"
"sync"
"time"
"golang.org/x/net/http/httpguts"
)
func lower(b byte) byte {
if 'A' <= b && b <= 'Z' {
return b + ('a' - 'A')
}
return b
}
func EqualFold(s, t string) bool {
if len(s) != len(t) {
return false
}
for i := 0; i < len(s); i++ {
if lower(s[i]) != lower(t[i]) {
return false
}
}
return true
}
func IsPrint(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] < ' ' || s[i] > '~' {
return false
}
}
return true
}
// A ProxyRequest contains a request to be rewritten by a ReverseProxy.
type ProxyRequest struct {
// In is the request received by the proxy.
// The Rewrite function must not modify In.
In *http.Request
// Out is the request which will be sent by the proxy.
// The Rewrite function may modify or replace this request.
// Hop-by-hop headers are removed from this request
// before Rewrite is called.
Out *http.Request
}
// SetURL routes the outbound request to the scheme, host, and base path
// provided in target. If the target's path is "/base" and the incoming
// request was for "/dir", the target request will be for "/base/dir".
//
// SetURL rewrites the outbound Host header to match the target's host.
// To preserve the inbound request's Host header (the default behavior
// of NewSingleHostReverseProxy):
//
// rewriteFunc := func(r *httputil.ProxyRequest) {
// r.SetURL(url)
// r.Out.Host = r.In.Host
// }
func (r *ProxyRequest) SetURL(target *url.URL) {
rewriteRequestURL(r.Out, target)
r.Out.Host = ""
}
// SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and
// X-Forwarded-Proto headers of the outbound request.
//
// - The X-Forwarded-For header is set to the client IP address.
// - The X-Forwarded-Host header is set to the host name requested
// by the client.
// - The X-Forwarded-Proto header is set to "http" or "https", depending
// on whether the inbound request was made on a TLS-enabled connection.
//
// If the outbound request contains an existing X-Forwarded-For header,
// SetXForwarded appends the client IP address to it. To append to the
// inbound request's X-Forwarded-For header (the default behavior of
// ReverseProxy when using a Director function), copy the header
// from the inbound request before calling SetXForwarded:
//
// rewriteFunc := func(r *httputil.ProxyRequest) {
// r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
// r.SetXForwarded()
// }
func (r *ProxyRequest) SetXForwarded() {
clientIP, _, err := net.SplitHostPort(r.In.RemoteAddr)
if err == nil {
prior := r.Out.Header["X-Forwarded-For"]
if len(prior) > 0 {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
r.Out.Header.Set("X-Forwarded-For", clientIP)
} else {
r.Out.Header.Del("X-Forwarded-For")
}
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
if r.In.TLS == nil {
r.Out.Header.Set("X-Forwarded-Proto", "http")
} else {
r.Out.Header.Set("X-Forwarded-Proto", "https")
}
}
// ReverseProxy is an HTTP Handler that takes an incoming request and
// sends it to another server, proxying the response back to the
// client.
//
// 1xx responses are forwarded to the client if the underlying
// transport supports ClientTrace.Got1xxResponse.
type ReverseProxy struct {
// Rewrite must be a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Rewrite must not access the provided ProxyRequest
// or its contents after returning.
//
// The Forwarded, X-Forwarded, X-Forwarded-Host,
// and X-Forwarded-Proto headers are removed from the
// outbound request before Rewrite is called. See also
// the ProxyRequest.SetXForwarded method.
//
// Unparsable query parameters are removed from the
// outbound request before Rewrite is called.
// The Rewrite function may copy the inbound URL's
// RawQuery to the outbound URL to preserve the original
// parameter string. Note that this can lead to security
// issues if the proxy's interpretation of query parameters
// does not match that of the downstream server.
//
// At most one of Rewrite or Director may be set.
Rewrite func(*ProxyRequest)
// Director is a function which modifies
// the request into a new request to be sent
// using Transport. Its response is then copied
// back to the original client unmodified.
// Director must not access the provided Request
// after returning.
//
// By default, the X-Forwarded-For header is set to the
// value of the client IP address. If an X-Forwarded-For
// header already exists, the client IP is appended to the
// existing values. As a special case, if the header
// exists in the Request.Header map but has a nil value
// (such as when set by the Director func), the X-Forwarded-For
// header is not modified.
//
// To prevent IP spoofing, be sure to delete any pre-existing
// X-Forwarded-For header coming from the client or
// an untrusted proxy.
//
// Hop-by-hop headers are removed from the request after
// Director returns, which can remove headers added by
// Director. Use a Rewrite function instead to ensure
// modifications to the request are preserved.
//
// Unparsable query parameters are removed from the outbound
// request if Request.Form is set after Director returns.
//
// At most one of Rewrite or Director may be set.
Director func(*http.Request)
// The transport used to perform proxy requests.
// If nil, http.DefaultTransport is used.
Transport http.RoundTripper
// FlushInterval specifies the flush interval
// to flush to the client while copying the
// response body.
// If zero, no periodic flushing is done.
// A negative value means to flush immediately
// after each write to the client.
// The FlushInterval is ignored when ReverseProxy
// recognizes a response as a streaming response, or
// if its ContentLength is -1; for such responses, writes
// are flushed to the client immediately.
FlushInterval time.Duration
// ErrorLog specifies an optional logger for errors
// that occur when attempting to proxy the request.
// If nil, logging is done via the log package's standard logger.
ErrorLog *log.Logger
// BufferPool optionally specifies a buffer pool to
// get byte slices for use by io.CopyBuffer when
// copying HTTP response bodies.
BufferPool BufferPool
// ModifyResponse is an optional function that modifies the
// Response from the backend. It is called if the backend
// returns a response at all, with any HTTP status code.
// If the backend is unreachable, the optional ErrorHandler is
// called without any call to ModifyResponse.
//
// If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default
// implementation is used.
ModifyResponse func(*http.Response) error
// ErrorHandler is an optional function that handles errors
// reaching the backend or errors from ModifyResponse.
//
// If nil, the default is to log the provided error and return
// a 502 Status Bad Gateway response.
ErrorHandler func(http.ResponseWriter, *http.Request, error)
}
// A BufferPool is an interface for getting and returning temporary
// byte slices for use by io.CopyBuffer.
type BufferPool interface {
Get() []byte
Put([]byte)
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}
func joinURLPath(a, b *url.URL) (path, rawpath string) {
if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), ""
}
// Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added
apath := a.EscapedPath()
bpath := b.EscapedPath()
aslash := strings.HasSuffix(apath, "/")
bslash := strings.HasPrefix(bpath, "/")
switch {
case aslash && bslash:
return a.Path + b.Path[1:], apath + bpath[1:]
case !aslash && !bslash:
return a.Path + "/" + b.Path, apath + "/" + bpath
}
return a.Path + b.Path, apath + bpath
}
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// URLs to the scheme, host, and base path provided in target. If the
// target's path is "/base" and the incoming request was for "/dir",
// the target request will be for /base/dir.
//
// NewSingleHostReverseProxy does not rewrite the Host header.
//
// To customize the ReverseProxy behavior beyond what
// NewSingleHostReverseProxy provides, use ReverseProxy directly
// with a Rewrite function. The ProxyRequest SetURL method
// may be used to route the outbound request. (Note that SetURL,
// unlike NewSingleHostReverseProxy, rewrites the Host header
// of the outbound request by default.)
//
// proxy := &ReverseProxy{
// Rewrite: func(r *ProxyRequest) {
// r.SetURL(target)
// r.Out.Host = r.In.Host // if desired
// }
// }
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
director := func(req *http.Request) {
rewriteRequestURL(req, target)
}
return &ReverseProxy{Director: director}
}
func rewriteRequestURL(req *http.Request, target *url.URL) {
targetQuery := target.RawQuery
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
// Hop-by-hop headers. These are removed when sent to the backend.
// As of RFC 7230, hop-by-hop headers are required to appear in the
// Connection header field. These are the headers defined by the
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
// compatibility.
var hopHeaders = []string{
"Connection",
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
"Transfer-Encoding",
"Upgrade",
}
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
p.logf("http: proxy error: %v", err)
rw.WriteHeader(http.StatusBadGateway)
}
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
if p.ErrorHandler != nil {
return p.ErrorHandler
}
return p.defaultErrorHandler
}
// modifyResponse conditionally runs the optional ModifyResponse hook
// and reports whether the request should proceed.
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
if p.ModifyResponse == nil {
return true
}
if err := p.ModifyResponse(res); err != nil {
res.Body.Close()
p.getErrorHandler()(rw, req, err)
return false
}
return true
}
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
transport := p.Transport
if transport == nil {
transport = http.DefaultTransport
}
ctx := req.Context()
if ctx.Done() != nil {
// CloseNotifier predates context.Context, and has been
// entirely superseded by it. If the request contains
// a Context that carries a cancellation signal, don't
// bother spinning up a goroutine to watch the CloseNotify
// channel (if any).
//
// If the request Context has a nil Done channel (which
// means it is either context.Background, or a custom
// Context implementation with no cancellation signal),
// then consult the CloseNotifier if available.
} else if cn, ok := rw.(http.CloseNotifier); ok {
var cancel context.CancelFunc
ctx, cancel = context.WithCancel(ctx)
defer cancel()
notifyChan := cn.CloseNotify()
go func() {
select {
case <-notifyChan:
cancel()
case <-ctx.Done():
}
}()
}
outreq := req.Clone(ctx)
if req.ContentLength == 0 {
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
}
if outreq.Body != nil {
// Reading from the request body after returning from a handler is not
// allowed, and the RoundTrip goroutine that reads the Body can outlive
// this handler. This can lead to a crash if the handler panics (see
// Issue 46866). Although calling Close doesn't guarantee there isn't
// any Read in flight after the handle returns, in practice it's safe to
// read after closing it.
defer outreq.Body.Close()
}
if outreq.Header == nil {
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
}
if (p.Director != nil) == (p.Rewrite != nil) {
p.getErrorHandler()(rw, req, errors.New("ReverseProxy must have exactly one of Director or Rewrite set"))
return
}
if p.Director != nil {
p.Director(outreq)
if outreq.Form != nil {
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
}
}
outreq.Close = false
reqUpType := upgradeType(outreq.Header)
if !IsPrint(reqUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
return
}
removeHopByHopHeaders(outreq.Header)
// Issue 21096: tell backend applications that care about trailer support
// that we support trailers. (We do, but we don't go out of our way to
// advertise that unless the incoming client request thought it was worth
// mentioning.) Note that we look at req.Header, not outreq.Header, since
// the latter has passed through removeHopByHopHeaders.
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
outreq.Header.Set("Te", "trailers")
}
// After stripping all the hop-by-hop connection headers above, add back any
// necessary for protocol upgrades, such as for websockets.
if reqUpType != "" {
outreq.Header.Set("Connection", "Upgrade")
outreq.Header.Set("Upgrade", reqUpType)
}
if p.Rewrite != nil {
// Strip client-provided forwarding headers.
// The Rewrite func may use SetXForwarded to set new values
// for these or copy the previous values from the inbound request.
outreq.Header.Del("Forwarded")
outreq.Header.Del("X-Forwarded-For")
outreq.Header.Del("X-Forwarded-Host")
outreq.Header.Del("X-Forwarded-Proto")
// Remove unparsable query parameters from the outbound request.
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
pr := &ProxyRequest{
In: req,
Out: outreq,
}
p.Rewrite(pr)
outreq = pr.Out
}
if _, ok := outreq.Header["User-Agent"]; !ok {
// If the outbound request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent.
outreq.Header.Set("User-Agent", "")
}
trace := &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
h := rw.Header()
copyHeader(h, http.Header(header))
rw.WriteHeader(code)
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
for k := range h {
delete(h, k)
}
return nil
},
}
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
res, err := transport.RoundTrip(outreq)
if err != nil {
p.getErrorHandler()(rw, outreq, err)
return
}
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
if res.StatusCode == http.StatusSwitchingProtocols {
if !p.modifyResponse(rw, res, outreq) {
return
}
p.handleUpgradeResponse(rw, outreq, res)
return
}
removeHopByHopHeaders(res.Header)
if !p.modifyResponse(rw, res, outreq) {
return
}
copyHeader(rw.Header(), res.Header)
// The "Trailer" header isn't included in the Transport's response,
// at least for *http.Transport. Build it up from Trailer.
announcedTrailers := len(res.Trailer)
if announcedTrailers > 0 {
trailerKeys := make([]string, 0, len(res.Trailer))
for k := range res.Trailer {
trailerKeys = append(trailerKeys, k)
}
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
}
rw.WriteHeader(res.StatusCode)
err = p.copyResponse(rw, res.Body, p.flushInterval(res))
if err != nil {
defer res.Body.Close()
// Since we're streaming the response, if we run into an error all we can do
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
// on read error while copying body.
if !shouldPanicOnCopyError(req) {
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
return
}
panic(http.ErrAbortHandler)
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
if len(res.Trailer) > 0 {
// Force chunking if we saw a response trailer.
// This prevents net/http from calculating the length for short
// bodies and adding a Content-Length.
if fl, ok := rw.(http.Flusher); ok {
fl.Flush()
}
}
if len(res.Trailer) == announcedTrailers {
copyHeader(rw.Header(), res.Trailer)
return
}
for k, vv := range res.Trailer {
k = http.TrailerPrefix + k
for _, v := range vv {
rw.Header().Add(k, v)
}
}
}
var inOurTests bool // whether we're in our own tests
// shouldPanicOnCopyError reports whether the reverse proxy should
// panic with http.ErrAbortHandler. This is the right thing to do by
// default, but Go 1.10 and earlier did not, so existing unit tests
// weren't expecting panics. Only panic in our own tests, or when
// running under the HTTP server.
func shouldPanicOnCopyError(req *http.Request) bool {
if inOurTests {
// Our tests know to handle this panic.
return true
}
if req.Context().Value(http.ServerContextKey) != nil {
// We seem to be running under an HTTP server, so
// it'll recover the panic.
return true
}
// Otherwise act like Go 1.10 and earlier to not break
// existing tests.
return false
}
// removeHopByHopHeaders removes hop-by-hop headers.
func removeHopByHopHeaders(h http.Header) {
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
for _, f := range h["Connection"] {
for _, sf := range strings.Split(f, ",") {
if sf = textproto.TrimString(sf); sf != "" {
h.Del(sf)
}
}
}
// RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
// This behavior is superseded by the RFC 7230 Connection header, but
// preserve it for backwards compatibility.
for _, f := range hopHeaders {
h.Del(f)
}
}
// flushInterval returns the p.FlushInterval value, conditionally
// overriding its value for a specific request/response.
func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration {
resCT := res.Header.Get("Content-Type")
// For Server-Sent Events responses, flush immediately.
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" {
return -1 // negative means immediately
}
// We might have the case of streaming for which Content-Length might be unset.
if res.ContentLength == -1 {
return -1
}
return p.FlushInterval
}
func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error {
if flushInterval != 0 {
if wf, ok := dst.(writeFlusher); ok {
mlw := &maxLatencyWriter{
dst: wf,
latency: flushInterval,
}
defer mlw.stop()
// set up initial timer so headers get flushed even if body writes are delayed
mlw.flushPending = true
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
dst = mlw
}
}
var buf []byte
if p.BufferPool != nil {
buf = p.BufferPool.Get()
defer p.BufferPool.Put(buf)
}
_, err := p.copyBuffer(dst, src, buf)
return err
}
// copyBuffer returns any write errors or non-EOF read errors, and the amount
// of bytes written.
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
if len(buf) == 0 {
buf = make([]byte, 32*1024)
}
var written int64
for {
nr, rerr := src.Read(buf)
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
}
if nr > 0 {
nw, werr := dst.Write(buf[:nr])
if nw > 0 {
written += int64(nw)
}
if werr != nil {
return written, werr
}
if nr != nw {
return written, io.ErrShortWrite
}
}
if rerr != nil {
if rerr == io.EOF {
rerr = nil
}
return written, rerr
}
}
}
func (p *ReverseProxy) logf(format string, args ...any) {
if p.ErrorLog != nil {
p.ErrorLog.Printf(format, args...)
} else {
log.Printf(format, args...)
}
}
type writeFlusher interface {
io.Writer
http.Flusher
}
type maxLatencyWriter struct {
dst writeFlusher
latency time.Duration // non-zero; negative means to flush immediately
mu sync.Mutex // protects t, flushPending, and dst.Flush
t *time.Timer
flushPending bool
}
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
m.mu.Lock()
defer m.mu.Unlock()
n, err = m.dst.Write(p)
if m.latency < 0 {
m.dst.Flush()
return
}
if m.flushPending {
return
}
if m.t == nil {
m.t = time.AfterFunc(m.latency, m.delayedFlush)
} else {
m.t.Reset(m.latency)
}
m.flushPending = true
return
}
func (m *maxLatencyWriter) delayedFlush() {
m.mu.Lock()
defer m.mu.Unlock()
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
return
}
m.dst.Flush()
m.flushPending = false
}
func (m *maxLatencyWriter) stop() {
m.mu.Lock()
defer m.mu.Unlock()
m.flushPending = false
if m.t != nil {
m.t.Stop()
}
}
func upgradeType(h http.Header) string {
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
return ""
}
return h.Get("Upgrade")
}
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
reqUpType := upgradeType(req.Header)
resUpType := upgradeType(res.Header)
if !IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
}
if !EqualFold(reqUpType, resUpType) {
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
return
}
hj, ok := rw.(http.Hijacker)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
return
}
backConnCloseCh := make(chan bool)
go func() {
// Ensure that the cancellation of a request closes the backend.
// See issue https://golang.org/issue/35559.
select {
case <-req.Context().Done():
case <-backConnCloseCh:
}
backConn.Close()
}()
defer close(backConnCloseCh)
conn, brw, err := hj.Hijack()
if err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err))
return
}
defer conn.Close()
copyHeader(rw.Header(), res.Header)
res.Header = rw.Header()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
return
}
if err := brw.Flush(); err != nil {
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
return
}
errc := make(chan error, 1)
spc := switchProtocolCopier{user: conn, backend: backConn}
go spc.copyToBackend(errc)
go spc.copyFromBackend(errc)
<-errc
}
// switchProtocolCopier exists so goroutines proxying data back and
// forth have nice names in stacks.
type switchProtocolCopier struct {
user, backend io.ReadWriter
}
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
_, err := io.Copy(c.user, c.backend)
errc <- err
}
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
_, err := io.Copy(c.backend, c.user)
errc <- err
}
func cleanQueryParams(s string) string {
reencode := func(s string) string {
v, _ := url.ParseQuery(s)
return v.Encode()
}
for i := 0; i < len(s); {
switch s[i] {
case ';':
return reencode(s)
case '%':
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
return reencode(s)
}
i += 3
default:
i++
}
}
return s
}
func ishex(c byte) bool {
switch {
case '0' <= c && c <= '9':
return true
case 'a' <= c && c <= 'f':
return true
case 'A' <= c && c <= 'F':
return true
}
return false
}

@ -1,6 +1,7 @@
package httpreverse package httpreverse
import ( import (
"b612.me/apps/b612/httpreverse/rp"
"b612.me/starlog" "b612.me/starlog"
"bytes" "bytes"
"context" "context"
@ -10,7 +11,6 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/httputil"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -27,7 +27,6 @@ func (h *ReverseConfig) Run() error {
for key, proxy := range h.proxy { for key, proxy := range h.proxy {
h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) { h.httpmux.HandleFunc(key, func(writer http.ResponseWriter, request *http.Request) {
starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent")) starlog.Infof("<%s> Req Path:%s ListenAddr:%s UA:%s\n", h.Name, request.URL.Path, request.RemoteAddr, request.Header.Get("User-Agent"))
if !h.BasicAuth(writer, request) { if !h.BasicAuth(writer, request) {
h.SetResponseHeader(writer) h.SetResponseHeader(writer)
return return
@ -111,31 +110,27 @@ func (h *ReverseConfig) dialTLS(ctx context.Context, network, addr string) (net.
} }
func (h *ReverseConfig) init() error { func (h *ReverseConfig) init() error {
h.proxy = make(map[string]*httputil.ReverseProxy) h.proxy = make(map[string]*rp.ReverseProxy)
for key, val := range h.ReverseURL { for key, val := range h.ReverseURL {
h.proxy[key] = &httputil.ReverseProxy{ h.proxy[key] = &rp.ReverseProxy{
Transport: &http.Transport{DialTLSContext: h.dialTLS}, Transport: &http.Transport{DialTLSContext: h.dialTLS},
Director: func(req *http.Request) {
targetQuery := val.RawQuery
req.URL.Scheme = val.Scheme
if h.Host == "" {
req.Host = val.Host
} else {
req.Host = h.Host
}
req.URL.Host = val.Host
req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
},
} }
h.proxy[key].ModifyResponse = h.ModifyResponse() h.proxy[key].ModifyResponse = h.ModifyResponse()
originalDirector := h.proxy[key].Director
h.proxy[key].Director = func(req *http.Request) { h.proxy[key].Director = func(req *http.Request) {
originalDirector(req) targetQuery := val.RawQuery
req.URL.Scheme = val.Scheme
if h.Host == "" {
req.Host = val.Host
} else {
req.Host = h.Host
}
req.URL.Host = val.Host
req.URL.Path, req.URL.RawPath = joinURLPath(val, req.URL, key)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
h.ModifyRequest(req, val) h.ModifyRequest(req, val)
} }
} }
@ -181,13 +176,54 @@ func (h *ReverseConfig) ModifyResponse() func(*http.Response) error {
} }
} }
func (h *ReverseConfig) isInCIDR(ip string) bool {
nip := net.ParseIP(strings.TrimSpace(ip))
if nip == nil {
return false
}
for _, c := range h.CIDR {
if c.Contains(nip) {
return true
}
}
return false
}
func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) { func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
if h.XForwardMode == 1 { switch h.IPFilterMode {
case 1:
req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0]) req.Header.Set("X-Forwarded-For", strings.Split(req.RemoteAddr, ":")[0])
} else if h.XForwardMode == 2 { case 2:
xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",") xforward := strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ",")
xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0]) xforward = append(xforward, strings.Split(req.RemoteAddr, ":")[0])
req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", ")) req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", "))
case 3:
var lastForwardIP string
var xforward []string
if h.FilterMustKey != "" && req.Header.Get(h.FilterMustKey) != "" {
lastForwardIP = req.Header.Get(h.FilterMustKey)
xforward = []string{lastForwardIP}
} else {
for _, ip := range append(strings.Split(strings.TrimSpace(req.Header.Get("X-Forwarded-For")), ","), strings.Split(req.RemoteAddr, ":")[0]) {
ip = strings.TrimSpace(ip)
if !h.isInCIDR(ip) {
xforward = append(xforward, ip)
lastForwardIP = ip
}
}
}
if lastForwardIP == "" {
lastForwardIP = strings.Split(req.RemoteAddr, ":")[0]
}
if h.FilterXForward {
req.Header.Set("X-Forwarded-For", strings.Join(xforward, ", "))
}
if h.FilterRemoteAddr {
req.Header.Set("X-Real-IP", lastForwardIP)
}
if h.FilterSetKey != "" {
req.Header.Set(h.FilterSetKey, lastForwardIP)
}
} }
for _, v := range h.Cookie { for _, v := range h.Cookie {
req.AddCookie(&http.Cookie{ req.AddCookie(&http.Cookie{
@ -196,20 +232,6 @@ func (h *ReverseConfig) ModifyRequest(req *http.Request, remote *url.URL) {
Path: v[0], Path: v[0],
}) })
} }
host := h.Host
if host == "" {
host = remote.Host
}
targetQuery := remote.RawQuery
req.URL.Scheme = remote.Scheme
req.URL.Host = remote.Host
req.Host = host
req.URL.Path, req.URL.RawPath = joinURLPath(remote, req.URL)
if targetQuery == "" || req.URL.RawQuery == "" {
req.URL.RawQuery = targetQuery + req.URL.RawQuery
} else {
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
}
for _, v := range h.InHeader { for _, v := range h.InHeader {
req.Header.Set(v[0], v[1]) req.Header.Set(v[0], v[1])
} }
@ -281,12 +303,17 @@ func (h *ReverseConfig) filter(w http.ResponseWriter, r *http.Request) bool {
return true return true
} }
func joinURLPath(a, b *url.URL) (path, rawpath string) { func joinURLPath(a, b *url.URL, hpath string) (path, rawpath string) {
if hpath != "/" {
b.Path = strings.TrimPrefix(b.Path, hpath)
b.RawPath = strings.TrimPrefix(b.RawPath, hpath)
}
if a.RawPath == "" && b.RawPath == "" { if a.RawPath == "" && b.RawPath == "" {
return singleJoiningSlash(a.Path, b.Path), "" return singleJoiningSlash(a.Path, b.Path), ""
} }
// Same as singleJoiningSlash, but uses EscapedPath to determine // Same as singleJoiningSlash, but uses EscapedPath to determine
// whether a slash should be added // whether a slash should be added
apath := a.EscapedPath() apath := a.EscapedPath()
bpath := b.EscapedPath() bpath := b.EscapedPath()

@ -5,9 +5,12 @@ import (
"b612.me/staros" "b612.me/staros"
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
"os/signal" "os/signal"
"regexp"
"strconv"
"strings" "strings"
) )
@ -15,6 +18,7 @@ var s HttpServer
var daemon bool var daemon bool
var hooks string var hooks string
var speedlimit string
func init() { func init() {
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify") Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
@ -33,8 +37,55 @@ func init() {
Cmd.Flags().StringVar(&s.page401, "401", "", "自定义401页面地址") Cmd.Flags().StringVar(&s.page401, "401", "", "自定义401页面地址")
Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址") Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址")
Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址") 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().Bool("daeapplied", false, "")
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址")
Cmd.Flags().MarkHidden("daeapplied") 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{ var Cmd = &cobra.Command{
@ -42,6 +93,19 @@ var Cmd = &cobra.Command{
Short: "HTTP文件服务器(HTTP File Browser Server)", Short: "HTTP文件服务器(HTTP File Browser Server)",
Long: `HTTP文件服务器(HTTP File Browser Server)`, Long: `HTTP文件服务器(HTTP File Browser Server)`,
Run: func(cmd *cobra.Command, args []string) { 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 hooks != "" {
if !staros.Exists(hooks) { if !staros.Exists(hooks) {
starlog.Criticalln("hook file not exists") starlog.Criticalln("hook file not exists")

@ -0,0 +1,10 @@
package httpserver
import (
"net/http"
"testing"
)
func TestHttpServer(t *testing.T) {
http.ListenAndServe(":89", http.FileServer(http.Dir(`./`)))
}

@ -8,8 +8,10 @@ import (
"context" "context"
_ "embed" _ "embed"
"encoding/base64" "encoding/base64"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"html/template"
"io" "io"
"io/ioutil" "io/ioutil"
"math" "math"
@ -22,7 +24,7 @@ import (
"time" "time"
) )
var version = "2.1.0" var version = "2.1.0.b11"
type HttpServerCfgs func(cfg *HttpServerCfg) type HttpServerCfgs func(cfg *HttpServerCfg)
@ -44,6 +46,15 @@ type HttpServerCfg struct {
disableMIME bool disableMIME bool
ctx context.Context ctx context.Context
hooks []ServerHook 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 { type ServerHook struct {
@ -66,219 +77,8 @@ var jquery []byte
//go:embed upload.html //go:embed upload.html
var uploadPage []byte var uploadPage []byte
var htmlTitle string = `<!DOCTYPE html> //go:embed template.html
<html lang="zh_CN"> var templateHtml []byte
<head>
<meta charset="UTF-8">
<title>B612 Http Server %s</title>
<style>
* {
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 24px;
}
h1 {
text-align: center;
margin-bottom: 24px;
}
table {
width: 100%%;
border-collapse: collapse;
margin-top: 24px;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
position: relative;
}
th[data-sort]:before {
content: "▼";
display: inline-block;
height: 20px;
width: 20px;
margin-right: 10px;
vertical-align: middle;
position: absolute;
right: 0;
top: 50%%;
transform: translateY(-50%%);
opacity: 0.3;
transition: all 0.2s ease-in-out;
}
th[data-sort].asc:before {
content: "▲";
opacity: 1;
}
th:hover:before {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>B612 Http Server - %s</h1>
<hr /><pre><h2> %s </h2></pre>%s
<table>
<thead>
<tr>
<th data-sort="name" class="asc">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody>`
var htmlTail = ` </tbody>
</table>
<hr />
<pre>
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</pre>
</div>
<script>
function sortTable(th, n) {
const table = document.querySelector('table');
const rows = table.rows;
let switching = true;
let shouldSwitch = false;
let direction = 'asc';
let switchcount = 0;
while (switching) {
switching = false;
let i;
for (i = 1; i < rows.length - 1; i++) {
shouldSwitch = false;
const x = rows[i].getElementsByTagName("td")[n];
const y = rows[i + 1].getElementsByTagName("td")[n];
let xValue, yValue;
if (n === 2) { // size sorting
if (x.innerText==="-") {
xValue=-1;
}else{
xValue = parseInt(x.innerText.split(' ')[0]);
}
if (y.innerText==="-") {
yValue=-1;
}else{
yValue = parseInt(y.innerText.split(' ')[0]);
}
} else {
xValue = x.innerText.toLowerCase();
yValue = y.innerText.toLowerCase();
}
if (direction === 'asc') {
if (xValue > yValue) {
shouldSwitch = true;
break;
}
} else if (direction === 'desc') {
if (xValue < yValue) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
} else {
if (switchcount === 0 && direction === 'asc') {
direction = 'desc';
switching = true;
}
}
}
// update sort class
const ths = table.getElementsByTagName('th');
for (let i = 0; i < ths.length; i++) {
const currentTh = ths[i];
if (currentTh !== th) {
currentTh.classList.remove('asc');
} else {
currentTh.classList.toggle('asc');
}
}
// hide arrow on non-sorting columns
const sortableThs = table.querySelectorAll('thead th[data-sort]');
for (let i = 0; i < sortableThs.length; i++) {
const sortableTh = sortableThs[i];
if (sortableTh !== th) {
sortableTh.classList.remove('asc');
}
}
}
// add sorting event listener to thead
const ths = document.querySelectorAll('table th[data-sort]');
for (let i = 0; i < ths.length; i++) {
const th = ths[i];
th.addEventListener('click', () => {
const sortType = th.getAttribute('data-sort');
let columnIndex;
switch (sortType) {
case 'name':
columnIndex = 0;
break;
case 'modified':
columnIndex = 1;
break;
case 'size':
columnIndex = 2;
break;
case 'type':
columnIndex = 3;
break;
}
sortTable(th, columnIndex);
});
}
</script>
</body>
</html>
`
func WithHooks(hooks []ServerHook) HttpServerCfgs { func WithHooks(hooks []ServerHook) HttpServerCfgs {
return func(cfg *HttpServerCfg) { return func(cfg *HttpServerCfg) {
@ -332,7 +132,7 @@ func (h *HttpServer) Run(ctx context.Context) error {
server.Shutdown(ctx) server.Shutdown(ctx)
} }
}() }()
if h.logpath != "" { if h.logpath != "" && starlog.GetWriter() == nil {
starlog.SetLogFile(h.logpath, starlog.Std, true) starlog.SetLogFile(h.logpath, starlog.Std, true)
} }
netcards, err := net.Interfaces() netcards, err := net.Interfaces()
@ -400,7 +200,14 @@ func (h *HttpServer) Page403(w http.ResponseWriter) {
return return
} }
} }
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">403 Forbidden</h1><hr ></body></html>`)) w.Write([]byte(`
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>B612 HTTP SERVER</center>
</body>
</html>`))
} }
func (h *HttpServer) Page401(w http.ResponseWriter) { func (h *HttpServer) Page401(w http.ResponseWriter) {
@ -477,6 +284,37 @@ func (h *HttpServer) SetUpload(w http.ResponseWriter, r *http.Request, path stri
} }
return false return false
} }
func (h *HttpServer) debugMode(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
w.WriteHeader(200)
html := `<html><head><meta charset="utf-8"><title>B612 Http Server</title></head><body><h1 "style="text-align: center;">Debug Mode</h1><hr >%s</body></html>`
resp := "<h2>Url</h2>"
resp += "<p>" + r.Method + " " + r.URL.Path + "</p>"
resp += "<p> query " + r.URL.RawQuery + "</p>"
resp += "<p> fragment " + r.URL.Fragment + "</p>"
resp += "<p> FullUrl " + r.URL.String() + "</p>"
resp += "<h2>Query</h2>"
for k, v := range r.URL.Query() {
resp += fmt.Sprintf("<p>%s:%s</p>", k, v)
}
resp += "<h2>Header</h2>"
for key, val := range r.Header {
for _, v := range val {
resp += fmt.Sprintf("<p>%s:%s</p>", key, v)
}
}
resp += "<h2>Cookie</h2>"
for _, c := range r.Cookies() {
resp += fmt.Sprintf("<p>%s:%s</p>", c.Name, c.Value)
}
resp += "<h2>RemoteAddr</h2>"
resp += "<p>" + r.RemoteAddr + "</p>"
resp += "<h2>Proto</h2>"
resp += "<p>" + r.Proto + "</p>"
w.Write([]byte(fmt.Sprintf(html, resp)))
}
func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) { func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
log := starlog.Std.NewFlag() log := starlog.Std.NewFlag()
log.SetShowFuncName(false) log.SetShowFuncName(false)
@ -487,11 +325,16 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
return return
} }
path := r.URL.Path path := r.URL.Path
ua := r.Header.Get("User-Agent")
if h.httpDebug {
log.Infof("debug mode:%s %s From %s %s\n", r.Method, path, r.RemoteAddr, ua)
h.debugMode(w, r)
return
}
if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 { if h.uploadFolder != "" && path == "/recv" && len(r.URL.Query()["upload"]) != 0 {
h.uploadFile(w, r) h.uploadFile(w, r)
return return
} }
ua := r.Header.Get("User-Agent")
fullpath := filepath.Clean(filepath.Join(h.envPath, path)) fullpath := filepath.Clean(filepath.Join(h.envPath, path))
{ {
@ -530,32 +373,33 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err) log.Warningf("%s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds(), err)
return return
} }
log.Infof("%s %s From %s %s %.2fs \n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds()) log.Infof("%s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
default: default:
log.Errorf("Invalid %s %s From %s %s %.2fs %v\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds()) log.Errorf("Invalid %s %s From %s %s %.2fs\n", r.Method, path, r.RemoteAddr, ua, time.Since(now).Seconds())
return return
} }
} }
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) { func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
var rangeStart, rangeEnd int64 var rangeStart, rangeEnd int64 = -1, -1
rangeStart, rangeEnd = -1, -1 ranges := r.Header.Get("Range")
for k, v := range r.Header { if ranges == "" {
if strings.ToLower(k) == "range" { return rangeStart, rangeEnd
if strings.Contains(v[0], "bytes=") { }
v[0] = strings.Replace(v[0], "bytes=", "", -1) if !strings.Contains(ranges, "bytes=") {
} return rangeStart, rangeEnd
data := strings.Split(v[0], "-") }
if len(data) == 0 { ranges = strings.TrimPrefix(ranges, "bytes=")
break data := strings.Split(ranges, "-")
} if len(data) == 0 {
rangeStart, _ = strconv.ParseInt(data[0], 10, 64) return rangeStart, rangeEnd
if len(data) > 1 { }
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64) rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
} if len(data) > 1 {
//w.WriteHeader(206) //206 支持断点续传 rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
break }
} if rangeEnd == 0 {
rangeEnd = -1
} }
return rangeStart, rangeEnd return rangeStart, rangeEnd
} }
@ -591,16 +435,19 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
if _, ok := h.willHook(fullpath); ok { if _, ok := h.willHook(fullpath); ok {
return nil return nil
} }
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
start, end := h.CalcRange(r) start, end := h.CalcRange(r)
if start != -1 { if start != -1 {
if end == -1 { if end == -1 {
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size(), 10)+"/"+strconv.FormatInt(finfo.Size(), 10)) 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)) //w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
} else { } 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-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)) //w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
} }
} else {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
} }
} }
} }
@ -627,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 { func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if staros.IsFolder(fullpath) { 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) 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) 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 { func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
dir, err := ioutil.ReadDir(fullpath) dir, err := ioutil.ReadDir(fullpath)
if err != nil { if err != nil {
@ -649,14 +542,24 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
if h.uploadFolder != "" { if h.uploadFolder != "" {
upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>` upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>`
} }
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 != "/" { if r.URL.Path != "/" {
p := r.URL.Path p := r.URL.Path
if p[len(p)-1:] != "/" { if p[len(p)-1:] != "/" {
p += "/" p += "/"
} }
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`, fdatas = append(fdatas, FileData{
p+"..", "../", "-", "-", "上层文件夹"))) Attr: p + ".." + attr,
Name: "..",
Modified: "-",
Size: -1,
Type: "上层文件夹",
})
} }
if r.URL.Path == "/" { if r.URL.Path == "/" {
r.URL.Path = "" r.URL.Path = ""
@ -667,23 +570,104 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
for _, v := range dir { for _, v := range dir {
if v.Name() != "." || v.Name() != ".." { if v.Name() != "." || v.Name() != ".." {
if !v.IsDir() { if !v.IsDir() {
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`, fdatas = append(fdatas, FileData{
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())))) 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 { } else {
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`, fdatas = append(fdatas, FileData{
r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹"))) 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(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
jData, err := json.Marshal(fdatas)
if err != nil {
log.Errorf("Json Marshal Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
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(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
return nil 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 { func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) { if !staros.Exists(fullpath) {
h.Page404(w) h.Page404(w)
return errors.New("File Not Found! 404 ERROR") 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) //starlog.Debugln(r.Header)
startRange, endRange := h.CalcRange(r) startRange, endRange := h.CalcRange(r)
fp, err := os.Open(fullpath) fp, err := os.Open(fullpath)
@ -717,10 +701,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if !needCurl { if !needCurl {
w.WriteHeader(200) w.WriteHeader(200)
for { for {
buf := make([]byte, 1048576) buf := make([]byte, 16384)
n, err := fp.Read(buf) n, err := fp.Read(buf)
if n != 0 { if n != 0 {
ns, err := w.Write(buf[0:n]) speedControl(n)
ns, err := w.Write(buf[:n])
transferData += ns transferData += ns
if err != nil { if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@ -731,7 +716,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if err == io.EOF { if err == io.EOF {
break break
} }
log.Errorln("Read File %s Failed:%v\n", fullpath, err) log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err return err
} }
} }
@ -745,13 +730,14 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
return err return err
} }
b64 := base64.StdEncoding.EncodeToString(data) b64 := base64.StdEncoding.EncodeToString(data)
req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{ req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
"data": b64,
"ip": r.RemoteAddr,
}),
"POST", "POST",
starnet.WithBytes(starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
})),
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond))) 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.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(200) w.WriteHeader(200)
ns, err := w.Write(data) ns, err := w.Write(data)
@ -762,9 +748,10 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
} }
return nil return nil
} }
recvData := req.Body().Bytes()
w.WriteHeader(200) w.WriteHeader(200)
w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData))) w.Header().Set("Content-Length", strconv.Itoa(len(recvData)))
ns, err := w.Write(req.RecvData) ns, err := w.Write(recvData)
transferData += ns transferData += ns
if err != nil { if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err) log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@ -777,7 +764,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
fp.Seek(int64(startRange), 0) fp.Seek(int64(startRange), 0)
count := startRange count := startRange
for { for {
buf := make([]byte, 1048576) buf := make([]byte, 16384)
n, err := fp.Read(buf) n, err := fp.Read(buf)
if err != nil { if err != nil {
if err == io.EOF { if err == io.EOF {
@ -786,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) log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
return err return err
} }
speedControl(n)
if endRange == -1 { if endRange == -1 {
ns, err := w.Write(buf[0:n]) ns, err := w.Write(buf[:n])
transferData += ns transferData += ns
if err != nil { if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err) log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
@ -802,11 +790,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
writeNum = int(endRange - count + 1) writeNum = int(endRange - count + 1)
} }
ns, err := w.Write(buf[0:writeNum]) ns, err := w.Write(buf[0:writeNum])
transferData += ns
if err != nil { if err != nil {
log.Errorln("Transfer Error:", err) log.Errorln("Transfer Error:", err)
return err return err
} }
transferData += ns
count += int64(n) count += int64(n)
} }
} }

@ -0,0 +1,478 @@
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>B612 Http Server {{ .IdxTitle }}</title>
<style>
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
height: 100vh;
position: relative; /* 为了使背景图片固定 */
}
.background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
{{ .Photo }} /* 这里的背景图片 URL 是动态插入的 */
background-size: cover;
opacity: 0.5; /* 调整透明度 */
}
@media screen and (max-width: 768px) {
.background {
{{ .MobilePhoto }}
background-size: cover;
}
}
.container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
width: 50%;
max-width: 1920px;
margin: 0 auto;
padding: 24px;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
background: rgba(245, 245, 245, 0.5); /* 添加一个半透明背景层 */
}
h1 {
text-align: center;
margin-bottom: 24px;
}
.table-container {
-webkit-flex: 1;
-moz-flex: 1;
-ms-flex: 1;
flex: 1;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 24px;
table-layout: fixed; /* 确保表格单元格宽度固定 */
}
th, td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
white-space: normal; /* 允许换行 */
background: rgba(245, 245, 245, 0.5); /* 设置表格单元格背景为半透明 */
}
th[data-sort]:after {
content: "▲";
display: inline-block;
height: 20px;
width: 20px;
margin-left: 10px;
vertical-align: middle;
opacity: 0.3;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
-ms-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
th.asc:after {
content: "▲";
opacity: 1;
}
th.desc:after {
content: "▼";
opacity: 1;
}
th:hover:after {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
display: block;
max-width: 600px; /* 调整你想要的最大宽度 */
word-wrap: break-word; /* 确保文件名能够换行 */
}
.filename:hover {
color: #0056b3;
}
tr:hover {
background-color: rgba(241, 241, 241, 0.5); /* 设置鼠标悬停效果的背景为半透明 */
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
@media (orientation: portrait) and (max-width: 1024px) {
.container {
width: 60%;
}
}
@media (max-width: 400px) {
.container {
width: 80%;
}
}
thead th {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
top: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置表头背景为半透明 */
}
tbody td:first-child,
thead th:first-child {
position: -webkit-sticky;
position: -moz-sticky;
position: sticky;
left: 0;
z-index: 1;
background: rgba(245, 245, 245, 0.5); /* 设置第一列背景为半透明 */
}
hr {
width: 100%;
}
.search-container {
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
-moz-justify-content: center;
-ms-justify-content: center;
justify-content: center;
margin-bottom: 20px;
}
.search-container input {
padding: 10px;
width: 80%;
max-width: 400px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="background"></div>
<div class="container">
<h1>B612 Http Server - {{ .Version }}</h1>
<hr />
<h2>{{ .Idx }}</h2>
{{ .Upload }}
<div class="search-container">
<input type="text" id="search-box" placeholder="Search for a file..." />
</div>
<div class="table-container" id="table-container">
<table>
<thead>
<tr>
<th data-sort="name">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody id="table-content"><!-- table content is dynamically filled by JavaScript -->
</tbody>
</table>
</div>
<hr />
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</div>
<!-- Context menu for copying file details -->
<div id="context-menu" style="display:none; position: absolute; z-index: 1000; background: white; border: 1px solid #ccc; padding: 5px;">
<ul style="list-style: none; margin: 0; padding: 0;">
<li id="copy-filename" style="padding: 5px; cursor: pointer;">复制文件名</li>
<li id="copy-link" style="padding: 5px; cursor: pointer;">复制文件链接地址</li>
<li id="copy-size-bytes" style="padding: 5px; cursor: pointer;">复制文件大小(按字节)</li>
<li id="copy-size-display" style="padding: 5px; cursor: pointer;">复制文件大小(按显示)</li>
</ul>
</div>
<script>
// 初始化内容
var dataRows = {{ .Data }};
function loadTableContent(dataRows) {
var tableContent = document.getElementById('table-content');
if (!tableContent) return;
var html = '';
dataRows.forEach(function(row) {
html += '<tr>';
html += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
html += '<td>' + row.modified + '</td>';
html += '<td title="' + row.size + ' bytes">' + formatSize(row.size) + '</td>';
html += '<td class="filetype">' + row.type + '</td>';
html += '</tr>';
});
tableContent.innerHTML = html;
}
function renderRows(rows) {
var tableContent = document.getElementById('table-content');
var fragment = document.createDocumentFragment();
rows.forEach(function(row) {
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
});
tableContent.innerHTML = ''; // 清空现有内容
tableContent.appendChild(fragment);
}
function chunkedRenderRows() {
var chunkSize = 50;
var currentIndex = 0;
function renderChunk() {
var fragment = document.createDocumentFragment();
for (var i = currentIndex; i < currentIndex + chunkSize && i < dataRows.length; i++) {
var row = dataRows[i];
if (row.name === '..' && i !== 0) continue;
var tr = document.createElement('tr');
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
tr.innerHTML += '<td>' + row.modified + '</td>';
var formattedSize = formatSize(row.size);
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
fragment.appendChild(tr);
}
document.getElementById('table-content').appendChild(fragment);
currentIndex += chunkSize;
if (currentIndex < dataRows.length) {
requestAnimationFrame(renderChunk);
}
}
// 清空现有内容
document.getElementById('table-content').innerHTML = '';
// 开始分块渲染
requestAnimationFrame(renderChunk);
}
function parseSize(size) {
var units = { 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024 };
var match = size.match(/(\d+\.?\d*)\s*(KB|MB|GB)/i);
if (match) {
return parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1);
}
return parseInt(size, 10);
}
function formatSize(size) {
if (size < 0) return "-";
if (size < 1024) return size + ' B';
else if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB';
else if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB';
else return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
}
function sortTable(th, n, initial) {
var direction = th.classList.contains('asc') && !initial ? 'desc' : 'asc';
dataRows.sort(function(a, b) {
// 检查 'name' 字段以确保 '..' 始终在第一位
if (a.name === '..') return -1;
if (b.name === '..') return 1;
var x = Object.values(a)[n];
var y = Object.values(b)[n];
if (n === 1) { // modified column
// 解析日期字符串
x = new Date(a.modified);
y = new Date(b.modified);
} else if (n === 2) { // size column
x = a.size;
y = b.size;
}
return direction === 'asc' ?
(x < y ? -1 : x > y ? 1 : 0) :
(x > y ? -1 : x < y ? 1 : 0);
});
th.classList.toggle('asc', direction === 'asc' && !initial);
th.classList.toggle('desc', direction === 'desc' && !initial);
updateSortIcons(th);
renderRows(dataRows);
}
function updateSortIcons(th) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(header) {
if (header !== th) {
header.classList.remove('asc');
header.classList.remove('desc');
}
});
}
// 初次加载时按 Name 列进行升序排序
function initialSort() {
var nameHeader = document.querySelector('th[data-sort="name"]');
if (nameHeader) {
nameHeader.classList.add('asc'); // 直接设置为升序状态
sortTable(nameHeader, 0, true); // 传递一个参数表示这是初始排序
}
}
function isIE() {
return window.navigator.userAgent.indexOf("MSIE ") > -1 || navigator.userAgent.indexOf("Trident/") > -1;
}
if (!isIE()) {
document.addEventListener('DOMContentLoaded', function(event) {
var ths = document.querySelectorAll('thead th[data-sort]');
ths.forEach(function(th, i) {
th.addEventListener('click', function() {
sortTable(th, i);
});
});
// 使用 chunkedRenderRows 进行初始渲染, 然后进行默认排序
renderRows(dataRows);
// 初次加载时按 Name 列进行升序排序
initialSort();
// 初始化右键菜单
initializeContextMenu();
});
// 处理搜索框输入事件
document.getElementById('search-box').addEventListener('input', function (e) {
var searchText = e.target.value.toLowerCase();
var filteredRows = dataRows.filter(function (row) {
return row.name.toLowerCase().includes(searchText);
});
renderRows(filteredRows);
});
}else{
loadTableContent(dataRows);
// 禁用搜索框
var searchBox = document.getElementById('search-box');
if (searchBox) {
searchBox.disabled = true;
searchBox.placeholder = "IE浏览器不受支持大部分功能受限";
}
}
function initializeContextMenu() {
var contextMenu = document.getElementById('context-menu');
document.addEventListener('contextmenu', function(e) {
if (e.target.classList.contains('filetype')) {
e.preventDefault();
var row = e.target.closest('tr');
var fileNameToCopy = row.querySelector('.filename').textContent;
var linkToCopy = row.querySelector('.filename').href;
var byteSizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').getAttribute('title') : '';
var displaySizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').textContent : '';
contextMenu.style.display = 'block';
contextMenu.style.top = e.pageY + 'px';
contextMenu.style.left = e.pageX + 'px';
contextMenu.setAttribute('data-filename', fileNameToCopy);
contextMenu.setAttribute('data-link', linkToCopy);
contextMenu.setAttribute('data-size-bytes', byteSizeToCopy);
contextMenu.setAttribute('data-size-display', displaySizeToCopy);
} else {
contextMenu.style.display = 'none';
}
});
document.addEventListener('click', function() {
contextMenu.style.display = 'none';
});
document.getElementById('copy-filename').addEventListener('click', function() {
var fileName = contextMenu.getAttribute('data-filename');
if (fileName) {
copyToClipboard(fileName);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-link').addEventListener('click', function() {
var fileLink = contextMenu.getAttribute('data-link');
if (fileLink) {
copyToClipboard(fileLink);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-bytes').addEventListener('click', function() {
var fileSizeBytes = contextMenu.getAttribute('data-size-bytes');
if (fileSizeBytes) {
copyToClipboard(fileSizeBytes);
}
contextMenu.style.display = 'none';
});
document.getElementById('copy-size-display').addEventListener('click', function() {
var fileSizeDisplay = contextMenu.getAttribute('data-size-display');
if (fileSizeDisplay) {
copyToClipboard(fileSizeDisplay);
}
contextMenu.style.display = 'none';
});
}
function copyToClipboard(text) {
var textarea = document.createElement('textarea');
textarea.value = text;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
</script>
</body>
</html>

@ -5,6 +5,7 @@ import (
"b612.me/starlog" "b612.me/starlog"
"b612.me/staros" "b612.me/staros"
"crypto/ecdsa" "crypto/ecdsa"
"crypto/ed25519"
"crypto/rsa" "crypto/rsa"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"os" "os"
@ -138,6 +139,12 @@ var CmdPub = &cobra.Command{
case *ecdsa.PrivateKey: case *ecdsa.PrivateKey:
starlog.Infoln("found ecdsa private key") starlog.Infoln("found ecdsa private key")
pub = n.Public() 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: default:
starlog.Errorln("unknown private key type") starlog.Errorln("unknown private key type")
os.Exit(1) os.Exit(1)

@ -15,6 +15,7 @@ import (
"encoding/pem" "encoding/pem"
"errors" "errors"
"fmt" "fmt"
"golang.org/x/crypto/ssh"
"io" "io"
"math/big" "math/big"
"os" "os"
@ -50,10 +51,13 @@ func (k *KeyGen) Gen() error {
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".pub")) { if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".pub")) {
return errors.New("ssh pub file exists") return errors.New("ssh pub file exists")
} }
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".openssh")) {
return errors.New("ssh priv file exists")
}
if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".key.pub")) { if !k.Force && staros.Exists(filepath.Join(k.Outfolder, k.Prefix+".key.pub")) {
return errors.New("pub file exists") return errors.New("pub file exists")
} }
var sshPubByte, keyPubByte, keyPrivByte, Crt []byte var sshPubByte, sshPrivByte, keyPubByte, keyPrivByte, Crt []byte
var priv, pub any var priv, pub any
var err error var err error
@ -101,10 +105,27 @@ func (k *KeyGen) Gen() error {
if err != nil { if err != nil {
return err return err
} }
var block *pem.Block
if k.Encrypt != "" {
block, err = ssh.MarshalPrivateKey(priv, "")
} else {
block, err = ssh.MarshalPrivateKeyWithPassphrase(priv, "", []byte(k.Encrypt))
}
if err != nil {
return err
}
sshPrivByte = pem.EncodeToMemory(block)
if err != nil {
return err
}
_, Crt, err = k.GenerateCert(priv) _, Crt, err = k.GenerateCert(priv)
if err != nil { if err != nil {
return err return err
} }
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".openssh"), sshPrivByte, 0644)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".crt"), Crt, 0644) err = os.WriteFile(filepath.Join(k.Outfolder, k.Prefix+".crt"), Crt, 0644)
if err != nil { if err != nil {
return err return err

@ -21,6 +21,7 @@ import (
"b612.me/apps/b612/image" "b612.me/apps/b612/image"
"b612.me/apps/b612/keygen" "b612.me/apps/b612/keygen"
"b612.me/apps/b612/merge" "b612.me/apps/b612/merge"
"b612.me/apps/b612/mget"
"b612.me/apps/b612/net" "b612.me/apps/b612/net"
"b612.me/apps/b612/rmt" "b612.me/apps/b612/rmt"
"b612.me/apps/b612/search" "b612.me/apps/b612/search"
@ -41,7 +42,7 @@ import (
var cmdRoot = &cobra.Command{ var cmdRoot = &cobra.Command{
Use: "b612", Use: "b612",
Version: "2.1.0.beta.8", Version: "2.1.0.beta.11",
} }
func init() { func init() {
@ -50,7 +51,7 @@ func init() {
base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd, 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, 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, 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() { func main() {

@ -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)
}
}

@ -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)
}
}
}
}

@ -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
}

@ -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())
}

@ -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))
}

@ -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<filename>[^;"]+)`)
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
}

@ -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
}

@ -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"
}

@ -17,6 +17,9 @@ func init() {
Cmd.AddCommand(netforward.CmdNetforward) Cmd.AddCommand(netforward.CmdNetforward)
} }
var nattestc NatTesterClient
var nattests NatTesterServer
var natc NatClient var natc NatClient
var nats NatServer var nats NatServer
@ -26,24 +29,28 @@ var maxHop int
var disableIpInfo bool var disableIpInfo bool
var bindAddr string var bindAddr string
var hideIncorrect bool var hideIncorrect bool
var natt NatThroughs
var scanip ScanIP
var scanport ScanPort
func init() { func init() {
CmdNatClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address") CmdNatPClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address")
CmdNatClient.Flags().StringVarP(&natc.CmdTarget, "server", "s", "", "nat server command address") CmdNatPClient.Flags().StringVarP(&natc.CmdTarget, "server", "s", "", "nat server command address")
CmdNatClient.Flags().StringVarP(&natc.Passwd, "passwd", "p", "", "password") CmdNatPClient.Flags().StringVarP(&natc.Passwd, "passwd", "p", "", "password")
CmdNatClient.Flags().BoolVarP(&natc.enableTCP, "enable-tcp", "T", true, "enable tcp forward") CmdNatPClient.Flags().BoolVarP(&natc.enableTCP, "enable-tcp", "T", true, "enable tcp forward")
CmdNatClient.Flags().BoolVarP(&natc.enableUDP, "enable-udp", "U", true, "enable udp forward") CmdNatPClient.Flags().BoolVarP(&natc.enableUDP, "enable-udp", "U", true, "enable udp forward")
CmdNatClient.Flags().IntVarP(&natc.DialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") CmdNatPClient.Flags().IntVarP(&natc.DialTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNatClient.Flags().IntVarP(&natc.UdpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") CmdNatPClient.Flags().IntVarP(&natc.UdpTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds")
Cmd.AddCommand(CmdNatClient) Cmd.AddCommand(CmdNatPClient)
CmdNatServer.Flags().StringVarP(&nats.ListenAddr, "listen", "l", "", "listen address") CmdNatPServer.Flags().StringVarP(&nats.ListenAddr, "listen", "l", "", "listen address")
CmdNatServer.Flags().StringVarP(&nats.Passwd, "passwd", "p", "", "password") CmdNatPServer.Flags().StringVarP(&nats.Passwd, "passwd", "p", "", "password")
CmdNatServer.Flags().Int64VarP(&nats.UDPTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds") CmdNatPServer.Flags().Int64VarP(&nats.UDPTimeout, "udp-timeout", "D", 60000, "udp connection timeout milliseconds")
CmdNatServer.Flags().Int64VarP(&nats.NetTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds") CmdNatPServer.Flags().Int64VarP(&nats.NetTimeout, "dial-timeout", "d", 10000, "dial timeout milliseconds")
CmdNatServer.Flags().BoolVarP(&nats.enableTCP, "enable-tcp", "T", true, "enable tcp forward") CmdNatPServer.Flags().BoolVarP(&nats.enableTCP, "enable-tcp", "T", true, "enable tcp forward")
CmdNatServer.Flags().BoolVarP(&nats.enableUDP, "enable-udp", "U", true, "enable udp forward") CmdNatPServer.Flags().BoolVarP(&nats.enableUDP, "enable-udp", "U", true, "enable udp forward")
Cmd.AddCommand(CmdNatServer) Cmd.AddCommand(CmdNatPServer)
CmdNetTrace.Flags().StringVarP(&dns, "dns", "d", "", "自定义dns服务器") CmdNetTrace.Flags().StringVarP(&dns, "dns", "d", "", "自定义dns服务器")
CmdNetTrace.Flags().StringVarP(&ipinfoaddr, "ipinfo", "i", "https://ip.b612.me/{ip}/detail", "自定义ip信息查询地址") CmdNetTrace.Flags().StringVarP(&ipinfoaddr, "ipinfo", "i", "https://ip.b612.me/{ip}/detail", "自定义ip信息查询地址")
@ -54,10 +61,52 @@ func init() {
CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点") CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点")
Cmd.AddCommand(CmdNetTrace, cmdSSHJar) Cmd.AddCommand(CmdNetTrace, cmdSSHJar)
CmdNatClient.Flags().IntVarP(&nattestc.RetryTime, "retry", "r", 2, "重试次数")
CmdNatClient.Flags().IntVarP(&nattestc.Timeout, "timeout", "t", 2, "超时时间")
CmdNatClient.Flags().StringSliceVarP(&nattestc.dns, "dns", "d", nil, "自定义dns服务器")
Cmd.AddCommand(CmdNatClient)
CmdNatServer.Flags().StringVarP(&nattests.MainIP, "main-ip", "m", "10.0.0.5", "主IP")
CmdNatServer.Flags().StringVarP(&nattests.AltIP, "alt-ip", "a", "10.0.0.2", "备用IP")
CmdNatServer.Flags().StringVarP(&nattests.MainPort, "main-port", "M", "41127", "主端口")
CmdNatServer.Flags().StringVarP(&nattests.AltPort, "alt-port", "A", "46610", "备用端口")
CmdNatServer.Flags().StringVarP(&nattests.LogPath, "log", "l", "", "日志文件")
Cmd.AddCommand(CmdNatServer)
CmdNatThrough.Flags().StringVarP(&natt.STUN, "stun", "s", "turn.b612.me:3478", "stun服务器")
CmdNatThrough.Flags().StringVarP(&natt.Remote, "remote", "r", "baidu.com:80", "keepalive地址")
CmdNatThrough.Flags().IntVarP(&natt.KeepAlivePeriod, "keepalive-period", "p", 30, "KeepAlive周期")
CmdNatThrough.Flags().IntVarP(&natt.KeepAliveIdel, "keepalive-idel", "i", 30, "KeepAlive空闲时间")
CmdNatThrough.Flags().IntVarP(&natt.KeepAliveCount, "keepalive-count", "c", 5, "KeepAlive次数")
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地址")
CmdScanIP.Flags().IntVarP(&scanip.Port, "port", "p", 80, "TCP模式扫描端口")
CmdScanIP.Flags().IntVarP(&scanip.Timeout, "timeout", "t", 2000, "超时时间")
CmdScanIP.Flags().IntVarP(&scanip.Threads, "threads", "m", 100, "最大线程数")
CmdScanIP.Flags().StringVarP(&scanip.Log, "log", "l", "", "日志文件地址")
CmdScanIP.Flags().StringVarP(&scanip.Mask, "mask", "M", "", "掩码")
CmdScanIP.Flags().IntVarP(&scanip.CIDR, "cidr", "c", 24, "CIDR")
CmdScanIP.Flags().StringVarP(&scanip.ScanType, "type", "T", "icmp", "扫描类型")
CmdScanIP.Flags().IntVarP(&scanip.Retry, "retry", "r", 2, "重试次数")
CmdScanIP.Flags().BoolVarP(&scanip.WithHostname, "with-hostname", "H", false, "显示主机名")
Cmd.AddCommand(CmdScanIP)
CmdScanPort.Flags().StringVarP(&scanport.Host, "ip", "i", "", "扫描IP地址")
CmdScanPort.Flags().IntVarP(&scanport.Timeout, "timeout", "t", 2000, "超时时间")
CmdScanPort.Flags().IntVarP(&scanport.Threads, "threads", "m", 100, "最大线程数")
CmdScanPort.Flags().StringVarP(&scanport.Log, "log", "l", "", "日志文件地址")
CmdScanPort.Flags().IntVarP(&scanport.Retry, "retry", "r", 2, "重试次数")
Cmd.AddCommand(CmdScanPort)
} }
var CmdNatClient = &cobra.Command{ var CmdNatPClient = &cobra.Command{
Use: "natc", Use: "natpc",
Short: "nat穿透客户端", Short: "nat穿透客户端",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if natc.ServiceTarget == "" || natc.CmdTarget == "" { if natc.ServiceTarget == "" || natc.CmdTarget == "" {
@ -68,8 +117,8 @@ var CmdNatClient = &cobra.Command{
}, },
} }
var CmdNatServer = &cobra.Command{ var CmdNatPServer = &cobra.Command{
Use: "nats", Use: "natps",
Short: "nat穿透服务端", Short: "nat穿透服务端",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
@ -77,6 +126,44 @@ var CmdNatServer = &cobra.Command{
}, },
} }
var CmdNatClient = &cobra.Command{
Use: "natc",
Short: "nat类型测试工具 - 客户端",
Long: "基于RFC3489的nat类型测试工具客户端",
Run: func(cmd *cobra.Command, args []string) {
if nattestc.dns != nil {
UseCustomeDNS(nattestc.dns)
for _, v := range nattestc.dns {
starlog.Infoln("使用自定义DNS:", v)
}
}
natserver := "nat.b612.me"
if len(args) == 0 {
starlog.Infoln("使用默认NAT测试服务器nat.b612.me")
} else {
natserver = args[0]
starlog.Infoln("使用NAT测试服务器", natserver)
}
res, err := nattestc.ServeAndRun(natserver)
if err != nil {
starlog.Errorln("测试NAT类型失败", err)
return
}
fmt.Println("-----------------")
fmt.Println("")
starlog.Green("您的NAT类型为%v - %v\nswitch可能显示NAT类型为%v\n%v", res.Code, res.RFC3489, res.NintendoSwitch, res.Desc)
},
}
var CmdNatServer = &cobra.Command{
Use: "nats",
Short: "nat类型测试工具 - 服务端",
Long: "基于RFC3489的nat类型测试工具服务端",
Run: func(cmd *cobra.Command, args []string) {
nattests.Run()
},
}
var CmdNetTrace = &cobra.Command{ var CmdNetTrace = &cobra.Command{
Use: "trace", Use: "trace",
Short: "网络路径追踪", Short: "网络路径追踪",
@ -95,3 +182,56 @@ var CmdNetTrace = &cobra.Command{
} }
}, },
} }
var CmdNatThrough = &cobra.Command{
Use: "natt",
Short: "nat tcp直接穿透",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
starlog.Errorf("请按照如下格式输入:\n [远端地址] 或 [本地地址|远端地址] 或 [名字@本地地址|远端地址] 或 [协议|名字|本地地址|远端地址]\n")
return
}
if err := natt.Parse(args); err != nil {
starlog.Errorln(err)
return
}
if err := natt.Run(); err != nil {
starlog.Errorln(err)
}
},
}
var CmdScanIP = &cobra.Command{
Use: "scanip",
Short: "扫描IP",
Run: func(cmd *cobra.Command, args []string) {
if scanip.Host == "" {
cmd.Help()
return
}
if scanip.ScanType == "icmp" {
scanip.ICMP()
} else {
scanip.TCP(scanip.Port)
}
},
}
var CmdScanPort = &cobra.Command{
Use: "scanport",
Short: "扫描端口",
Run: func(cmd *cobra.Command, args []string) {
if scanport.Host == "" {
cmd.Help()
return
}
if len(args) != 1 {
starlog.Errorln("请指定端口范围,如80,443,1000-2000")
}
if err := scanport.Parse(args[0]); err != nil {
starlog.Errorln(err)
return
}
scanport.Run()
},
}

@ -0,0 +1,274 @@
package net
import (
"b612.me/starlog"
"context"
"encoding/json"
"fmt"
"net"
"strings"
"time"
)
type NatTesterClient struct {
MainPort string `json:"mainport"`
AltPort string `json:"altport"`
MainIP string `json:"mainip"`
AltIP string `json:"altip"`
RetryTime int `json:"retrytime"`
Timeout int `json:"timeout"`
dns []string
ch chan Message
}
type Message struct {
Success bool
Cmd string
Msg string
Address string
}
type NatType struct {
Code string
RFC3489 string
NintendoSwitch string
Desc string
}
func (n *NatTesterClient) GetMsg() Message {
select {
case msg := <-n.ch:
return msg
case <-time.After(time.Second * time.Duration(n.Timeout)):
return Message{Success: false, Msg: "timeout"}
}
}
func (n *NatTesterClient) RecvMsg(c *net.UDPConn) {
for {
buf := make([]byte, 1024)
num, r, e := c.ReadFromUDP(buf)
if e != nil {
return
}
go n.Analyse(r, strings.Split(string(buf[:num]), "::"))
}
}
func (n *NatTesterClient) Analyse(r *net.UDPAddr, cmds []string) {
switch cmds[0] {
case "ip":
if len(cmds) == 2 {
n.ch <- Message{Success: true, Cmd: "ip", Msg: cmds[1], Address: r.String()}
}
case "stage1":
n.ch <- Message{Success: true, Cmd: "stage1", Msg: "stage1", Address: r.String()}
case "stage2":
n.ch <- Message{Success: true, Cmd: "stage2", Msg: "stage2", Address: r.String()}
case "stage3":
n.ch <- Message{Success: true, Cmd: "stage3", Msg: "stage3", Address: r.String()}
}
}
func (n *NatTesterClient) Run() (NatType, error) {
var firstAddr, secondAddr string
var natTypeMap = map[string]NatType{
"Block": {Code: "Block", RFC3489: "Block", NintendoSwitch: "NAT F", Desc: "您的网络似乎禁止了UDP无法连接到外部网络"},
"Open": {Code: "Open", RFC3489: "Open", NintendoSwitch: "Open", Desc: "您的网络为开放网络,拥有最好的上网体验"},
"OpenBlock": {Code: "OpenBlock", RFC3489: "Symmetric Firewall", NintendoSwitch: "NAT F", Desc: "您的网络虽然时开放网络,但是存在对称型防火墙,可能会遇到严重的连接问题"},
"NAT1": {Code: "NAT1", RFC3489: "Full Cone", NintendoSwitch: "NAT A", Desc: "您的NAT类型为全锥形NAT拥有最好的NAT体验"},
"NAT2": {Code: "NAT2", RFC3489: "Address Restricted Cone", NintendoSwitch: "NAT B", Desc: "您的NAT类型为地址限制锥形NAT拥有良好的NAT体验"},
"NAT3": {Code: "NAT3", RFC3489: "Port Restricted Cone", NintendoSwitch: "NAT B/C", Desc: "您的NAT类型为端口限制锥形NAT可能会遇到一些连接问题"},
"NAT4": {Code: "NAT4", RFC3489: "Symmetric", NintendoSwitch: "NAT C/D", Desc: "您的NAT类型为对称NAT可能会遇到严重的连接问题"},
"Unknown": {Code: "Unknown", RFC3489: "Unknown", NintendoSwitch: "Unknown", Desc: "无法确定您的NAT类型"},
}
tmp, err := net.Dial("udp", n.MainIP+":80")
if err != nil {
return NatType{}, err
}
curIp := tmp.LocalAddr().(*net.UDPAddr).IP.String()
starlog.Infof("Current Output IP: %s\n", curIp)
localAddr, err := net.ResolveUDPAddr("udp", curIp+":0")
if err != nil {
return NatType{}, err
}
conn, err := net.ListenUDP("udp", localAddr)
if err != nil {
return NatType{}, err
}
starlog.Infof("Listening on %s\n", conn.LocalAddr().String())
defer conn.Close()
go n.RecvMsg(conn)
n.ch = make(chan Message)
defer close(n.ch)
succ := false
mainAddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.MainPort)
if err != nil {
return NatType{}, err
}
altAddr, err := net.ResolveUDPAddr("udp", n.AltIP+":"+n.AltPort)
if err != nil {
return NatType{}, err
}
starlog.Noticef("Getting IP from NatServer Main\n")
for i := 0; i < n.RetryTime; i++ {
_, err = conn.WriteToUDP([]byte("ip"), mainAddr)
if err != nil {
starlog.Errorln("failed to get main ip,retrying:" + err.Error())
continue
}
msg := n.GetMsg()
if msg.Success && msg.Cmd == "ip" {
starlog.Noticef("Remote IP: %s\n", msg.Address)
starlog.Infof("Current IP: %s\n", msg.Msg)
succ = true
firstAddr = msg.Msg
break
}
starlog.Errorln("failed to get main ip,retrying:" + msg.Msg)
}
if !succ {
return NatType{}, fmt.Errorf("failed to get current ip")
}
{
starlog.Noticef("Start NAT1 Test\n")
succ = false
for i := 0; i < n.RetryTime; i++ {
_, err = conn.WriteToUDP([]byte("startnat1"), mainAddr)
if err != nil {
starlog.Errorln("failed to send nat1 test data,retrying:" + err.Error())
continue
}
msg := n.GetMsg()
if msg.Success && msg.Cmd == "stage1" {
starlog.Noticef("Recv Nat1 Data From Remote IP: %s\n", msg.Address)
succ = true
break
}
starlog.Errorln("failed to recv Nat1 data,retrying:" + msg.Msg)
}
if succ {
if strings.Split(firstAddr, ":")[0] == curIp {
starlog.Infof("Current NAT Type: Open\n")
return natTypeMap["Open"], nil
}
starlog.Infof("Current NAT Type: NAT1\n")
return natTypeMap["NAT1"], nil
} else {
if strings.Split(firstAddr, ":")[0] == curIp {
starlog.Infof("Current NAT Type: OpenBlock\n")
return natTypeMap["OpenBlock"], nil
}
}
}
{
starlog.Noticef("Start NAT2 Test\n")
succ = false
for i := 0; i < n.RetryTime; i++ {
_, err = conn.WriteToUDP([]byte("startnat2"), mainAddr)
if err != nil {
starlog.Errorln("failed to send nat2 test data,retrying:" + err.Error())
continue
}
msg := n.GetMsg()
if msg.Success && msg.Cmd == "stage2" {
starlog.Noticef("Recv Nat2 Data From Remote IP: %s\n", msg.Address)
succ = true
break
}
starlog.Errorln("failed to recv Nat2 data,retrying:" + msg.Msg)
}
if succ {
starlog.Infof("Current NAT Type: NAT2\n")
return natTypeMap["NAT2"], nil
}
}
{
starlog.Noticef("Start NAT3 Test\n")
succ = false
for i := 0; i < n.RetryTime; i++ {
_, err = conn.WriteToUDP([]byte("startnat3"), mainAddr)
if err != nil {
starlog.Errorln("failed to send nat1 test data,retrying:" + err.Error())
continue
}
msg := n.GetMsg()
if msg.Success && msg.Cmd == "stage3" {
starlog.Noticef("Recv Nat1 Data From Remote IP: %s\n", msg.Address)
succ = true
break
}
starlog.Errorln("failed to recv Nat3 data,retrying:" + msg.Msg)
}
if !succ {
starlog.Errorf("Failed to get NAT Type\n")
return natTypeMap["Unknown"], fmt.Errorf("failed to get nat type")
}
}
succ = false
starlog.Noticef("Gettting IP from NatServer Alt\n")
for i := 0; i < n.RetryTime; i++ {
_, err = conn.WriteToUDP([]byte("ip"), altAddr)
if err != nil {
starlog.Errorln("failed to get alt ip,retrying:" + err.Error())
continue
}
msg := n.GetMsg()
if msg.Success && msg.Cmd == "ip" {
starlog.Noticef("Remote IP: %s\n", msg.Address)
starlog.Infof("Current IP: %s\n", msg.Msg)
succ = true
secondAddr = msg.Msg
break
}
starlog.Errorln("failed to get alt ip,retrying:" + msg.Msg)
}
if !succ {
starlog.Errorf("Failed to get NAT Type\n")
return natTypeMap["Unknown"], fmt.Errorf("failed to get nat type")
}
starlog.Debugf("First IP: %s, Second IP: %s\n", firstAddr, secondAddr)
starlog.Debugf("Listening on %s\n", conn.LocalAddr().String())
if firstAddr == secondAddr {
starlog.Infof("Current NAT Type: NAT3\n")
return natTypeMap["NAT3"], nil
}
starlog.Infof("Current NAT Type: NAT4\n")
return natTypeMap["NAT4"], nil
}
func (n *NatTesterClient) ServeAndRun(addr string) (NatType, error) {
starlog.SetShowFlag(false)
starlog.SetShowFuncName(false)
starlog.SetShowOriginFile(false)
data, err := net.LookupTXT(addr)
if err != nil {
return NatType{}, err
}
if len(data) == 0 {
return NatType{}, fmt.Errorf("no data found")
}
err = json.Unmarshal([]byte(data[0]), n)
if err != nil {
return NatType{}, err
}
starlog.Debugf("MainIP: %s, MainPort: %s, AltIP: %s, AltPort: %s\n", n.MainIP, n.MainPort, n.AltIP, n.AltPort)
return n.Run()
}
func UseCustomeDNS(dns []string) {
resolver := net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (conn net.Conn, err error) {
for _, addr := range dns {
if conn, err = net.Dial("udp", addr+":53"); err != nil {
continue
} else {
return conn, nil
}
}
return
},
}
net.DefaultResolver = &resolver
}

@ -0,0 +1,124 @@
package net
import (
"b612.me/starlog"
"context"
"fmt"
"net"
"strings"
"sync/atomic"
)
type NatTesterServer struct {
MainPort string
AltPort string
MainIP string
AltIP string
LogPath string
stopCtx context.Context
stopFn context.CancelFunc
maina *net.UDPConn
mainb *net.UDPConn
alt *net.UDPConn
running int32
}
func (n *NatTesterServer) Run() error {
if atomic.LoadInt32(&n.running) > 0 {
starlog.Errorln("already running")
return fmt.Errorf("already running")
}
atomic.StoreInt32(&n.running, 1)
defer atomic.StoreInt32(&n.running, 0)
if n.LogPath != "" {
starlog.SetLogFile(n.LogPath, starlog.Std, true)
starlog.Infof("Log file set to %s\n", n.LogPath)
}
starlog.Infof("MainPort: %s\n", n.MainPort)
starlog.Infof("AltPort: %s\n", n.AltPort)
tmp, err := net.Dial("udp", "8.8.8.8:53")
if err != nil {
return err
}
starlog.Infof("Current Output IP: %s\n", tmp.LocalAddr().(*net.UDPAddr).IP.String())
tmp.Close()
n.stopCtx, n.stopFn = context.WithCancel(context.Background())
mainaaddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.MainPort)
if err != nil {
return err
}
mainbaddr, err := net.ResolveUDPAddr("udp", n.MainIP+":"+n.AltPort)
if err != nil {
return err
}
n.maina, err = net.ListenUDP("udp", mainaaddr)
if err != nil {
return err
}
starlog.Infof("UDP MainIP:MainPort Listening on %s\n", n.maina.LocalAddr().String())
n.mainb, err = net.ListenUDP("udp", mainbaddr)
if err != nil {
return err
}
starlog.Infof("UDP MainIP:AltPort Listening on %s\n", n.mainb.LocalAddr().String())
altaddr, err := net.ResolveUDPAddr("udp", n.AltIP+":"+n.AltPort)
if err != nil {
return err
}
n.alt, err = net.ListenUDP("udp", altaddr)
if err != nil {
return err
}
starlog.Infof("UDP AltIP:AltPort Listening on %s\n", n.alt.LocalAddr().String())
go func() {
for {
select {
case <-n.stopCtx.Done():
starlog.Infoln("Stopping,Reason: Context Done")
return
default:
}
buf := make([]byte, 1024)
num, r, e := n.alt.ReadFromUDP(buf)
if e != nil {
continue
}
go n.Analyse(n.alt, r, strings.Split(string(buf[:num]), "::"))
}
}()
for {
select {
case <-n.stopCtx.Done():
starlog.Infoln("Stopping,Reason: Context Done")
n.maina.Close()
n.mainb.Close()
n.alt.Close()
return nil
default:
}
buf := make([]byte, 1024)
num, r, e := n.maina.ReadFromUDP(buf)
if e != nil {
continue
}
go n.Analyse(n.maina, r, strings.Split(string(buf[:num]), "::"))
}
}
func (n *NatTesterServer) Analyse(c *net.UDPConn, r *net.UDPAddr, cmds []string) error {
switch cmds[0] {
case "ip":
c.WriteToUDP([]byte("ip::"+r.String()), r)
starlog.Infof("Recv IP Request from %s,Local: %s\n", r.String(), c.LocalAddr().String())
case "startnat1":
n.alt.WriteToUDP([]byte("stage1"), r)
starlog.Infof("Start NAT1 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.alt.LocalAddr().String())
case "startnat2":
n.mainb.WriteToUDP([]byte("stage2"), r)
starlog.Infof("Start NAT2 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.mainb.LocalAddr().String())
case "startnat3":
n.maina.WriteToUDP([]byte("stage3"), r)
starlog.Infof("Start NAT3 Test from %s,Recv Local:%s Send Local:%s\n", r.String(), c.LocalAddr().String(), n.maina.LocalAddr().String())
}
return nil
}

@ -0,0 +1,765 @@
package net
import (
"b612.me/apps/b612/netforward"
"b612.me/starlog"
"b612.me/starmap"
"context"
"encoding/binary"
"encoding/json"
"fmt"
"github.com/huin/goupnp/dcps/internetgateway2"
"math/rand"
"net"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
type NatThroughs struct {
Lists []*NatThrough
WebPort int
AutoUPnP bool
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
STUN string
Remote string
HealthCheckInterval int
Type string
}
func (n *NatThroughs) Close() {
for _, v := range n.Lists {
v.Close()
}
}
func (n *NatThroughs) Parse(reqs []string) error {
if n.KeepAlivePeriod == 0 {
n.KeepAlivePeriod = 10
}
if n.KeepAliveIdel == 0 {
n.KeepAliveIdel = 30
}
if n.KeepAliveCount == 0 {
n.KeepAliveCount = 5
}
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 * 30,
KeepAlivePeriod: n.KeepAlivePeriod,
KeepAliveIdel: n.KeepAliveIdel,
KeepAliveCount: n.KeepAliveCount,
UsingKeepAlive: true,
EnableTCP: true,
EnableUDP: true,
},
Type: n.Type,
STUN: n.STUN,
Remote: n.Remote,
KeepAlivePeriod: n.KeepAlivePeriod,
KeepAliveIdel: n.KeepAliveIdel,
KeepAliveCount: n.KeepAliveCount,
AutoUPnP: n.AutoUPnP,
HealthCheckInterval: n.HealthCheckInterval,
}
strs := strings.Split(v, ",")
switch len(strs) {
case 1:
req.Forward.RemoteURI = strs[0]
case 2:
ipport := strings.Split(strs[0], ":")
if len(ipport) == 1 {
port, err := strconv.Atoi(ipport[0])
if err != nil {
return err
}
req.Forward.LocalPort = port
} else {
req.Forward.LocalAddr = ipport[0]
port, err := strconv.Atoi(ipport[1])
if err != nil {
return err
}
req.Forward.LocalPort = port
}
req.Forward.RemoteURI = strs[1]
case 3:
ipport := strings.Split(strs[1], ":")
if len(ipport) == 1 {
port, err := strconv.Atoi(ipport[0])
if err != nil {
return err
}
req.Forward.LocalPort = port
} else {
req.Forward.LocalAddr = ipport[0]
port, err := strconv.Atoi(ipport[1])
if err != nil {
return err
}
req.Forward.LocalPort = port
}
req.Forward.RemoteURI = strs[2]
req.Name = strs[0]
case 4:
ipport := strings.Split(strs[2], ":")
if len(ipport) == 1 {
port, err := strconv.Atoi(ipport[0])
if err != nil {
return err
}
req.Forward.LocalPort = port
} else {
req.Forward.LocalAddr = ipport[0]
port, err := strconv.Atoi(ipport[1])
if err != nil {
return err
}
req.Forward.LocalPort = port
}
req.Type = strings.ToLower(strs[0])
req.Forward.RemoteURI = strs[3]
req.Name = strs[1]
}
n.Lists = append(n.Lists, &req)
}
return nil
}
func (n *NatThroughs) Run() error {
go n.WebService()
wg := sync.WaitGroup{}
for _, v := range n.Lists {
wg.Add(1)
go func(v *NatThrough) {
defer wg.Done()
if err := v.Run(); err != nil {
starlog.Errorf("Failed to run natThrough: %v\n", err)
}
v.HealthCheck()
}(v)
}
wg.Wait()
return nil
}
type nattinfo struct {
Id int `json:"id"`
Name string `json:"name"`
Ext string `json:"ext"`
Local string `json:"local"`
Forward string `json:"forward"`
}
func (n *NatThroughs) WebService() error {
if n.WebPort == 0 {
return nil
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", n.WebPort))
if err != nil {
return err
}
starlog.Infof("Web service listen on %d\n", n.WebPort)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var str string
for k, v := range n.Lists {
str += fmt.Sprintf("id:%d name:%s : %s <----> %s <-----> %s\n", k, v.Name, v.ExtUrl, v.localipport, v.Forward.RemoteURI)
}
w.Write([]byte(str))
})
http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
var res []nattinfo
for k, v := range n.Lists {
res = append(res, nattinfo{
Id: k,
Name: v.Name,
Ext: v.ExtUrl,
Local: v.localipport,
Forward: v.Forward.RemoteURI,
})
}
w.Header().Set("Content-Type", "application/json")
data, _ := json.Marshal(res)
w.Write(data)
return
})
http.HandleFunc("/jump", func(w http.ResponseWriter, r *http.Request) {
types := "https://"
name := r.URL.Query().Get("name")
if name == "" {
w.Write([]byte("id is empty"))
return
}
if r.URL.Query().Get("type") == "http" {
types = "http://"
}
for _, v := range n.Lists {
if v.Name == name {
http.Redirect(w, r, types+v.ExtUrl, http.StatusFound)
return
}
}
})
return http.Serve(listener, nil)
}
// NatThrough 类似于natter.py 是一个用于Full Cone NAT直接穿透的工具
type NatThrough struct {
Name string
OriginLocalPort int
Forward netforward.NetForward
Type string
STUN string
Remote string
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
AutoUPnP bool
isOk bool
ExtUrl string
localipport string
keepaliveConn net.Conn
HealthCheckInterval int
stopFn context.CancelFunc
stopCtx context.Context
}
func (n *NatThrough) Close() {
n.stopFn()
n.Forward.Close()
}
func (c *NatThrough) Run() error {
c.isOk = false
c.stopCtx, c.stopFn = context.WithCancel(context.Background())
c.OriginLocalPort = c.Forward.LocalPort
if c.Forward.LocalPort == 0 {
listener, err := net.Listen(c.Type, c.Forward.LocalAddr+":0")
if err != nil {
return fmt.Errorf("Failed to listen on %s: %v", c.Forward.LocalAddr, err)
}
if c.Type == "tcp" {
c.Forward.LocalPort = listener.Addr().(*net.TCPAddr).Port
} else {
c.Forward.LocalPort = listener.Addr().(*net.UDPAddr).Port
}
listener.Close()
}
if c.Type == "tcp" {
c.Forward.EnableTCP = true
c.Forward.EnableUDP = false
} else {
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)
innerIp, extIp, err := c.GetIPPortFromSTUN(c.Type, c.Forward.LocalAddr, c.Forward.LocalPort, c.STUN)
if err != nil {
return fmt.Errorf("Failed to get external IP and port: %v", err)
}
starlog.Infof("Internal Addr: %s \n", innerIp.String())
starlog.Infof("External Addr: %s \n", extIp.String())
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
}
}
go func() {
if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil {
starlog.Errorf("Failed to run keepalive: %v\n", err)
c.Forward.Close()
c.stopFn()
}
}()
innerIp, extIp, err = c.GetIPPortFromSTUN(c.Type, c.Forward.LocalAddr, c.Forward.LocalPort, c.STUN)
if err != nil {
return fmt.Errorf("Failed to get external IP and port: %v", err)
}
starlog.Infof("Retest Internal Addr: %s \n", innerIp.String())
starlog.Infof("Retest External Addr: %s \n", extIp.String())
if c.AutoUPnP {
go c.HandleUPnP(getIP(innerIp), uint16(getPort(extIp)))
}
err = c.Forward.Run()
if err != nil {
return fmt.Errorf("Failed to run forward: %v", err)
}
c.isOk = true
c.localipport = fmt.Sprintf("%s:%d", getIP(innerIp), getPort(innerIp))
c.ExtUrl = fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp))
return nil
}
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
}
for {
select {
case <-c.stopCtx.Done():
return
case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)):
}
if c.Type == "udp" {
_, 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 {
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
starlog.Errorf("Failed to connect to remote, close connection retrying\n")
c.stopFn()
c.keepaliveConn.Close()
c.Forward.Close()
forward := netforward.NetForward{
LocalAddr: c.Forward.LocalAddr,
LocalPort: c.OriginLocalPort,
RemoteURI: c.Forward.RemoteURI,
KeepAlivePeriod: c.KeepAlivePeriod,
KeepAliveIdel: c.KeepAliveIdel,
KeepAliveCount: c.KeepAliveCount,
UsingKeepAlive: true,
}
time.Sleep(time.Second * 22)
c.Forward = forward
c.Run()
}
}
}
func (c *NatThrough) KeepAlive(localAddr string, localPort int) error {
for {
select {
case <-c.stopCtx.Done():
return nil
default:
}
if c.Type == "tcp" {
dialer := net.Dialer{
Control: netforward.ControlSetReUseAddr,
LocalAddr: &net.TCPAddr{IP: net.ParseIP(localAddr), Port: localPort},
}
conn, err := dialer.Dial("tcp", c.Remote)
if err != nil {
starlog.Errorf("Failed to dial remote: %v\n", err)
time.Sleep(time.Second * 5)
continue
}
c.keepaliveConn = conn
conn.(*net.TCPConn).SetLinger(0)
netforward.SetTcpInfo(conn.(*net.TCPConn), true, c.KeepAliveIdel, c.KeepAlivePeriod, c.KeepAliveCount, 0)
starlog.Infof("Keepalive local:%s remote: %s\n", conn.LocalAddr().String(), conn.RemoteAddr().String())
go func() {
for {
str := fmt.Sprintf("HEAD /keep-alive HTTP/1.1\r\n"+
"Host: %s\r\n"+
"User-Agent: curl/8.0.0 (B612)\r\n"+
"Accept: */*\r\n"+
"Connection: keep-alive\r\n\r\n", strings.Split(c.Remote, ":")[0])
//fmt.Println(str)
if _, err = conn.Write([]byte(str)); err != nil {
fmt.Println(err)
}
time.Sleep(time.Second * 20)
}
}()
for {
_, err := conn.Read(make([]byte, 4096))
if err != nil {
starlog.Warningf("Failed to keepalive remote: %v\n", err)
conn.Close()
break
}
}
} else if c.Type == "udp" {
rmtUdpAddr, err := net.ResolveUDPAddr("udp", c.Remote)
if err != nil {
return err
}
if c.Forward.UdpListener() == nil {
time.Sleep(time.Second * 5)
continue
}
c.keepaliveConn = c.Forward.UdpListener()
for {
_, err = c.Forward.UdpListener().WriteTo([]byte("b612 udp nat through"), rmtUdpAddr)
if err != nil {
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)
}
}
}
}
func (c *NatThrough) HandleUPnP(localaddr string, extPort uint16) {
for {
select {
case <-c.stopCtx.Done():
return
default:
}
client, err := c.FoundUsableUPnP()
if err != nil {
starlog.Errorf("Failed to find UPnP device: %v\n", err)
time.Sleep(time.Second * 20)
continue
}
starlog.Infof("Found UPnP device!\n")
_, _, _, _, _, err = client.GetSpecificPortMappingEntry("", uint16(c.Forward.LocalPort), "TCP")
if err == nil {
starlog.Infof("Port mapping Ok\n")
time.Sleep(time.Second * 20)
continue
}
err = client.AddPortMapping("", uint16(c.Forward.LocalPort), strings.ToUpper(c.Type), uint16(c.Forward.LocalPort), localaddr, true, "B612 TCP Nat PassThrough", 75)
if err != nil {
starlog.Errorf("Failed to add port mapping: %v\n", err)
time.Sleep(time.Second * 20)
continue
}
starlog.Infof("Port mapping added:externalPort %d,localAddr %s,localPort %d\n", extPort, localaddr, c.Forward.LocalPort)
time.Sleep(time.Second * 20)
}
}
func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort int, stunServer string) (net.Addr, net.Addr, error) {
// 替换为你的 TURN 服务器地址
stunAddr, err := net.ResolveUDPAddr("udp", stunServer)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err)
}
var conn net.Conn
if netType == "tcp" {
dialer := net.Dialer{
Control: netforward.ControlSetReUseAddr,
LocalAddr: &net.TCPAddr{IP: net.ParseIP(localip), Port: localPort},
}
conn, err = dialer.Dial("tcp", stunAddr.String())
if err != nil {
return nil, nil, err
}
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)
}
}
defer conn.Close()
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.Write(stunRequest)
if err != nil {
return nil, nil, fmt.Errorf("failed to send STUN request: %v", err)
}
buf := make([]byte, 1500)
conn.SetReadDeadline(time.Now().Add(3 * time.Second))
n, err := conn.Read(buf)
if err != nil {
return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err)
}
// 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) 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 {
return ""
}
return tmp.LocalAddr().(*net.UDPAddr).IP.String()
}
func (c *NatThrough) FoundUsableUPnP() (RouterClient, error) {
wg := sync.WaitGroup{}
found := false
result := make(chan RouterClient, 3)
defer close(result)
wg.Add(3)
go func() {
defer wg.Done()
clients, errors, err := internetgateway2.NewWANIPConnection2Clients()
if err != nil {
return
}
if len(errors) > 0 {
return
}
if len(clients) == 0 {
return
}
starlog.Infof("Found WANIPConnection2 clients:%s\n", clients[0].Location.String())
found = true
result <- clients[0]
}()
go func() {
defer wg.Done()
clients, errors, err := internetgateway2.NewWANIPConnection1Clients()
if err != nil {
return
}
if len(errors) > 0 {
return
}
if len(clients) == 0 {
return
}
starlog.Infof("Found WANIPConnection1 clients:%s\n", clients[0].Location.String())
found = true
result <- clients[0]
}()
go func() {
defer wg.Done()
clients, errors, err := internetgateway2.NewWANPPPConnection1Clients()
if err != nil {
return
}
if len(errors) > 0 {
return
}
if len(clients) == 0 {
return
}
starlog.Infof("Found WANPPPConnection1 clients:%s\n", clients[0].Location.String())
found = true
result <- clients[0]
}()
wg.Wait()
if found {
return <-result, nil
}
return nil, fmt.Errorf("no UPnP devices discovered")
}
type RouterClient interface {
AddPortMapping(
NewRemoteHost string,
NewExternalPort uint16,
NewProtocol string,
NewInternalPort uint16,
NewInternalClient string,
NewEnabled bool,
NewPortMappingDescription string,
NewLeaseDuration uint32,
) (err error)
GetExternalIPAddress() (
NewExternalIPAddress string,
err error,
)
DeletePortMapping(NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (err error)
GetSpecificPortMappingEntry(NewRemoteHost string, NewExternalPort uint16, NewProtocol string) (NewInternalPort uint16, NewInternalClient string, NewEnabled bool, NewPortMappingDescription string, NewLeaseDuration uint32, err error)
}

@ -0,0 +1,50 @@
package net
import (
"b612.me/apps/b612/netforward"
"fmt"
"testing"
"time"
)
func TestNathrough(t *testing.T) {
var n = NatThrough{
Forward: netforward.NetForward{
LocalAddr: "0.0.0.0",
LocalPort: 0,
RemoteURI: "127.0.0.1:88",
EnableTCP: true,
EnableUDP: false,
DelayMilSec: 0,
DelayToward: 0,
StdinMode: false,
IgnoreEof: false,
DialTimeout: 3000,
UDPTimeout: 3000,
KeepAlivePeriod: 30,
KeepAliveIdel: 30,
KeepAliveCount: 5,
UserTimeout: 0,
UsingKeepAlive: true,
},
Type: "tcp",
STUN: "turn.b612.me:3478",
Remote: "baidu.com:80",
KeepAlivePeriod: 3000,
KeepAliveIdel: 3000,
KeepAliveCount: 5,
AutoUPnP: true,
stopFn: nil,
stopCtx: nil,
}
go func() {
time.Sleep(time.Second * 10)
fmt.Println(n.ExtUrl)
}()
if err := n.Run(); err != nil {
fmt.Println(err)
t.Error(err)
}
n.HealthCheck()
time.Sleep(time.Second * 5)
}

@ -0,0 +1,33 @@
package net
import (
"testing"
)
func TestScan(t *testing.T) {
s := ScanPort{
Host: "192.168.2.109",
Timeout: 2000,
Threads: 5000,
}
if err := s.Parse("1-65535"); err != nil {
t.Error(err)
}
if err := s.Run(); err != nil {
t.Error(err)
}
}
func TestScanIP(t *testing.T) {
s := ScanIP{
Host: "192.168.2.1",
CIDR: 23,
Timeout: 2000,
Threads: 5000,
ScanType: "icmp",
WithHostname: true,
}
if err := s.ICMP(); err != nil {
t.Error(err)
}
}

@ -0,0 +1,279 @@
package net
import (
"b612.me/apps/b612/netforward"
"b612.me/stario"
"b612.me/starlog"
"b612.me/starnet"
"fmt"
"math"
"net"
"sync/atomic"
"time"
)
type ScanIP struct {
Host string
CIDR int
Port int
Mask string
Threads int
Timeout int
ScanType string
ipNet *net.IPNet
Log string
Retry int
WithHostname bool
}
func (s *ScanIP) Parse() error {
if s.CIDR == 0 && s.Mask == "" {
return fmt.Errorf("CIDR or Mask must be set")
}
if s.CIDR != 0 {
return nil
}
//mask to cidr
ipMask := net.IPMask(net.ParseIP(s.Mask).To4())
if ipMask == nil {
return fmt.Errorf("invalid mask")
}
s.CIDR, _ = ipMask.Size()
return nil
}
func (s *ScanIP) nextIP(ipStr string) (net.IP, error) {
var err error
if s.ipNet == nil {
_, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR))
if err != nil {
return nil, fmt.Errorf("invalid CIDR: %v", err)
}
}
ip := net.ParseIP(ipStr)
if ip == nil {
return nil, fmt.Errorf("invalid IP: %v", ipStr)
}
// Convert IP to 4-byte representation
ip = ip.To4()
if ip == nil {
return nil, fmt.Errorf("non-IPv4 address: %v", ipStr)
}
// Increment IP
for i := len(ip) - 1; i >= 0; i-- {
ip[i]++
if ip[i] > 0 {
break
}
}
// Check if incremented IP is still in range
if !s.ipNet.Contains(ip) {
return nil, nil
}
return ip, nil
}
func (s *ScanIP) NetSize() (int, error) {
var err error
if s.ipNet == nil {
_, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR))
if err != nil {
return 0, fmt.Errorf("invalid CIDR: %v", err)
}
}
maskSize, _ := s.ipNet.Mask.Size()
return int(math.Pow(2, float64(32-maskSize))) - 2, nil
}
func (s *ScanIP) FirstLastIP() (net.IP, net.IP, error) {
var err error
if s.ipNet == nil {
_, s.ipNet, err = net.ParseCIDR(s.Host + "/" + fmt.Sprintf("%d", s.CIDR))
if err != nil {
return nil, nil, fmt.Errorf("invalid CIDR: %v", err)
}
}
firstIP := s.ipNet.IP.Mask(s.ipNet.Mask)
lastIP := make(net.IP, len(firstIP))
copy(lastIP, firstIP)
for i := range firstIP {
lastIP[i] = firstIP[i] | ^s.ipNet.Mask[i]
}
return firstIP, lastIP, nil
}
func (s *ScanIP) ICMP() error {
if s.ScanType != "icmp" {
return fmt.Errorf("scan type must be icmp")
}
if err := s.Parse(); err != nil {
return err
}
if s.Log != "" {
starlog.SetLogFile(s.Log, starlog.Std, true)
}
firstIP, lastIP, err := s.FirstLastIP()
if err != nil {
return err
}
ns, _ := s.NetSize()
starlog.Infof("Scan %s/%d\n", s.Host, s.CIDR)
starlog.Infof("Scan %s-%s\n", firstIP.String(), lastIP.String())
starlog.Infof("There are %d hosts\n", ns)
starlog.Infof("Threads: %d\n", s.Threads)
wg := stario.NewWaitGroup(s.Threads)
count := int32(0)
allcount := int32(0)
interrupt := make(chan [2]string)
go func() {
for {
select {
case <-time.After(time.Second * 2):
fmt.Printf("scan %d ips, %d up\r", allcount, count)
case ip, opened := <-interrupt:
if !opened {
return
}
if s.WithHostname {
starlog.Infof("Host %v is up, Name:%v\n", ip[0], ip[1])
} else {
starlog.Infof("Host %v is up\n", ip[0])
}
}
}
}()
idx := 0
for {
ip := firstIP.String()
if ip == lastIP.String() {
break
}
idx++
wg.Add(1)
go func(ip string, idx int) {
defer func() {
atomic.AddInt32(&allcount, 1)
}()
defer wg.Done()
for i := 0; i < s.Retry+1; i++ {
_, err := starnet.Ping(ip, idx, time.Duration(s.Timeout)*time.Millisecond)
if err == nil {
atomic.AddInt32(&count, 1)
if s.WithHostname {
hostname, err := net.LookupAddr(ip)
if err == nil {
interrupt <- [2]string{ip, hostname[0]}
return
}
}
interrupt <- [2]string{ip, ""}
return
}
}
}(ip, idx)
firstIP, _ = s.nextIP(ip)
}
wg.Wait()
close(interrupt)
starlog.Infof("scan %d ips, %d up\n", ns, count)
return nil
}
func (s *ScanIP) TCP(port int) error {
if s.ScanType != "tcp" {
return fmt.Errorf("scan type must be tcp")
}
if err := s.Parse(); err != nil {
return err
}
if s.Log != "" {
starlog.SetLogFile(s.Log, starlog.Std, true)
}
firstIP, lastIP, err := s.FirstLastIP()
if err != nil {
return err
}
ns, _ := s.NetSize()
starlog.Infof("Scan %s/%d\n", s.Host, s.CIDR)
starlog.Infof("Scan %s-%s\n", firstIP.String(), lastIP.String())
starlog.Infof("There are %d hosts\n", ns)
starlog.Infof("Threads: %d\n", s.Threads)
wg := stario.NewWaitGroup(s.Threads)
count := int32(0)
allcount := int32(0)
interrupt := make(chan [2]string)
go func() {
for {
select {
case <-time.After(time.Second * 2):
fmt.Printf("scan %d ips, %d up\r", allcount, count)
case ip, opened := <-interrupt:
if !opened {
return
}
if s.WithHostname {
starlog.Infof("Host %v is up, Name:%v\n", ip[0], ip[1])
} else {
starlog.Infof("Host %v is up\n", ip[0])
}
}
}
}()
idx := 0
localAddr, err := net.ResolveTCPAddr("tcp", ":0")
if err != nil {
starlog.Errorln("ResolveTCPAddr error, ", err)
return err
}
for {
ip := firstIP.String()
if ip == lastIP.String() {
break
}
idx++
wg.Add(1)
go func(ip string, idx int) {
defer func() {
atomic.AddInt32(&allcount, 1)
}()
defer wg.Done()
for i := 0; i < s.Retry+1; i++ {
dialer := net.Dialer{
LocalAddr: localAddr,
Timeout: time.Duration(s.Timeout) * time.Millisecond,
Control: netforward.ControlSetReUseAddr,
}
_, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, port))
if err == nil {
atomic.AddInt32(&count, 1)
if s.WithHostname {
hostname, err := net.LookupAddr(ip)
if err == nil {
interrupt <- [2]string{ip, hostname[0]}
return
}
}
interrupt <- [2]string{ip, ""}
return
}
}
}(ip, idx)
firstIP, _ = s.nextIP(ip)
}
wg.Wait()
close(interrupt)
starlog.Infof("scan %d ips, %d up\n", ns, count)
return nil
}

@ -0,0 +1,131 @@
package net
import (
"b612.me/apps/b612/netforward"
"b612.me/stario"
"b612.me/starlog"
"fmt"
"net"
"sort"
"strconv"
"strings"
"sync/atomic"
"time"
)
type ScanPort struct {
Host string
Ports []int
Timeout int
Threads int
Log string
Retry int
}
func (s *ScanPort) Parse(potStr string) error {
ports := strings.Split(potStr, ",")
for _, port := range ports {
port = strings.TrimSpace(port)
if strings.Contains(port, "-") {
// range
r := strings.Split(port, "-")
if len(r) != 2 {
continue
}
start, err := strconv.Atoi(r[0])
if err != nil {
starlog.Warningf("invalid port: %s\n", r[0])
continue
}
end, err := strconv.Atoi(r[1])
if err != nil {
starlog.Warningf("invalid port: %s\n", r[1])
continue
}
for i := start; i <= end; i++ {
if i < 1 || i > 65535 {
starlog.Warningf("invalid port: %d\n", i)
continue
}
s.Ports = append(s.Ports, i)
}
} else {
// single port
tmp, err := strconv.Atoi(port)
if err != nil {
starlog.Warningf("invalid port: %s\n", port)
continue
}
if tmp < 1 || tmp > 65535 {
starlog.Warningf("invalid port: %d\n", tmp)
continue
}
s.Ports = append(s.Ports, tmp)
}
}
return nil
}
func (s *ScanPort) Run() error {
if s.Threads < 1 {
s.Threads = 1
}
if s.Log != "" {
starlog.SetLogFile(s.Log, starlog.Std, true)
}
sort.Ints(s.Ports)
starlog.Infof("scan count %d ports for host %v\n", len(s.Ports), s.Host)
wg := stario.NewWaitGroup(s.Threads)
localAddr, err := net.ResolveTCPAddr("tcp", ":0")
if err != nil {
starlog.Errorln("ResolveTCPAddr error, ", err)
return err
}
count := int32(0)
allcount := int32(0)
interrupt := make(chan int)
go func() {
for {
select {
case <-time.After(time.Second * 2):
fmt.Printf("scan %d ports, %d open\r", atomic.LoadInt32(&allcount), count)
case port, opened := <-interrupt:
if !opened {
return
}
starlog.Infof("port %d is open\n", port)
}
}
}()
for _, port := range s.Ports {
wg.Add(1)
go func(port int) {
defer wg.Done()
defer func() {
atomic.AddInt32(&allcount, 1)
}()
for i := 0; i < s.Retry+1; i++ {
dialer := net.Dialer{
LocalAddr: localAddr,
Timeout: time.Duration(s.Timeout) * time.Millisecond,
Control: netforward.ControlSetReUseAddr,
}
conn, err := dialer.Dial("tcp", net.JoinHostPort(s.Host, strconv.Itoa(port)))
if err != nil {
continue
}
conn.(*net.TCPConn).SetLinger(0)
conn.Close()
interrupt <- port
atomic.AddInt32(&count, 1)
return
}
}(port)
}
wg.Wait()
close(interrupt)
starlog.Infof("scan %d ports, %d open\n", len(s.Ports), count)
return nil
}

@ -5,23 +5,24 @@ import (
"b612.me/starlog" "b612.me/starlog"
"b612.me/starnet" "b612.me/starnet"
"crypto/elliptic" "crypto/elliptic"
"encoding/json" "encoding/csv"
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"net" "net"
"os" "os"
"os/signal"
"strings" "strings"
"time"
) )
var ( var (
listenAddr string listenAddr string
keyFile string keyFile string
KeyPasswd string KeyPasswd string
outpath string outpath string
curlUrl string curlUrl string
curlArg []string serverVersion string
curlArg []string
) )
func init() { func init() {
@ -29,6 +30,7 @@ func init() {
cmdSSHJar.Flags().StringVarP(&keyFile, "key", "k", "", "私钥文件") cmdSSHJar.Flags().StringVarP(&keyFile, "key", "k", "", "私钥文件")
cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码") cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码")
cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件") cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件")
cmdSSHJar.Flags().StringVarP(&serverVersion, "version", "v", "SSH-2.0-OpenSSH_8.0", "SSH版本")
} }
var cmdSSHJar = &cobra.Command{ var cmdSSHJar = &cobra.Command{
@ -36,11 +38,11 @@ var cmdSSHJar = &cobra.Command{
Short: "SSH蜜罐", Short: "SSH蜜罐",
Long: "SSH蜜罐", Long: "SSH蜜罐",
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath) runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, serverVersion)
}, },
} }
func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath string) { func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
var f *os.File var f *os.File
var err error var err error
if outpath != "" { if outpath != "" {
@ -50,16 +52,18 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath string) {
return return
} }
} }
conn := csv.NewWriter(f)
defer f.Close() defer f.Close()
defer conn.Flush()
config := &ssh.ServerConfig{ config := &ssh.ServerConfig{
ServerVersion: version,
// 密码验证回调函数 // 密码验证回调函数
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) { PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass)) starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass))
data := []string{c.RemoteAddr().String(), c.User(), string(pass)} data := []string{time.Now().Format("2006-01-02 15:04:05"), c.RemoteAddr().String(), c.User(), string(pass)}
bts, _ := json.Marshal(data)
if f != nil { if f != nil {
f.Write(bts) conn.Write(data)
f.Write([]byte("\n")) conn.Flush()
} }
if curlUrl != "" { if curlUrl != "" {
go func() { go func() {
@ -75,7 +79,7 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath string) {
data[args[0]] = args[1] 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()
} }
}() }()
} }
@ -121,16 +125,7 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath string) {
return return
} }
starlog.Noticeln("SSH HoneyJar is listening on", listenAddr) starlog.Noticeln("SSH HoneyJar is listening on", listenAddr)
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt, os.Kill)
for { for {
select {
case <-sig:
starlog.Noticef("SSH HoneyJar is shutting down")
listener.Close()
return
default:
}
conn, err := listener.Accept() conn, err := listener.Accept()
if err != nil { if err != nil {
continue continue

@ -3,5 +3,5 @@ package net
import "testing" import "testing"
func TestSSHJar(t *testing.T) { func TestSSHJar(t *testing.T) {
//runSSHHoneyJar("0.0.0.0:22") runSSHHoneyJar("127.0.0.1:22", "", "", "./test.csv", "")
} }

@ -198,7 +198,7 @@ func (s *TcpServer) Run() error {
starlog.Errorln("AcceptTCP error:", err) starlog.Errorln("AcceptTCP error:", err)
continue continue
} }
starlog.Infof("Accept new connection from %s", conn.RemoteAddr().String()) starlog.Infof("Accept new connection from %s\n", conn.RemoteAddr().String())
s.Lock() s.Lock()
s.Clients[conn.RemoteAddr().String()] = s.getTcpConn(conn) s.Clients[conn.RemoteAddr().String()] = s.getTcpConn(conn)
s.Unlock() s.Unlock()

@ -4,7 +4,6 @@ import (
"b612.me/starlog" "b612.me/starlog"
"b612.me/starnet" "b612.me/starnet"
"context" "context"
"encoding/json"
"fmt" "fmt"
"golang.org/x/net/icmp" "golang.org/x/net/icmp"
"golang.org/x/net/ipv4" "golang.org/x/net/ipv4"
@ -214,12 +213,12 @@ func GetIPInfo(ip string, addr string) string {
return "" return ""
} }
uri := strings.ReplaceAll(addr, "{ip}", ip) 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 { if err != nil {
return "获取IP信息失败" + err.Error() return "获取IP信息失败" + err.Error()
} }
var ipinfo IPInfo var ipinfo IPInfo
err = json.Unmarshal(res.RecvData, &ipinfo) err = res.Body().Unmarshal(&ipinfo)
if err != nil { if err != nil {
return "解析IP信息失败" + err.Error() return "解析IP信息失败" + err.Error()
} }

@ -29,6 +29,7 @@ func init() {
CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count") 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().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.IgnoreEof, "ignore-eof", "E", false, "ignore eof")
CmdNetforward.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "verbose mode")
} }
var CmdNetforward = &cobra.Command{ var CmdNetforward = &cobra.Command{

@ -3,6 +3,7 @@ package netforward
import ( import (
"b612.me/stario" "b612.me/stario"
"b612.me/starlog" "b612.me/starlog"
"b612.me/starmap"
"context" "context"
"errors" "errors"
"fmt" "fmt"
@ -12,6 +13,7 @@ import (
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
"syscall"
"time" "time"
) )
@ -30,12 +32,19 @@ type NetForward struct {
stopCtx context.Context stopCtx context.Context
stopFn context.CancelFunc stopFn context.CancelFunc
running int32 running int32
UdpHooks map[string]*starmap.StarStack
KeepAlivePeriod int KeepAlivePeriod int
KeepAliveIdel int KeepAliveIdel int
KeepAliveCount int KeepAliveCount int
UserTimeout int UserTimeout int
UsingKeepAlive bool UsingKeepAlive bool
Verbose bool
udpListener *net.UDPConn
}
func (n *NetForward) UdpListener() *net.UDPConn {
return n.udpListener
} }
func (n *NetForward) Close() { func (n *NetForward) Close() {
@ -127,7 +136,12 @@ func (n *NetForward) Run() error {
func (n *NetForward) runTCP() error { func (n *NetForward) runTCP() error {
atomic.AddInt32(&n.running, 1) atomic.AddInt32(&n.running, 1)
defer atomic.AddInt32(&n.running, -1) defer atomic.AddInt32(&n.running, -1)
listen, err := net.Listen("tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) cfg := net.ListenConfig{
Control: func(network, address string, c syscall.RawConn) error {
return c.Control(SetReUseAddr)
},
}
listen, err := cfg.Listen(context.Background(), "tcp", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
if err != nil { if err != nil {
starlog.Errorln("Listening On Tcp Failed:", err) starlog.Errorln("Listening On Tcp Failed:", err)
return err return err
@ -188,17 +202,17 @@ type UDPConn struct {
lastbeat int64 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() u.lastbeat = time.Now().Unix()
return u.Conn.Write(p) 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() u.lastbeat = time.Now().Unix()
return u.Conn.Read(p) return u.Conn.Read(p)
} }
func (u UDPConn) Work(delay int) { func (u *UDPConn) Work(delay int, verbose bool) {
buf := make([]byte, 8192) buf := make([]byte, 8192)
for { for {
if delay > 0 { if delay > 0 {
@ -210,7 +224,10 @@ func (u UDPConn) Work(delay int) {
u.lastbeat = 0 u.lastbeat = 0
return 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 { if err != nil {
u.lastbeat = 0 u.lastbeat = 0
return return
@ -230,12 +247,13 @@ func (n *NetForward) runUDP() error {
if err != nil { if err != nil {
return err return err
} }
n.udpListener = listen
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort)) starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
go func() { go func() {
<-n.stopCtx.Done() <-n.stopCtx.Done()
listen.Close() listen.Close()
}() }()
udpMap := make(map[string]UDPConn) udpMap := make(map[string]*UDPConn)
go func() { go func() {
for { for {
select { select {
@ -264,6 +282,22 @@ func (n *NetForward) runUDP() error {
if err != nil || rmt.String() == n.RemoteURI { if err != nil || rmt.String() == n.RemoteURI {
continue 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) { go func(data []byte, rmt *net.UDPAddr) {
log := starlog.Std.NewFlag() log := starlog.Std.NewFlag()
mu.Lock() mu.Lock()
@ -276,20 +310,23 @@ func (n *NetForward) runUDP() error {
mu.Unlock() mu.Unlock()
return return
} }
addr = UDPConn{ addr = &UDPConn{
Conn: conn, Conn: conn,
remoteAddr: rmt, remoteAddr: rmt,
listen: listen, listen: listen,
lastbeat: time.Now().Unix(), lastbeat: time.Now().Unix(),
} }
udpMap[rmt.String()] = addr 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) log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
} }
mu.Unlock() mu.Unlock()
if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) { if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec)) 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) _, err := addr.Write(data)
if err != nil { if err != nil {
mu.Lock() mu.Lock()
@ -302,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) { func (n *NetForward) copy(dst, src net.Conn) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(2) wg.Add(2)
@ -318,6 +361,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
src.Close() src.Close()
return return
} }
n.showVerbose("T", src.RemoteAddr().String(), dst.RemoteAddr().String(), bufsize[:count])
_, err = dst.Write(bufsize[:count]) _, err = dst.Write(bufsize[:count])
if err != nil { if err != nil {
src.Close() src.Close()
@ -342,6 +386,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
dst.Close() dst.Close()
return return
} }
n.showVerbose("U", dst.RemoteAddr().String(), src.RemoteAddr().String(), bufsize[:count])
_, err = src.Write(bufsize[:count]) _, err = src.Write(bufsize[:count])
if err != nil { if err != nil {
src.Close() src.Close()

@ -28,6 +28,7 @@ func TestForward(t *testing.T) {
fmt.Println(f.Status()) fmt.Println(f.Status())
continue continue
} }
return break
} }
time.Sleep(time.Second * 5)
} }

@ -3,6 +3,7 @@
package netforward package netforward
import ( import (
"golang.org/x/sys/unix"
"net" "net"
"syscall" "syscall"
) )
@ -33,3 +34,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
} }
return err return err
} }
func SetReUseAddr(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}
func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) {
if err := c.Control(func(fd uintptr) {
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
if err != nil {
return
}
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
if err != nil {
return
}
}); err != nil {
return err
}
return err
}

@ -3,6 +3,7 @@
package netforward package netforward
import ( import (
"golang.org/x/sys/unix"
"net" "net"
"syscall" "syscall"
) )
@ -37,3 +38,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
} }
return err return err
} }
func SetReUseAddr(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
}
func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) {
if err := c.Control(func(fd uintptr) {
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
if err != nil {
return
}
err = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1)
if err != nil {
return
}
}); err != nil {
return err
}
return err
}

@ -31,3 +31,16 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
} }
return conn.SetKeepAlive(false) return conn.SetKeepAlive(false)
} }
func SetReUseAddr(fd uintptr) {
syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}
func ControlSetReUseAddr(network, address string, c syscall.RawConn) (err error) {
if err := c.Control(func(fd uintptr) {
err = syscall.SetsockoptInt(syscall.Handle(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
}); err != nil {
return err
}
return
}

@ -1,13 +1,18 @@
package whois package whois
import ( import (
"b612.me/sdk/whois"
"b612.me/stario"
"b612.me/starlog" "b612.me/starlog"
"b612.me/staros" "b612.me/staros"
"github.com/likexian/whois" "bufio"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"golang.org/x/net/proxy" "golang.org/x/net/proxy"
"io"
"os" "os"
"strings" "strings"
"sync"
"time" "time"
) )
@ -16,13 +21,37 @@ var output string
var whoisServer []string var whoisServer []string
var socks5 string var socks5 string
var socks5Auth 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() { func init() {
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间") Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹") Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹")
Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器") Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080") 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{ var Cmd = &cobra.Command{
@ -35,7 +64,6 @@ var Cmd = &cobra.Command{
return return
} }
if !staros.Exists(output) { if !staros.Exists(output) {
cmd.Println("输出文件夹不存在,将使用标准输出")
output = "" output = ""
} }
c := whois.NewClient() c := whois.NewClient()
@ -70,9 +98,238 @@ var Cmd = &cobra.Command{
cmd.Println("-----------------------------------------------------") cmd.Println("-----------------------------------------------------")
continue 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("-----------------------------------------------------") 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
}
}
}

Loading…
Cancel
Save