Compare commits
8 Commits
v2.1.0.bet
...
master
Author | SHA1 | Date | |
---|---|---|---|
1879810c9e | |||
98f50488ca | |||
98ed1980ff | |||
32c6e7b534 | |||
5b0b3834bb | |||
8845d35339 | |||
ff8a3470c7 | |||
4fd9c37944 |
74
astro/astro.go
Normal file
74
astro/astro.go
Normal file
@ -0,0 +1,74 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed city.json
|
||||
var cityByte []byte
|
||||
|
||||
type City struct {
|
||||
Code int `json:"code"`
|
||||
CityName string `json:"city_name"`
|
||||
Lat float64 `json:"lat"`
|
||||
Lon float64 `json:"lon"`
|
||||
}
|
||||
|
||||
var lon, lat, height, loc float64
|
||||
var city string
|
||||
|
||||
func SetLonLatHeight(lo, la, he float64) error {
|
||||
lon, lat, height = lo, la, he
|
||||
return os.WriteFile(".ast.env", []byte(fmt.Sprintf("%f,%f,%f,%f", lon, lat, height, loc)), 0644)
|
||||
}
|
||||
|
||||
func LoadLonLatHeight() error {
|
||||
if _, err := os.Stat(".ast.env"); errors.Is(err, os.ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
b, err := os.ReadFile(".ast.env")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Sscanf(string(b), "%f,%f,%f,%f", &lon, &lat, &height, &loc)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetLonLatHeight() (float64, float64, float64) {
|
||||
return lon, lat, height
|
||||
}
|
||||
|
||||
func Lon() float64 {
|
||||
return lon
|
||||
}
|
||||
|
||||
func Lat() float64 {
|
||||
return lat
|
||||
}
|
||||
|
||||
func Height() float64 {
|
||||
return height
|
||||
}
|
||||
|
||||
func GetFromCity(name string) bool {
|
||||
var c []City
|
||||
err := json.Unmarshal(cityByte, &c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, v := range c {
|
||||
if strings.Contains(v.CityName, name) {
|
||||
lon, lat = v.Lon, v.Lat
|
||||
loc = 8
|
||||
time.Local = time.FixedZone("Local", int(loc*3600))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
431
astro/calendar.go
Normal file
431
astro/calendar.go
Normal file
@ -0,0 +1,431 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"b612.me/astro/calendar"
|
||||
"b612.me/staros"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var year int
|
||||
var nowDay string
|
||||
var isTimestamp bool
|
||||
var isLunar, isLive, isLeap bool
|
||||
|
||||
func init() {
|
||||
CmdDateInfo.Flags().StringVarP(&nowDay, "now", "n", "", "指定现在的时间")
|
||||
CmdDateInfo.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "是否为时间戳")
|
||||
CmdDateInfo.Flags().BoolVarP(&isLunar, "lunar", "l", false, "是否为农历")
|
||||
CmdDateInfo.Flags().BoolVarP(&isLive, "live", "v", false, "是否为实时")
|
||||
CmdDateInfo.Flags().BoolVarP(&isLeap, "leap", "L", false, "是否为农历闰月")
|
||||
CmdHoliday.Flags().IntVarP(&year, "year", "y", 0, "年份")
|
||||
CmdCal.AddCommand(CmdHoliday, CmdDateInfo)
|
||||
}
|
||||
|
||||
var CmdCal = &cobra.Command{
|
||||
Use: "cal",
|
||||
Args: cobra.MaximumNArgs(100),
|
||||
Short: "简洁日历与日期相关方法",
|
||||
Long: "简洁日历,支持多个月份,多个年份,支持年份月份混合输入,日期方法请查看子命令",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
now := time.Now()
|
||||
fmt.Println(now.Format("2006年01月"))
|
||||
fmt.Println("------------------------")
|
||||
ShowCalendar(now.Year(), int(now.Month()))
|
||||
return
|
||||
}
|
||||
for _, v := range args {
|
||||
if len(v) < 4 {
|
||||
fmt.Println("输入错误,年份至少4位")
|
||||
return
|
||||
}
|
||||
year, err := strconv.Atoi(v[:4])
|
||||
if err != nil {
|
||||
fmt.Println("输入错误:", err)
|
||||
return
|
||||
}
|
||||
if len(v) == 4 {
|
||||
for i := 1; i <= 12; i++ {
|
||||
fmt.Printf("%d年%d月\n", year, i)
|
||||
fmt.Println("------------------------")
|
||||
ShowCalendar(year, i)
|
||||
fmt.Println()
|
||||
fmt.Println()
|
||||
}
|
||||
continue
|
||||
}
|
||||
month, err := strconv.Atoi(v[4:])
|
||||
if err != nil {
|
||||
fmt.Println("输入错误:", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%d年%d月\n", year, month)
|
||||
fmt.Println("------------------------")
|
||||
ShowCalendar(year, month)
|
||||
fmt.Println()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdHoliday = &cobra.Command{
|
||||
Use: "hol",
|
||||
Short: "中国法定节日放假安排",
|
||||
Long: "中国法定节日放假安排",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if year == 0 {
|
||||
year = time.Now().Year()
|
||||
}
|
||||
fmt.Printf("%d年放假安排\n", year)
|
||||
for _, v := range args {
|
||||
fmt.Println("------------------------")
|
||||
var d Holiday
|
||||
switch v {
|
||||
case "1", "元旦":
|
||||
d = YuanDan(year)
|
||||
case "2", "春节":
|
||||
d = ChunJie(year)
|
||||
case "3", "清明", "清明节":
|
||||
d = QingMing(year)
|
||||
case "4", "劳动", "劳动节":
|
||||
d = LaoDongJie(year)
|
||||
case "5", "端午", "端午节":
|
||||
d = DuanWu(year)
|
||||
case "6", "中秋", "中秋节":
|
||||
d = ZhongQiu(year)
|
||||
case "7", "国庆", "国庆节":
|
||||
d = GuoQing(year)
|
||||
}
|
||||
d.BasicInfo()
|
||||
d.Detail()
|
||||
}
|
||||
if len(args) == 0 {
|
||||
for _, v := range ChineseHoliday(year) {
|
||||
fmt.Println("------------------------")
|
||||
v.BasicInfo()
|
||||
v.Detail()
|
||||
fmt.Println(" ")
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdDateInfo = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "指定时刻的详情",
|
||||
Long: "指定时刻的详情,格式:info [时间] [选项]",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var now = time.Now()
|
||||
var target = now
|
||||
var err error
|
||||
if nowDay != "" {
|
||||
now, err = parseDate(time.Time{}, nowDay, isTimestamp)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(args) > 0 {
|
||||
target, err = parseDate(now, args[0], isTimestamp)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
if isLunar {
|
||||
LDateInfo(now, target, isLeap)
|
||||
} else {
|
||||
DateInfo(now, target)
|
||||
}
|
||||
if !isLive {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Nanosecond*
|
||||
time.Duration(1000000000-time.Now().Nanosecond()) + 1)
|
||||
if nowDay == "" {
|
||||
now = time.Now()
|
||||
now = now.Add(time.Duration(now.Nanosecond()*-1) * time.Nanosecond)
|
||||
} else {
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
ClearScreen()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func ClearScreen() {
|
||||
fmt.Print("\033[H\033[2J")
|
||||
}
|
||||
|
||||
func GenerateCalendar(year int, month int) []string {
|
||||
var days []string
|
||||
date := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.Local)
|
||||
firstWeekday := int(date.Weekday()) - 1
|
||||
if firstWeekday < 0 {
|
||||
firstWeekday += 7
|
||||
}
|
||||
count := 0
|
||||
for i := 0; i < firstWeekday; i++ {
|
||||
days = append(days, " ")
|
||||
count++
|
||||
}
|
||||
for i := 1; i <= 32; i++ {
|
||||
insertLunar := func(start int) {
|
||||
if start < 0 {
|
||||
start += 7
|
||||
}
|
||||
for j := start; j >= 0; j-- {
|
||||
if i-j < 1 {
|
||||
days = append(days, "\u3000\u3000")
|
||||
continue
|
||||
}
|
||||
date = time.Date(year, time.Month(month), i-j, 0, 0, 0, 0, time.Local)
|
||||
_, d, _, str := calendar.RapidSolarToLunar(date)
|
||||
if d != 1 {
|
||||
str = str[strings.Index(str, "月")+3:]
|
||||
|
||||
} else {
|
||||
str = str[:strings.Index(str, "月")+3]
|
||||
}
|
||||
days = append(days, str)
|
||||
}
|
||||
}
|
||||
date = time.Date(year, time.Month(month), i, 0, 0, 0, 0, time.Local)
|
||||
if date.Month() != time.Month(month) {
|
||||
if (count)%7 != 0 {
|
||||
i--
|
||||
insertLunar(count%7 - 1)
|
||||
}
|
||||
break
|
||||
}
|
||||
days = append(days, fmt.Sprintf("%d", i))
|
||||
count++
|
||||
if count%7 == 0 {
|
||||
insertLunar(6)
|
||||
}
|
||||
}
|
||||
return days
|
||||
}
|
||||
|
||||
func ShowCalendar(year int, month int) {
|
||||
days := GenerateCalendar(year, month)
|
||||
fd := int(os.Stdout.Fd())
|
||||
width, _, err := term.GetSize(fd)
|
||||
if err != nil {
|
||||
fmt.Println("Error getting terminal size:", err)
|
||||
return
|
||||
}
|
||||
spark := 4
|
||||
if width > 45 {
|
||||
spark = 5
|
||||
}
|
||||
if width < 30 {
|
||||
fmt.Println("Terminal too small")
|
||||
return
|
||||
}
|
||||
for _, v := range []string{"\u3000一", "\u3000二", "\u3000三", "\u3000四", "\u3000五", "\u3000六", "\u3000日"} {
|
||||
fmt.Printf("%*s", spark-1, v)
|
||||
}
|
||||
fmt.Println()
|
||||
ct := 0
|
||||
doBreak := false
|
||||
for i, v := range days {
|
||||
if len(days)-i < 7 && i%7 == len(days)-i && !doBreak {
|
||||
doBreak = true
|
||||
ct++
|
||||
if ct%2 == 1 {
|
||||
spark -= 2
|
||||
} else {
|
||||
spark += 2
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Print(" ")
|
||||
}
|
||||
if i%7 == 0 && i != 0 && !doBreak {
|
||||
fmt.Println()
|
||||
ct++
|
||||
if ct%2 == 1 {
|
||||
fmt.Print(" ")
|
||||
spark -= 2
|
||||
} else {
|
||||
spark += 2
|
||||
}
|
||||
}
|
||||
fmt.Printf("%*s ", spark, v)
|
||||
}
|
||||
}
|
||||
|
||||
func LDateInfo(now, date time.Time, isLeap bool) {
|
||||
sdate := calendar.RapidLunarToSolar(date.Year(), int(date.Month()), date.Day(), isLeap)
|
||||
DateInfo(now, sdate)
|
||||
}
|
||||
func DateInfo(now, date time.Time) {
|
||||
if now.IsZero() {
|
||||
now = time.Now()
|
||||
}
|
||||
_, m, _, str := calendar.RapidSolarToLunar(date)
|
||||
gz := calendar.GanZhi(date.Year())
|
||||
if m > 10 && int(date.Month()) < 3 {
|
||||
gz = calendar.GanZhi(date.Year() - 1)
|
||||
}
|
||||
fmt.Println("现在:", now.Format("2006年01月02日 15:04:05"))
|
||||
fmt.Println("-------------------------")
|
||||
xq := []string{"日", "一", "二", "三", "四", "五", "六"}
|
||||
fmt.Printf("公历:%s 星期%s\n", date.Format("2006年01月02日 15:04:05"), xq[date.Weekday()])
|
||||
fmt.Println("农历:", gz+str)
|
||||
fmt.Printf("时间戳:%v\n", date.Unix())
|
||||
fmt.Println("-------------------------")
|
||||
diff := date.Sub(now)
|
||||
fmt.Printf("距今: %.5f秒\n", diff.Seconds())
|
||||
fmt.Printf("距今: %.5f分钟\n", diff.Minutes())
|
||||
fmt.Printf("距今: %.5f小时\n", diff.Hours())
|
||||
fmt.Printf("距今: %.5f天\n", diff.Hours()/24)
|
||||
fmt.Printf("距今: %.5f年\n", diff.Hours()/24/365.2425)
|
||||
fmt.Println("距今:", dayDiff(now, date))
|
||||
}
|
||||
|
||||
func dayDiff(date1, date2 time.Time) string {
|
||||
// 提取年、月、日
|
||||
pr := ""
|
||||
if date1.After(date2) {
|
||||
pr = "-"
|
||||
date1, date2 = date2, date1
|
||||
}
|
||||
years, months, days := date2.Date()
|
||||
yearDiff := years - date1.Year()
|
||||
monthDiff := int(months) - int(date1.Month())
|
||||
dayDiff := days - date1.Day()
|
||||
|
||||
// 处理负的月份和日期差
|
||||
if dayDiff < 0 {
|
||||
monthDiff--
|
||||
// 计算上个月的天数
|
||||
prevMonth := date2.AddDate(0, -1, 0)
|
||||
dayDiff += time.Date(prevMonth.Year(), prevMonth.Month(), 0, 0, 0, 0, 0, time.UTC).Day()
|
||||
}
|
||||
if monthDiff < 0 {
|
||||
yearDiff--
|
||||
monthDiff += 12
|
||||
}
|
||||
|
||||
// 提取小时、分钟和秒
|
||||
hours := date2.Hour() - date1.Hour()
|
||||
minutes := date2.Minute() - date1.Minute()
|
||||
seconds := date2.Second() - date1.Second()
|
||||
|
||||
// 处理负的小时、分钟和秒差
|
||||
if seconds < 0 {
|
||||
minutes--
|
||||
seconds += 60
|
||||
}
|
||||
if minutes < 0 {
|
||||
hours--
|
||||
minutes += 60
|
||||
}
|
||||
if hours < 0 {
|
||||
days--
|
||||
hours += 24
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s%d年%d月%d日%d时%d分%d秒", pr, yearDiff, monthDiff, dayDiff, hours, minutes, seconds)
|
||||
}
|
||||
|
||||
func parseDate(base time.Time, date string, isTimestamp bool) (time.Time, error) {
|
||||
if isTimestamp {
|
||||
i, err := strconv.Atoi(date)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return time.Unix(int64(i), 0), nil
|
||||
}
|
||||
if base.IsZero() {
|
||||
base = time.Now()
|
||||
}
|
||||
if strings.HasPrefix(date, "+") || strings.HasPrefix(date, "p") {
|
||||
val, err := staros.Calc(date[1:])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
for val > 9.22e09 {
|
||||
val = val - 9.22e09
|
||||
base = base.Add(9.22e09 * time.Second)
|
||||
}
|
||||
return base.Add(time.Second * time.Duration(int(val))), nil
|
||||
}
|
||||
if strings.HasPrefix(date, "-") || strings.HasPrefix(date, "—") ||
|
||||
strings.HasPrefix(date, "~") || strings.HasPrefix(date, "!") ||
|
||||
strings.HasPrefix(date, "m") {
|
||||
val, err := staros.Calc(date[1:])
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
for val > 9.22e09 {
|
||||
val = val - 9.22e09
|
||||
base = base.Add(-9.22e09 * time.Second)
|
||||
}
|
||||
return base.Add(-1 * time.Second * time.Duration(int(val))), nil
|
||||
}
|
||||
if strings.Contains(date, "-") {
|
||||
|
||||
d, err := time.ParseInLocation("2006-01-02 15:04:05", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("2006-01-02", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("2006-01-02T15:04:05-0700", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("2006-01-02T15:04:05Z", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
return time.Time{}, err
|
||||
}
|
||||
if strings.Contains(date, "/") {
|
||||
d, err := time.ParseInLocation("2006/01/02 15:04:05", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("2006/01/02", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("02/01/2006 15:04:05", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("02/01/2006", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
|
||||
d, err = time.ParseInLocation("01/02/2006 15:04:05", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("01/02/2006", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
return time.Time{}, err
|
||||
}
|
||||
d, err := time.ParseInLocation("20060102150405", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
d, err = time.ParseInLocation("20060102", date, time.Local)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
return time.Time{}, fmt.Errorf("无法解析日期")
|
||||
}
|
37
astro/calendar_test.go
Normal file
37
astro/calendar_test.go
Normal file
@ -0,0 +1,37 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCal(t *testing.T) {
|
||||
//LDateInfo(time.Time{}, time.Date(2021, 1, 1, 0, 0, 0, 0, time.Local), false)
|
||||
//DateInfo(time.Time{}, time.Now().Add(time.Second*-5))
|
||||
for y := 2008; y < 2028; y++ {
|
||||
zq := GuoQing(y)
|
||||
zq.BasicInfo()
|
||||
zq.Detail()
|
||||
fmt.Println("--------")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestChineseHoliday(t *testing.T) {
|
||||
legalData := [][]Holiday{
|
||||
//year 2000
|
||||
{
|
||||
{
|
||||
Start: time.Date(1999, 12, 31, 0, 0, 0, 0, time.Local),
|
||||
End: time.Date(2000, 1, 2, 0, 0, 0, 0, time.Local),
|
||||
Core: time.Date(2000, 1, 1, 0, 0, 0, 0, time.Local),
|
||||
Total: 3,
|
||||
Instead: nil,
|
||||
Name: "元旦",
|
||||
LegalDays: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
_ = legalData
|
||||
}
|
691
astro/chineseday.go
Normal file
691
astro/chineseday.go
Normal file
@ -0,0 +1,691 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"b612.me/astro/calendar"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Holiday struct {
|
||||
//假日开始时间
|
||||
Start time.Time
|
||||
//假日结束时间
|
||||
End time.Time
|
||||
//节日所在日期
|
||||
Core time.Time
|
||||
//假日总天数
|
||||
Total int
|
||||
//调休日期
|
||||
Instead []time.Time
|
||||
//假日名称
|
||||
Name string
|
||||
//法定假日天数
|
||||
LegalDays int
|
||||
Comment string
|
||||
}
|
||||
|
||||
func (h Holiday) BasicInfo() {
|
||||
xq := []string{"日", "一", "二", "三", "四", "五", "六"}
|
||||
_, m, _, str := calendar.RapidSolarToLunar(h.Core)
|
||||
gz := calendar.GanZhi(h.Core.Year())
|
||||
if m > 10 && int(h.Core.Month()) < 3 {
|
||||
gz = calendar.GanZhi(h.Core.Year() - 1)
|
||||
}
|
||||
if h.Start.IsZero() {
|
||||
fmt.Println(h.Name + "无假期")
|
||||
return
|
||||
}
|
||||
fmt.Printf("节日: %s 法定假期: %d天 放假天数: %d天\n", h.Name, h.LegalDays, h.Total)
|
||||
fmt.Println("公历:", h.Core.Format("2006年01月02日"), "星期"+xq[h.Core.Weekday()])
|
||||
fmt.Println("农历:", gz+str)
|
||||
if h.Comment != "" {
|
||||
fmt.Println(h.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
func (h Holiday) Detail() {
|
||||
if h.Start.IsZero() {
|
||||
return
|
||||
}
|
||||
xq := []string{"日", "一", "二", "三", "四", "五", "六"}
|
||||
if h.Start.Equal(h.End) || h.End.IsZero() {
|
||||
fmt.Printf("假期: %s(星期%s) 共%d天\n",
|
||||
h.Start.Format("2006年01月02日"), xq[h.Start.Weekday()], h.Total)
|
||||
} else {
|
||||
fmt.Printf("假期: %s(星期%s) - %s(星期%s) 共%d天\n",
|
||||
h.Start.Format("2006年01月02日"), xq[h.Start.Weekday()],
|
||||
h.End.Format("2006年01月02日"), xq[h.End.Weekday()], h.Total)
|
||||
}
|
||||
if len(h.Instead) > 0 {
|
||||
fmt.Print("调休: ")
|
||||
for idx, v := range h.Instead {
|
||||
fmt.Printf("%s(星期%s)", v.Format("01月02日"), xq[v.Weekday()])
|
||||
if idx != len(h.Instead)-1 {
|
||||
fmt.Print("、")
|
||||
}
|
||||
}
|
||||
fmt.Println(" 上班")
|
||||
return
|
||||
}
|
||||
fmt.Println("不调休")
|
||||
}
|
||||
|
||||
func target3Day1(date time.Time) Holiday {
|
||||
switch date.Weekday() {
|
||||
case 0:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date.Add(-24 * time.Hour),
|
||||
End: date.Add(24 * time.Hour),
|
||||
Total: 3,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 1:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date.Add(-48 * time.Hour),
|
||||
End: date,
|
||||
Total: 3,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 5, 6:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date,
|
||||
End: date.Add(48 * time.Hour),
|
||||
Total: 3,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 3:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date,
|
||||
End: date,
|
||||
Total: 1,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 2:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date.Add(-48 * time.Hour),
|
||||
End: date,
|
||||
Total: 3,
|
||||
Instead: []time.Time{date.Add(-72 * time.Hour)},
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 4:
|
||||
return Holiday{
|
||||
Core: date,
|
||||
Start: date,
|
||||
End: date.Add(48 * time.Hour),
|
||||
Total: 3,
|
||||
Instead: []time.Time{date.Add(72 * time.Hour)},
|
||||
LegalDays: 1,
|
||||
}
|
||||
}
|
||||
return Holiday{}
|
||||
}
|
||||
|
||||
func target5Day1(date time.Time) Holiday {
|
||||
switch date.Weekday() {
|
||||
case 1:
|
||||
return Holiday{
|
||||
Start: date.Add(time.Hour * -48),
|
||||
End: date.Add(time.Hour * 48),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 8), date.Add(time.Hour * 24 * 5)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 2:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * -24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 2), date.Add(time.Hour * 24 * 5)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 3:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 3), date.Add(time.Hour * 24 * 10)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 4:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 4), date.Add(time.Hour * 24 * 9)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 5:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 5), date.Add(time.Hour * 24 * 8)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 6:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 6), date.Add(time.Hour * 24 * 7)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 0:
|
||||
return Holiday{
|
||||
Start: date.Add(time.Hour * -24),
|
||||
End: date.Add(time.Hour * 24 * 3),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 7), date.Add(time.Hour * 24 * 6)},
|
||||
Name: "",
|
||||
LegalDays: 1,
|
||||
}
|
||||
}
|
||||
return Holiday{}
|
||||
}
|
||||
|
||||
func target5Day2(date time.Time) Holiday {
|
||||
switch date.Weekday() {
|
||||
case 1:
|
||||
return Holiday{
|
||||
Start: date.Add(time.Hour * -48),
|
||||
End: date.Add(time.Hour * 48),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 5)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 2:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * -24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 2)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 3:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 3)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 4:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * -24 * 4)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 5:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 8)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 6:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 24 * 4),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 7)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 0:
|
||||
return Holiday{
|
||||
Start: date.Add(time.Hour * -24),
|
||||
End: date.Add(time.Hour * 24 * 3),
|
||||
Core: date,
|
||||
Total: 5,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 6)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
}
|
||||
return Holiday{}
|
||||
}
|
||||
|
||||
func target7Day3(date time.Time) Holiday {
|
||||
switch date.Weekday() {
|
||||
case 1:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -2), date.Add(time.Hour * 24 * -1)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 2:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -2), date.Add(time.Hour * 24 * 11)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 3:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -3), date.Add(time.Hour * 24 * 10)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 4:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -4), date.Add(time.Hour * 24 * 9)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 5:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -5), date.Add(time.Hour * 24 * 8)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 6:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 7), date.Add(time.Hour * 24 * 8)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
case 0:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 6 * 24),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -1), date.Add(time.Hour * 24 * 7)},
|
||||
Name: "",
|
||||
LegalDays: 3,
|
||||
}
|
||||
}
|
||||
return Holiday{}
|
||||
}
|
||||
|
||||
func target8Day4(date time.Time) Holiday {
|
||||
switch date.Weekday() {
|
||||
case 1:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -2), date.Add(time.Hour * 24 * -1)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
case 2:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -2), date.Add(time.Hour * 24 * 11)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
case 3:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -3), date.Add(time.Hour * 24 * 10)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
case 4:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -4), date.Add(time.Hour * 24 * 9)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
case 5:
|
||||
if date.Year() >= 2025 {
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 8 * 24),
|
||||
Core: date,
|
||||
Total: 9,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -5), date.Add(time.Hour * 24 * 9)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
}
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * 8), date.Add(time.Hour * 24 * 9)},
|
||||
Name: "",
|
||||
LegalDays: 4,
|
||||
}
|
||||
case 6:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -6), date.Add(time.Hour * 24 * 8)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
case 0:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(time.Hour * 7 * 24),
|
||||
Core: date,
|
||||
Total: 8,
|
||||
Instead: []time.Time{date.Add(time.Hour * 24 * -1)},
|
||||
Name: "",
|
||||
LegalDays: 2,
|
||||
}
|
||||
}
|
||||
return Holiday{}
|
||||
}
|
||||
|
||||
// 元旦
|
||||
func YuanDan(year int) Holiday {
|
||||
name := "元旦"
|
||||
date := time.Date(year, 1, 1, 0, 0, 0, 0, time.Local)
|
||||
if year > 2007 || year == 2005 {
|
||||
d := target3Day1(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
switch year {
|
||||
case 2007:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(3 * 24 * time.Hour),
|
||||
Core: date,
|
||||
Total: 3,
|
||||
Instead: []time.Time{date.Add(-24 * time.Hour), date.Add(-2 * 24 * time.Hour)},
|
||||
Name: name,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 2006:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(3 * 24 * time.Hour),
|
||||
Core: date,
|
||||
Total: 3,
|
||||
Instead: []time.Time{date.Add(-24 * time.Hour)},
|
||||
Name: name,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 2004, 2003:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date,
|
||||
Core: date,
|
||||
Total: 1,
|
||||
Instead: nil,
|
||||
Name: name,
|
||||
LegalDays: 1,
|
||||
}
|
||||
case 2002:
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(3 * 24 * time.Hour),
|
||||
Core: date,
|
||||
Total: 3,
|
||||
Instead: []time.Time{date.Add(-3 * 24 * time.Hour), date.Add(-2 * 24 * time.Hour)},
|
||||
Name: name,
|
||||
LegalDays: 1,
|
||||
}
|
||||
}
|
||||
return Holiday{Name: name}
|
||||
}
|
||||
|
||||
// 春节
|
||||
func ChunJie(year int) Holiday {
|
||||
name := "春节"
|
||||
chineseNewYear := calendar.RapidLunarToSolar(year, 1, 1, false)
|
||||
chuxi := chineseNewYear.AddDate(0, 0, -1)
|
||||
if year <= 2007 || year == 2014 {
|
||||
d := target7Day3(chineseNewYear)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
if year >= 2008 && year < 2024 {
|
||||
d := target7Day3(chuxi)
|
||||
d.Name = name
|
||||
if year == 2020 {
|
||||
d.Comment = "因新冠疫情防控,2020年春节延长至2月2日"
|
||||
}
|
||||
return d
|
||||
}
|
||||
if year == 2024 {
|
||||
d := target8Day4(chineseNewYear)
|
||||
d.LegalDays = 3
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
d := target8Day4(chuxi)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
|
||||
func QingMing(year int) Holiday {
|
||||
name := "清明节"
|
||||
if year < 2008 {
|
||||
return Holiday{Name: name}
|
||||
}
|
||||
qingming := calendar.JieQi(year, calendar.JQ_清明)
|
||||
d := target3Day1(qingming)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
|
||||
func LaoDongJie(year int) Holiday {
|
||||
name := "劳动节"
|
||||
date := time.Date(year, 5, 1, 0, 0, 0, 0, time.Local)
|
||||
if year < 2008 {
|
||||
d := target7Day3(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
if year == 2019 {
|
||||
return Holiday{
|
||||
Start: date,
|
||||
End: date.Add(4 * 24 * time.Hour),
|
||||
Core: date,
|
||||
Total: 4,
|
||||
Instead: []time.Time{date.Add(-24 * 3 * time.Hour), date.Add(24 * time.Hour * 5)},
|
||||
Name: name,
|
||||
LegalDays: 1,
|
||||
Comment: "",
|
||||
}
|
||||
}
|
||||
if year >= 2008 && year < 2020 {
|
||||
d := target3Day1(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
if year >= 2020 && year < 2025 {
|
||||
d := target5Day1(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
d := target5Day2(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
|
||||
func DuanWu(year int) Holiday {
|
||||
name := "端午节"
|
||||
if year < 2008 {
|
||||
return Holiday{Name: name}
|
||||
}
|
||||
date := calendar.RapidLunarToSolar(year, 5, 5, false)
|
||||
d := target3Day1(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
|
||||
func ZhongQiu(year int) Holiday {
|
||||
name := "中秋节"
|
||||
if year < 2008 {
|
||||
return Holiday{Name: name}
|
||||
}
|
||||
date := calendar.RapidLunarToSolar(year, 8, 15, false)
|
||||
if date.Month() == 9 && date.Day() >= 28 {
|
||||
d := target8Day4(date)
|
||||
d.Name = "国庆中秋连假"
|
||||
return d
|
||||
} else if date.Month() == 10 {
|
||||
d := target8Day4(time.Date(year, 10, 1, 0, 0, 0, 0, time.Local))
|
||||
d.Name = "国庆中秋连假"
|
||||
return d
|
||||
}
|
||||
d := target3Day1(date)
|
||||
d.Name = name
|
||||
if date.Month() == 9 && date.Day() <= 20 {
|
||||
return d
|
||||
}
|
||||
if date.Weekday() == 3 {
|
||||
d.Instead = []time.Time{date.Add(-24 * 3 * time.Hour), date.Add(24 * 3 * time.Hour)}
|
||||
d.End = date.Add(2 * 24 * time.Hour)
|
||||
d.Total = 3
|
||||
}
|
||||
gq := target7Day3(time.Date(year, 10, 1, 0, 0, 0, 0, time.Local))
|
||||
for _, v := range gq.Instead {
|
||||
if v.Before(d.End) || v.Equal(d.End) {
|
||||
if v.Equal(date) {
|
||||
d.Total = d.Total - 1
|
||||
d.End = d.End.Add(-24 * time.Hour)
|
||||
} else {
|
||||
if v.Equal(d.Start) {
|
||||
d.Total = d.Total - 1
|
||||
d.Start = d.Start.Add(24 * time.Hour)
|
||||
} else if v.Equal(d.End) {
|
||||
d.Total = d.Total - 1
|
||||
d.End = d.End.Add(-24 * time.Hour)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func GuoQing(year int) Holiday {
|
||||
name := "国庆节"
|
||||
date := time.Date(year, 10, 1, 0, 0, 0, 0, time.Local)
|
||||
zq := calendar.RapidLunarToSolar(year, 8, 15, false)
|
||||
if year == 2008 {
|
||||
return Holiday{
|
||||
Start: date.Add(-24 * 2 * time.Hour),
|
||||
End: date.Add(4 * 24 * time.Hour),
|
||||
Core: date,
|
||||
Total: 7,
|
||||
Instead: []time.Time{date.Add(-24 * 4 * time.Hour), date.Add(24 * -3 * time.Hour)},
|
||||
Name: name,
|
||||
LegalDays: 3,
|
||||
}
|
||||
}
|
||||
if year < 2008 || (zq.Month() == 9 && zq.Day() <= 20) {
|
||||
d := target7Day3(date)
|
||||
d.Name = name
|
||||
return d
|
||||
}
|
||||
|
||||
if zq.Month() == 9 && zq.Day() >= 28 {
|
||||
d := target8Day4(zq)
|
||||
d.Name = "国庆中秋连假"
|
||||
return d
|
||||
} else if zq.Month() == 10 {
|
||||
d := target8Day4(date)
|
||||
d.Name = "国庆中秋连假"
|
||||
return d
|
||||
}
|
||||
zqd := target3Day1(zq)
|
||||
if date.Weekday() == 3 {
|
||||
zqd.Instead = []time.Time{date.Add(-24 * 3 * time.Hour), date.Add(24 * 3 * time.Hour)}
|
||||
zqd.End = date.Add(2 * 24 * time.Hour)
|
||||
zqd.Total = 3
|
||||
}
|
||||
d := target7Day3(date)
|
||||
d.Name = name
|
||||
for k, v := range d.Instead {
|
||||
if v.Before(zqd.End) || v.Equal(zqd.End) {
|
||||
if v.Equal(zq) {
|
||||
d.Instead = append(d.Instead[:k], d.Instead[k+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func ChineseHoliday(year int) []Holiday {
|
||||
d := []Holiday{
|
||||
YuanDan(year),
|
||||
ChunJie(year),
|
||||
QingMing(year),
|
||||
LaoDongJie(year),
|
||||
DuanWu(year),
|
||||
ZhongQiu(year),
|
||||
GuoQing(year),
|
||||
}
|
||||
if d[len(d)-1].Name == "国庆中秋连假" {
|
||||
return d[:len(d)-1]
|
||||
}
|
||||
return d
|
||||
}
|
2024
astro/city.json
Normal file
2024
astro/city.json
Normal file
File diff suppressed because it is too large
Load Diff
253
astro/cmd.go
Normal file
253
astro/cmd.go
Normal file
@ -0,0 +1,253 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"time"
|
||||
)
|
||||
|
||||
var isFormat bool
|
||||
var jieqi string
|
||||
|
||||
func init() {
|
||||
Cmd.PersistentFlags().Float64Var(&lon, "lon", -273, "经度,WGS84坐标系")
|
||||
Cmd.PersistentFlags().Float64Var(&lat, "lat", -273, "纬度,WGS84坐标系")
|
||||
Cmd.PersistentFlags().Float64Var(&height, "height", 0, "海拔高度")
|
||||
CmdSun.Flags().StringVarP(&nowDay, "now", "n", "", "指定现在的时间")
|
||||
CmdSun.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "是否为时间戳")
|
||||
CmdSun.Flags().BoolVarP(&isLive, "live", "v", false, "是否为实时")
|
||||
CmdSun.Flags().BoolVarP(&isFormat, "format", "f", false, "格式化输出")
|
||||
CmdSun.Flags().StringVarP(&city, "city", "c", "", "城市名")
|
||||
|
||||
CmdMoon.Flags().StringVarP(&nowDay, "now", "n", "", "指定现在的时间")
|
||||
CmdMoon.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "是否为时间戳")
|
||||
CmdMoon.Flags().BoolVarP(&isLive, "live", "v", false, "是否为实时")
|
||||
CmdMoon.Flags().BoolVarP(&isFormat, "format", "f", false, "格式化输出")
|
||||
CmdMoon.Flags().StringVarP(&city, "city", "c", "", "城市名")
|
||||
|
||||
CmdStar.Flags().StringVarP(&nowDay, "now", "n", "", "指定现在的时间")
|
||||
CmdStar.Flags().BoolVarP(&isTimestamp, "timestamp", "t", false, "是否为时间戳")
|
||||
CmdStar.Flags().BoolVarP(&isLive, "live", "v", false, "是否为实时")
|
||||
CmdStar.Flags().BoolVarP(&isFormat, "format", "f", false, "格式化输出")
|
||||
CmdStar.Flags().StringVarP(&city, "city", "c", "", "城市名")
|
||||
|
||||
Cmd.AddCommand(CmdCal, CmdSun, CmdMoon, CmdStar)
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "astro",
|
||||
Short: "天文计算",
|
||||
}
|
||||
|
||||
var CmdSun = &cobra.Command{
|
||||
Use: "sun",
|
||||
Short: "太阳计算",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
format := 0
|
||||
if isFormat {
|
||||
format = 1
|
||||
}
|
||||
isSet := CliLoadLonLatHeight()
|
||||
var now = time.Now()
|
||||
var err error
|
||||
if nowDay != "" {
|
||||
now, err = parseDate(now, nowDay, isTimestamp)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
if isSet {
|
||||
fmt.Printf("经度: %f 纬度: %f 海拔: %f\n", lon, lat, height)
|
||||
}
|
||||
BasicSun(now, uint8(format))
|
||||
if isSet {
|
||||
SunDetail(now, lon, lat, height, uint8(format))
|
||||
}
|
||||
if !isLive {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Nanosecond*
|
||||
time.Duration(1000000000-time.Now().Nanosecond()) + 1)
|
||||
if nowDay == "" {
|
||||
now = time.Now()
|
||||
now = now.Add(time.Duration(now.Nanosecond()*-1) * time.Nanosecond)
|
||||
} else {
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
ClearScreen()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdMoon = &cobra.Command{
|
||||
Use: "moon",
|
||||
Short: "月亮计算",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
format := 0
|
||||
if isFormat {
|
||||
format = 1
|
||||
}
|
||||
isSet := CliLoadLonLatHeight()
|
||||
var now = time.Now()
|
||||
var err error
|
||||
if nowDay != "" {
|
||||
now, err = parseDate(now, nowDay, isTimestamp)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
if isSet {
|
||||
fmt.Printf("经度: %f 纬度: %f 海拔: %f\n", lon, lat, height)
|
||||
}
|
||||
BasicMoon(now, uint8(format))
|
||||
if isSet {
|
||||
MoonDetail(now, lon, lat, height, uint8(format))
|
||||
}
|
||||
if !isLive {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Nanosecond*
|
||||
time.Duration(1000000000-time.Now().Nanosecond()) + 1)
|
||||
if nowDay == "" {
|
||||
now = time.Now()
|
||||
now = now.Add(time.Duration(now.Nanosecond()*-1) * time.Nanosecond)
|
||||
} else {
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
ClearScreen()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdStar = &cobra.Command{
|
||||
Use: "star",
|
||||
Short: "星星计算",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Println("请输入星星名字")
|
||||
return
|
||||
}
|
||||
format := 0
|
||||
if isFormat {
|
||||
format = 1
|
||||
}
|
||||
isSet := CliLoadLonLatHeight()
|
||||
var now = time.Now()
|
||||
var err error
|
||||
if nowDay != "" {
|
||||
now, err = parseDate(now, nowDay, isTimestamp)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
for {
|
||||
if isSet {
|
||||
fmt.Printf("经度: %f 纬度: %f 海拔: %f\n", lon, lat, height)
|
||||
}
|
||||
BasicStar(now, args[0], uint8(format))
|
||||
if isSet {
|
||||
StarDetail(now, args[0], lon, lat, height, uint8(format))
|
||||
}
|
||||
if !isLive {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Nanosecond*
|
||||
time.Duration(1000000000-time.Now().Nanosecond()) + 1)
|
||||
if nowDay == "" {
|
||||
now = time.Now()
|
||||
now = now.Add(time.Duration(now.Nanosecond()*-1) * time.Nanosecond)
|
||||
} else {
|
||||
now = now.Add(time.Second)
|
||||
}
|
||||
ClearScreen()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdJieqi = &cobra.Command{
|
||||
Use: "jq",
|
||||
Short: "节气计算",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
fmt.Println("请输入年份或节气")
|
||||
return
|
||||
}
|
||||
year := args[0]
|
||||
if year == "" {
|
||||
year = time.Now().Format("2006")
|
||||
}
|
||||
year = year[:4]
|
||||
fmt.Println("年份: ", year)
|
||||
/*
|
||||
var jqname = map[string]int{
|
||||
"春分": 0,
|
||||
"清明": 15,
|
||||
"谷雨": 30,
|
||||
"立夏": 45,
|
||||
"小满": 60,
|
||||
"芒种": 75,
|
||||
"夏至": 90,
|
||||
"小暑": 105,
|
||||
"大暑": 120,
|
||||
"立秋": 135,
|
||||
"处暑": 150,
|
||||
"白露": 165,
|
||||
"秋分": 180,
|
||||
"寒露": 195,
|
||||
"霜降": 210,
|
||||
"立冬": 225,
|
||||
"小雪": 240,
|
||||
"大雪": 255,
|
||||
"冬至": 270,
|
||||
"小寒": 285,
|
||||
"大寒": 300,
|
||||
"立春": 315,
|
||||
"雨水": 330,
|
||||
"惊蛰": 345,
|
||||
}
|
||||
if jieqi != "" {
|
||||
if v, ok := jqname[jieqi]; !ok {
|
||||
fmt.Println("节气名错误")
|
||||
return
|
||||
} else {
|
||||
fmt.Println("节气名: ", jieqi)
|
||||
fmt.Println("时间: ", calendar.JieQi(year, v))
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
},
|
||||
}
|
||||
|
||||
func CliLoadLonLatHeight() bool {
|
||||
if city != "" {
|
||||
if !GetFromCity(city) {
|
||||
fmt.Println("城市名错误")
|
||||
return false
|
||||
}
|
||||
fmt.Println("城市名: ", city)
|
||||
SetLonLatHeight(lon, lat, height)
|
||||
return true
|
||||
}
|
||||
tlon, tlat, theight := lon, lat, height
|
||||
LoadLonLatHeight()
|
||||
if lon == -273 && lon != tlon {
|
||||
lon = tlon
|
||||
}
|
||||
if lat == -273 && lat != tlat {
|
||||
lat = tlat
|
||||
}
|
||||
if height == 0 && height != theight {
|
||||
height = theight
|
||||
}
|
||||
SetLonLatHeight(lon, lat, height)
|
||||
if lon == -273 || lat == -273 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
1
astro/jieqi.go
Normal file
1
astro/jieqi.go
Normal file
@ -0,0 +1 @@
|
||||
package astro
|
130
astro/moon.go
Normal file
130
astro/moon.go
Normal file
@ -0,0 +1,130 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"b612.me/astro/moon"
|
||||
"b612.me/astro/star"
|
||||
"b612.me/astro/sun"
|
||||
"b612.me/astro/tools"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BasicMoon(date time.Time, format uint8) {
|
||||
fmt.Printf("时间: %s\n", date.Format("2006-01-02 15:04:05"))
|
||||
lo := moon.ApparentLo(date)
|
||||
eo := moon.Phase(date)
|
||||
bo := moon.TrueBo(date)
|
||||
fmt.Println("月亮")
|
||||
fmt.Println("------------------------")
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("黄经: %f\n", lo)
|
||||
fmt.Printf("黄纬: %f\n", bo)
|
||||
case 1:
|
||||
flo := tools.Format(lo, 0)
|
||||
fbo := tools.Format(bo, 0)
|
||||
fmt.Printf("黄经: %s\n", flo)
|
||||
fmt.Printf("黄纬: %s\n", fbo)
|
||||
}
|
||||
phaseStr := ""
|
||||
mslo := tools.Limit360(lo - sun.ApparentLo(date))
|
||||
if mslo >= 0 && mslo <= 30 {
|
||||
phaseStr = "新月"
|
||||
} else if mslo > 30 && mslo <= 75 {
|
||||
phaseStr = "上峨眉月"
|
||||
} else if mslo > 75 && mslo <= 135 {
|
||||
phaseStr = "上弦月"
|
||||
} else if mslo > 135 && mslo < 170 {
|
||||
phaseStr = "盈凸月"
|
||||
} else if mslo >= 170 && mslo <= 190 {
|
||||
phaseStr = "满月"
|
||||
} else if mslo > 190 && mslo < 225 {
|
||||
phaseStr = "亏凸月"
|
||||
} else if mslo >= 225 && mslo < 285 {
|
||||
phaseStr = "下弦月"
|
||||
} else if mslo >= 285 && mslo < 330 {
|
||||
phaseStr = "下峨眉月"
|
||||
} else {
|
||||
phaseStr = "残月"
|
||||
}
|
||||
|
||||
fmt.Printf("月相: %.2f%% %s\n", eo*100, phaseStr)
|
||||
sx := moon.NextShangXianYue(date)
|
||||
xx := moon.NextXiaXianYue(date)
|
||||
wang := moon.NextWangYue(date)
|
||||
shuo := moon.NextShuoYue(date)
|
||||
fmt.Printf("朔月: %s\n", shuo.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("上弦: %s\n", sx.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("望月: %s\n", wang.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("下弦: %s\n", xx.Format("2006-01-02 15:04:05"))
|
||||
|
||||
}
|
||||
|
||||
func MoonDetail(date time.Time, lon, lat, height float64, format uint8) {
|
||||
var err error
|
||||
ra, dec := moon.ApparentRaDec(date, lon, lat)
|
||||
tmp := new(time.Time)
|
||||
*tmp, err = moon.RiseTime(date, lon, lat, height, true)
|
||||
if err != nil {
|
||||
if err == moon.ERR_NOT_TODAY {
|
||||
*tmp, err = moon.RiseTime(date.AddDate(0, 0, -1), lon, lat, 0, true)
|
||||
if err != nil {
|
||||
*tmp = time.Time{}
|
||||
}
|
||||
}
|
||||
}
|
||||
rise := *tmp
|
||||
tmp = new(time.Time)
|
||||
*tmp, err = moon.DownTime(date, lon, lat, 0, true)
|
||||
if err != nil {
|
||||
if err == moon.ERR_NOT_TODAY {
|
||||
*tmp, err = moon.DownTime(date.AddDate(0, 0, 1), lon, lat, 0, true)
|
||||
if err != nil {
|
||||
*tmp = time.Time{}
|
||||
}
|
||||
}
|
||||
}
|
||||
if tmp.Before(rise) {
|
||||
tmp = new(time.Time)
|
||||
*tmp, err = moon.DownTime(date.AddDate(0, 0, 1), lon, lat, 0, true)
|
||||
if err != nil {
|
||||
if err == moon.ERR_NOT_TODAY {
|
||||
*tmp, err = moon.DownTime(date.AddDate(0, 0, 2), lon, lat, 0, true)
|
||||
if err != nil {
|
||||
*tmp = time.Time{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
set := tmp
|
||||
cst := star.Constellation(ra, dec, date)
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("视赤经: %s\n", ra)
|
||||
fmt.Printf("视赤纬: %s\n", dec)
|
||||
case 1:
|
||||
fra := tools.Format(ra/15, 1)
|
||||
fdec := tools.Format(dec, 0)
|
||||
fmt.Printf("视赤经: %s\n", fra)
|
||||
fmt.Printf("视赤纬: %s\n", fdec)
|
||||
}
|
||||
fmt.Printf("星座: %s\n", cst)
|
||||
fmt.Printf("升起: %s\n", rise.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("落下: %s\n", set.Format("2006-01-02 15:04:05"))
|
||||
az := moon.Azimuth(date, lon, lat)
|
||||
alt := moon.Zenith(date, lon, lat)
|
||||
ta := moon.HourAngle(date, lon, lat)
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("方位角: %f\n", az)
|
||||
fmt.Printf("高度角: %f\n", alt)
|
||||
fmt.Printf("时角: %f\n", ta)
|
||||
case 1:
|
||||
faz := tools.Format(az, 0)
|
||||
falt := tools.Format(alt, 0)
|
||||
fta := tools.Format(ta/15, 1)
|
||||
fmt.Printf("方位角: %s\n", faz)
|
||||
fmt.Printf("高度角: %s\n", falt)
|
||||
fmt.Printf("时角: %s\n", fta)
|
||||
}
|
||||
}
|
101
astro/star.go
Normal file
101
astro/star.go
Normal file
@ -0,0 +1,101 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"b612.me/astro/star"
|
||||
"b612.me/astro/tools"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BasicStar(date time.Time, name string, format uint8) {
|
||||
fmt.Printf("时间: %s\n", date.Format("2006-01-02 15:04:05"))
|
||||
s, err := star.StarDataByName(name)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
ra, dec := s.RaDecByDate(date)
|
||||
fmt.Printf("%s %s 星等:%.2f\n", s.ChineseName, s.Name, s.Mag)
|
||||
fmt.Println("------------------------")
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("视赤经: %f\n", ra)
|
||||
fmt.Printf("视赤纬: %f\n", dec)
|
||||
case 1:
|
||||
fra := tools.Format(ra/15, 1)
|
||||
fdec := tools.Format(dec, 0)
|
||||
fmt.Printf("视赤经: %s\n", fra)
|
||||
fmt.Printf("视赤纬: %s\n", fdec)
|
||||
}
|
||||
cst := star.Constellation(ra, dec, date)
|
||||
fmt.Printf("星座: %s\n", cst)
|
||||
}
|
||||
|
||||
func StarDetail(date time.Time, name string, lon, lat, height float64, format uint8) {
|
||||
s, err := star.StarDataByName(name)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
ra, dec := s.RaDecByDate(date)
|
||||
alt := star.Zenith(date, ra, dec, lon, lat)
|
||||
ta := star.HourAngle(date, ra, lon)
|
||||
zt := star.CulminationTime(date, ra, lon)
|
||||
az := star.Azimuth(date, ra, dec, lon, lat)
|
||||
var rise, set time.Time
|
||||
var duration time.Duration
|
||||
for {
|
||||
nk := date.Add(duration)
|
||||
rise, err = star.RiseTime(nk, ra, dec, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if alt > 0 && rise.After(nk) {
|
||||
rise, err = star.RiseTime(nk.Add(time.Hour*-24), ra, dec, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
} else if alt < 0 && rise.Before(nk) {
|
||||
rise, err = star.RiseTime(nk.Add(time.Hour*24), ra, dec, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
}
|
||||
set, err = star.DownTime(nk, ra, dec, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
if set.Before(rise) {
|
||||
set, err = star.DownTime(nk.Add(time.Hour*24), ra, dec, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
if set.Before(date) {
|
||||
duration += time.Hour * 24
|
||||
continue
|
||||
}
|
||||
if zt.Before(rise) {
|
||||
zt = star.CulminationTime(nk.Add(time.Hour*24), ra, lon)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
fmt.Printf("升起: %s\n", rise.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("中天: %s\n", zt.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("落下: %s\n", set.Format("2006-01-02 15:04:05"))
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("方位角: %f\n", az)
|
||||
fmt.Printf("高度角: %f\n", alt)
|
||||
fmt.Printf("时角: %f\n", ta)
|
||||
case 1:
|
||||
faz := tools.Format(az, 0)
|
||||
falt := tools.Format(alt, 0)
|
||||
fta := tools.Format(ta/15, 1)
|
||||
fmt.Printf("方位角: %s\n", faz)
|
||||
fmt.Printf("高度角: %s\n", falt)
|
||||
fmt.Printf("时角: %s\n", fta)
|
||||
}
|
||||
}
|
76
astro/sun.go
Normal file
76
astro/sun.go
Normal file
@ -0,0 +1,76 @@
|
||||
package astro
|
||||
|
||||
import (
|
||||
"b612.me/astro/star"
|
||||
"b612.me/astro/sun"
|
||||
"b612.me/astro/tools"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BasicSun(date time.Time, format uint8) {
|
||||
fmt.Printf("时间: %s\n", date.Format("2006-01-02 15:04:05"))
|
||||
ra, dec := sun.ApparentRaDec(date)
|
||||
lo := sun.ApparentLo(date)
|
||||
eo := sun.EclipticObliquity(date, true)
|
||||
fmt.Println("太阳")
|
||||
fmt.Println("------------------------")
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("视赤经: %f\n", ra)
|
||||
fmt.Printf("视赤纬: %f\n", dec)
|
||||
fmt.Printf("视黄经: %f\n", lo)
|
||||
fmt.Printf("黄赤交角: %f\n", eo)
|
||||
case 1:
|
||||
fra := tools.Format(ra/15, 1)
|
||||
fdec := tools.Format(dec, 0)
|
||||
flo := tools.Format(lo, 0)
|
||||
feo := tools.Format(eo, 0)
|
||||
fmt.Printf("视赤经: %s\n", fra)
|
||||
fmt.Printf("视赤纬: %s\n", fdec)
|
||||
fmt.Printf("视黄经: %s\n", flo)
|
||||
fmt.Printf("黄赤交角: %s\n", feo)
|
||||
}
|
||||
cst := star.Constellation(ra, dec, date)
|
||||
fmt.Printf("星座: %s\n", cst)
|
||||
}
|
||||
|
||||
func SunDetail(date time.Time, lon, lat, height float64, format uint8) {
|
||||
rise, err := sun.RiseTime(date, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
set, err := sun.DownTime(date, lon, lat, height, true)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
morning, err := sun.MorningTwilight(date, lon, lat, -6)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
evening, err := sun.EveningTwilight(date, lon, lat, -6)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
|
||||
}
|
||||
fmt.Printf("晨朦影: %s\n", morning.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("升起: %s\n", rise.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("落下: %s\n", set.Format("2006-01-02 15:04:05"))
|
||||
fmt.Printf("昏朦影: %s\n", evening.Format("2006-01-02 15:04:05"))
|
||||
az := sun.Azimuth(date, lon, lat)
|
||||
alt := sun.Zenith(date, lon, lat)
|
||||
ta := sun.HourAngle(date, lon, lat)
|
||||
switch format {
|
||||
case 0:
|
||||
fmt.Printf("方位角: %f\n", az)
|
||||
fmt.Printf("高度角: %f\n", alt)
|
||||
fmt.Printf("时角: %f\n", ta)
|
||||
case 1:
|
||||
faz := tools.Format(az, 0)
|
||||
falt := tools.Format(alt, 0)
|
||||
fta := tools.Format(ta/15, 1)
|
||||
fmt.Printf("方位角: %s\n", faz)
|
||||
fmt.Printf("高度角: %s\n", falt)
|
||||
fmt.Printf("时角: %s\n", fta)
|
||||
}
|
||||
}
|
12
cert/ca.go
12
cert/ca.go
@ -50,3 +50,15 @@ func LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Cert
|
||||
}
|
||||
return caKey, cert, nil
|
||||
}
|
||||
|
||||
func LoadPriv(caKeyPath, KeyPwd string) (crypto.PrivateKey, error) {
|
||||
caKeyBytes, err := os.ReadFile(caKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
caKey, err := starcrypto.DecodePrivateKey(caKeyBytes, KeyPwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return caKey, nil
|
||||
}
|
||||
|
110
cert/cert.go
110
cert/cert.go
@ -15,6 +15,7 @@ import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"os"
|
||||
"reflect"
|
||||
"software.sslmate.com/src/go-pkcs12"
|
||||
"strings"
|
||||
)
|
||||
@ -104,9 +105,9 @@ func ParseCert(data []byte, pwd string) {
|
||||
switch n := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
starlog.Green("这是一个RSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Size())
|
||||
starlog.Green("私钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("私钥指数:%d\n", n.E)
|
||||
starlog.Green("公钥位数:%d\n", n.Size())
|
||||
starlog.Green("公钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("公钥指数:%d\n", n.E)
|
||||
starlog.Green("私钥系数:%d\n", n.D)
|
||||
starlog.Green("私钥质数p:%d\n", n.Primes[0])
|
||||
starlog.Green("私钥质数q:%d\n", n.Primes[1])
|
||||
@ -115,8 +116,8 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("私钥系数qInv:%d\n", n.Precomputed.Qinv)
|
||||
case *ecdsa.PrivateKey:
|
||||
starlog.Green("这是一个ECDSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
|
||||
starlog.Green("私钥曲线:%s\n", n.Curve.Params().Name)
|
||||
starlog.Green("公钥位数:%d\n", n.Curve.Params().BitSize)
|
||||
starlog.Green("公钥曲线:%s\n", n.Curve.Params().Name)
|
||||
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
|
||||
starlog.Green("私钥系数:%d\n", n.D)
|
||||
starlog.Green("私钥公钥X:%d\n", n.PublicKey.X)
|
||||
@ -125,7 +126,7 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
starlog.Green("私钥系数:%d\n", n.X)
|
||||
starlog.Green("私钥公钥Y:%d\n", n.Y)
|
||||
case *ed25519.PrivateKey:
|
||||
case *ed25519.PrivateKey, ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
@ -208,7 +209,7 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("公钥公钥Y:%d\n", n.Y)
|
||||
case *ecdh.PublicKey:
|
||||
starlog.Green("公钥算法为ECDH\n")
|
||||
case *ed25519.PublicKey:
|
||||
case *ed25519.PublicKey, ed25519.PublicKey:
|
||||
starlog.Green("公钥算法为ED25519\n")
|
||||
default:
|
||||
starlog.Green("未知公钥类型\n")
|
||||
@ -236,9 +237,9 @@ func ParseCert(data []byte, pwd string) {
|
||||
switch n := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
starlog.Green("这是一个RSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Size())
|
||||
starlog.Green("私钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("私钥指数:%d\n", n.E)
|
||||
starlog.Green("公钥位数:%d\n", n.Size())
|
||||
starlog.Green("公钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("公钥指数:%d\n", n.E)
|
||||
starlog.Green("私钥系数:%d\n", n.D)
|
||||
starlog.Green("私钥质数p:%d\n", n.Primes[0])
|
||||
starlog.Green("私钥质数q:%d\n", n.Primes[1])
|
||||
@ -257,12 +258,18 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
starlog.Green("私钥系数:%d\n", n.X)
|
||||
starlog.Green("私钥公钥Y:%d\n", n.Y)
|
||||
case ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
|
||||
starlog.Green("公钥:%s\n", string(sshPub))
|
||||
case *ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
|
||||
starlog.Green("公钥:%s\n", string(sshPub))
|
||||
case ecdh.PrivateKey:
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
default:
|
||||
starlog.Green("未知私钥类型\n")
|
||||
starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
|
||||
}
|
||||
continue
|
||||
|
||||
@ -371,9 +378,9 @@ func ParseCert(data []byte, pwd string) {
|
||||
switch n := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
starlog.Green("这是一个RSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Size())
|
||||
starlog.Green("私钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("私钥指数:%d\n", n.E)
|
||||
starlog.Green("公钥位数:%d\n", n.Size())
|
||||
starlog.Green("公钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("公钥指数:%d\n", n.E)
|
||||
starlog.Green("私钥系数:%d\n", n.D)
|
||||
starlog.Green("私钥质数p:%d\n", n.Primes[0])
|
||||
starlog.Green("私钥质数q:%d\n", n.Primes[1])
|
||||
@ -392,12 +399,18 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
starlog.Green("私钥系数:%d\n", n.X)
|
||||
starlog.Green("私钥公钥Y:%d\n", n.Y)
|
||||
case ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
|
||||
starlog.Green("公钥:%s\n", string(sshPub))
|
||||
case *ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
|
||||
starlog.Green("公钥:%s\n", string(sshPub))
|
||||
case ecdh.PrivateKey:
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
default:
|
||||
starlog.Green("未知私钥类型\n")
|
||||
starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
|
||||
}
|
||||
continue
|
||||
case "OPENSSH PUBLIC KEY":
|
||||
@ -410,7 +423,7 @@ func ParseCert(data []byte, pwd string) {
|
||||
starlog.Green("公钥算法:%s\n", pub.Type())
|
||||
continue
|
||||
default:
|
||||
starlog.Infof("未知证书文件类型\n")
|
||||
starlog.Infof("不支持的证书文件类型:%v\n", reflect.TypeOf(block))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -546,9 +559,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
|
||||
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
|
||||
case *dsa.PrivateKey:
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
case *ed25519.PrivateKey:
|
||||
case ed25519.PrivateKey, *ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
case ecdh.PrivateKey, *ecdh.PrivateKey:
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
default:
|
||||
starlog.Green("未知私钥类型\n")
|
||||
@ -627,15 +640,15 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
|
||||
switch n := priv.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
starlog.Green("这是一个RSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Size())
|
||||
starlog.Green("公钥位数:%d\n", n.Size())
|
||||
case *ecdsa.PrivateKey:
|
||||
starlog.Green("这是一个ECDSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
|
||||
case *dsa.PrivateKey:
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
case *ed25519.PrivateKey:
|
||||
case ed25519.PrivateKey, *ed25519.PrivateKey:
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
case ecdh.PrivateKey:
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
default:
|
||||
starlog.Green("未知私钥类型\n")
|
||||
@ -747,8 +760,8 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
|
||||
case *rsa.PrivateKey:
|
||||
common = append(common, n)
|
||||
starlog.Green("这是一个RSA私钥\n")
|
||||
starlog.Green("私钥位数:%d\n", n.Size())
|
||||
starlog.Green("私钥长度:%d\n", n.N.BitLen())
|
||||
starlog.Green("公钥位数:%d\n", n.Size())
|
||||
starlog.Green("公钥长度:%d\n", n.N.BitLen())
|
||||
case *ecdsa.PrivateKey:
|
||||
common = append(common, n)
|
||||
starlog.Green("这是一个ECDSA私钥\n")
|
||||
@ -756,10 +769,10 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
|
||||
case *dsa.PrivateKey:
|
||||
common = append(common, n)
|
||||
starlog.Green("这是一个DSA私钥\n")
|
||||
case *ed25519.PrivateKey:
|
||||
case ed25519.PrivateKey, *ed25519.PrivateKey:
|
||||
common = append(common, n)
|
||||
starlog.Green("这是一个ED25519私钥\n")
|
||||
case *ecdh.PrivateKey:
|
||||
case ecdh.PrivateKey:
|
||||
common = append(common, n)
|
||||
starlog.Green("这是一个ECDH私钥\n")
|
||||
default:
|
||||
@ -777,28 +790,42 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(len(keys))
|
||||
for _, v := range keys {
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
data, err = x509.MarshalPKCS8PrivateKey(n)
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey, ecdh.PrivateKey:
|
||||
var key interface{} = n
|
||||
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
|
||||
fmt.Println("1")
|
||||
key = *(n.(*ed25519.PrivateKey))
|
||||
}
|
||||
fmt.Println("2")
|
||||
data, err = x509.MarshalPKCS8PrivateKey(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("3")
|
||||
var block *pem.Block
|
||||
if newPwd != "" {
|
||||
block, err = x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", data, []byte(newPwd), x509.PEMCipherAES256)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
block = &pem.Block{Type: "PRIVATE KEY", Bytes: data}
|
||||
}
|
||||
fmt.Println("4")
|
||||
err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(block), 0644)
|
||||
if err != nil {
|
||||
fmt.Println("5")
|
||||
return err
|
||||
} else {
|
||||
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8")
|
||||
}
|
||||
fmt.Println("6")
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
|
||||
data, err = x509.MarshalPKIXPublicKey(n)
|
||||
if err != nil {
|
||||
@ -810,6 +837,8 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
|
||||
} else {
|
||||
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("未知的密钥类型:%v", reflect.TypeOf(n))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -865,8 +894,11 @@ func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName strin
|
||||
continue
|
||||
}
|
||||
switch n := v.(type) {
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
priv = n
|
||||
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
|
||||
priv = *(n.(*ed25519.PrivateKey))
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -927,7 +959,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
|
||||
} else {
|
||||
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx))
|
||||
}
|
||||
case *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
case *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey:
|
||||
data, err = x509.MarshalPKCS8PrivateKey(n)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -938,7 +970,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
|
||||
} else {
|
||||
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key")
|
||||
}
|
||||
case *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
|
||||
case *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
|
||||
data, err = x509.MarshalPKIXPublicKey(n)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -966,11 +998,15 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
|
||||
}
|
||||
var block *pem.Block
|
||||
switch n := v.(type) {
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
if newPwd != "" {
|
||||
block, err = ssh.MarshalPrivateKey(n, "")
|
||||
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
|
||||
var key interface{} = n
|
||||
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
|
||||
key = *(n.(*ed25519.PrivateKey))
|
||||
}
|
||||
if newPwd == "" {
|
||||
block, err = ssh.MarshalPrivateKey(key, "")
|
||||
} else {
|
||||
block, err = ssh.MarshalPrivateKeyWithPassphrase(n, "", []byte(newPwd))
|
||||
block, err = ssh.MarshalPrivateKeyWithPassphrase(key, "", []byte(newPwd))
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@ -981,7 +1017,7 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
|
||||
} else {
|
||||
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".openssh")
|
||||
}
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
|
||||
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
|
||||
sk, err := ssh.NewPublicKey(n)
|
||||
if err != nil {
|
||||
return err
|
||||
|
71
cert/cmd.go
71
cert/cmd.go
@ -4,6 +4,7 @@ import (
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
@ -64,12 +65,6 @@ var CmdCsr = &cobra.Command{
|
||||
if dnsName == nil {
|
||||
dnsName = stario.MessageBox("请输入dns名称,用逗号分割:", "").MustSliceString(",")
|
||||
}
|
||||
if startStr == "" {
|
||||
startStr = stario.MessageBox("请输入开始时间:", "").MustString()
|
||||
}
|
||||
if endStr == "" {
|
||||
endStr = stario.MessageBox("请输入结束时间:", "").MustString()
|
||||
}
|
||||
}
|
||||
start, err = time.Parse(time.RFC3339, startStr)
|
||||
if err != nil {
|
||||
@ -81,7 +76,12 @@ var CmdCsr = &cobra.Command{
|
||||
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName, start, end, isCa, maxPathLenZero, maxPathLen))
|
||||
key, err := LoadPriv(caKey, caKeyPwd)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载Key错误", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName), key)
|
||||
err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644)
|
||||
if err != nil {
|
||||
starlog.Errorln("保存csr文件错误", err)
|
||||
@ -112,10 +112,21 @@ var CmdGen = &cobra.Command{
|
||||
starlog.Errorln("证书公钥不能为空")
|
||||
os.Exit(1)
|
||||
}
|
||||
caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载CA错误", err)
|
||||
os.Exit(1)
|
||||
var caKeyRaw crypto.PrivateKey
|
||||
var caCertRaw *x509.Certificate
|
||||
var err error
|
||||
if !isCa {
|
||||
caKeyRaw, caCertRaw, err = LoadCA(caKey, caCert, caKeyPwd)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载CA错误", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
caKeyRaw, err = LoadPriv(caKey, caKeyPwd)
|
||||
if err != nil {
|
||||
starlog.Errorln("加载CA错误", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
csrRaw, err := LoadCsr(csr)
|
||||
if err != nil {
|
||||
@ -132,7 +143,18 @@ var CmdGen = &cobra.Command{
|
||||
starlog.Errorln("解析公钥错误", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
cert, err := MakeCert(caKeyRaw, caCertRaw, csrRaw, pubKeyRaw)
|
||||
certReq := &x509.Certificate{
|
||||
Subject: csrRaw.Subject,
|
||||
IsCA: isCa,
|
||||
NotBefore: start,
|
||||
NotAfter: end,
|
||||
MaxPathLen: maxPathLen,
|
||||
MaxPathLenZero: maxPathLenZero,
|
||||
}
|
||||
if isCa {
|
||||
caCertRaw = certReq
|
||||
}
|
||||
cert, err := MakeCert(caKeyRaw, caCertRaw, certReq, pubKeyRaw)
|
||||
if err != nil {
|
||||
starlog.Errorln("生成证书错误", err)
|
||||
os.Exit(1)
|
||||
@ -171,19 +193,21 @@ var CmdParse = &cobra.Command{
|
||||
func init() {
|
||||
Cmd.AddCommand(CmdCsr)
|
||||
CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
|
||||
CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家")
|
||||
CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份")
|
||||
CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市")
|
||||
CmdCsr.Flags().StringVarP(&country, "country", "c", "CN", "国家")
|
||||
CmdCsr.Flags().StringVarP(&province, "province", "p", "B612", "省份")
|
||||
CmdCsr.Flags().StringVarP(&city, "city", "t", "B612", "城市")
|
||||
CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织")
|
||||
CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
|
||||
CmdCsr.Flags().StringVarP(&name, "name", "n", "", "通用名称")
|
||||
CmdCsr.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称")
|
||||
CmdCsr.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
|
||||
CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdCsr.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
|
||||
CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
|
||||
CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
|
||||
CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
|
||||
CmdCsr.Flags().StringVarP(&caKey, "secret-key", "k", "", "加密私钥")
|
||||
CmdCsr.Flags().StringVarP(&caKeyPwd, "secret-key-passwd", "K", "", "加密私钥的密码")
|
||||
//CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
|
||||
//CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
//CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
//CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
|
||||
//CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
|
||||
|
||||
CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥")
|
||||
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
|
||||
@ -191,6 +215,11 @@ func init() {
|
||||
CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥")
|
||||
CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
|
||||
CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码")
|
||||
CmdGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
|
||||
CmdGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
|
||||
CmdGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
|
||||
CmdGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
|
||||
Cmd.AddCommand(CmdGen)
|
||||
|
||||
CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")
|
||||
|
57
cert/csr.go
57
cert/csr.go
@ -1,17 +1,16 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string, start, end time.Time, isCa bool, maxPathLenZero bool, maxPathLen int) *x509.Certificate {
|
||||
func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []string) *x509.CertificateRequest {
|
||||
var trueDNS []string
|
||||
var trueIp []net.IP
|
||||
for _, v := range dnsName {
|
||||
@ -22,15 +21,17 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
|
||||
}
|
||||
trueIp = append(trueIp, ip)
|
||||
}
|
||||
ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
eku := x509.ExtKeyUsageServerAuth
|
||||
if isCa {
|
||||
ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
|
||||
eku = x509.ExtKeyUsageAny
|
||||
}
|
||||
return &x509.Certificate{
|
||||
Version: 3,
|
||||
SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
/*
|
||||
ku := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment
|
||||
eku := x509.ExtKeyUsageServerAuth
|
||||
if isCa {
|
||||
ku = x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageKeyEncipherment | x509.KeyUsageKeyAgreement | x509.KeyUsageDigitalSignature
|
||||
eku = x509.ExtKeyUsageAny
|
||||
}
|
||||
*/
|
||||
return &x509.CertificateRequest{
|
||||
Version: 3,
|
||||
//SerialNumber: big.NewInt(time.Now().Unix()),
|
||||
Subject: pkix.Name{
|
||||
Country: s2s(country),
|
||||
Province: s2s(province),
|
||||
@ -39,23 +40,27 @@ func GenerateCsr(country, province, city, org, orgUnit, name string, dnsName []s
|
||||
OrganizationalUnit: s2s(orgUnit),
|
||||
CommonName: name,
|
||||
},
|
||||
DNSNames: trueDNS,
|
||||
IPAddresses: trueIp,
|
||||
NotBefore: start,
|
||||
NotAfter: end,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: isCa,
|
||||
MaxPathLen: maxPathLen,
|
||||
MaxPathLenZero: maxPathLenZero,
|
||||
KeyUsage: ku,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{eku},
|
||||
DNSNames: trueDNS,
|
||||
IPAddresses: trueIp,
|
||||
//NotBefore: start,
|
||||
//NotAfter: end,
|
||||
//BasicConstraintsValid: true,
|
||||
//IsCA: isCa,
|
||||
//MaxPathLen: maxPathLen,
|
||||
//MaxPathLenZero: maxPathLenZero,
|
||||
//KeyUsage: ku,
|
||||
//ExtKeyUsage: []x509.ExtKeyUsage{eku},
|
||||
}
|
||||
}
|
||||
|
||||
func outputCsr(csr *x509.Certificate) []byte {
|
||||
func outputCsr(csr *x509.CertificateRequest, priv interface{}) []byte {
|
||||
csrBytes, err := x509.CreateCertificateRequest(rand.Reader, csr, priv)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr.Raw,
|
||||
Bytes: csrBytes,
|
||||
})
|
||||
}
|
||||
|
||||
@ -66,7 +71,7 @@ func s2s(str string) []string {
|
||||
return []string{str}
|
||||
}
|
||||
|
||||
func LoadCsr(csrPath string) (*x509.Certificate, error) {
|
||||
func LoadCsr(csrPath string) (*x509.CertificateRequest, error) {
|
||||
csrBytes, err := os.ReadFile(csrPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -75,7 +80,7 @@ func LoadCsr(csrPath string) (*x509.Certificate, error) {
|
||||
if block == nil || block.Type != "CERTIFICATE REQUEST" {
|
||||
return nil, errors.New("Failed to decode PEM block containing the certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
cert, err := x509.ParseCertificateRequest(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -151,6 +151,7 @@ func (c *DoHClient) Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time
|
||||
|
||||
// No need to use hreq.URL.Query()
|
||||
hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil)
|
||||
hreq.Header.Set("User-Agent", "B612 DoH Client")
|
||||
hreq.Header.Add("Accept", DoHMediaType)
|
||||
resp, err := c.cli.Do(hreq)
|
||||
if err != nil {
|
||||
|
87
go.mod
87
go.mod
@ -1,52 +1,101 @@
|
||||
module b612.me/apps/b612
|
||||
|
||||
go 1.21
|
||||
|
||||
toolchain go1.21.2
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
b612.me/notify v1.2.5
|
||||
b612.me/astro v0.0.4
|
||||
b612.me/bcap v0.0.4
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0
|
||||
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991
|
||||
b612.me/starcrypto v0.0.5
|
||||
b612.me/stario v0.0.9
|
||||
b612.me/starlog v1.3.3
|
||||
b612.me/starnet v0.1.8
|
||||
b612.me/staros v1.1.7
|
||||
b612.me/stario v0.0.10
|
||||
b612.me/starlog v1.3.4
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062
|
||||
b612.me/starnet v0.2.1
|
||||
b612.me/staros v1.1.8
|
||||
b612.me/starssh v0.0.2
|
||||
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd
|
||||
b612.me/wincmd v0.0.3
|
||||
b612.me/wincmd v0.0.4
|
||||
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
|
||||
github.com/emersion/go-smtp v0.20.2
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0
|
||||
github.com/gdamore/tcell/v2 v2.7.1
|
||||
github.com/go-acme/lego/v4 v4.16.1
|
||||
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9
|
||||
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/huin/goupnp v1.3.0
|
||||
github.com/inconshreveable/mousetrap v1.1.0
|
||||
github.com/likexian/whois v1.15.1
|
||||
github.com/miekg/dns v1.1.58
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57
|
||||
github.com/shirou/gopsutil/v4 v4.24.10
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/things-go/go-socks5 v0.0.5
|
||||
golang.org/x/crypto v0.21.0
|
||||
golang.org/x/net v0.21.0
|
||||
github.com/vbauerster/mpb/v8 v8.8.3
|
||||
golang.org/x/crypto v0.26.0
|
||||
golang.org/x/net v0.28.0
|
||||
golang.org/x/sys v0.26.0
|
||||
golang.org/x/term v0.23.0
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0
|
||||
|
||||
)
|
||||
|
||||
require (
|
||||
b612.me/starmap v1.2.4 // indirect
|
||||
b612.me/win32api v0.0.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
|
||||
github.com/VividCortex/ewma v1.2.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
|
||||
github.com/cpu/goacmedns v0.1.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.1 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
|
||||
github.com/gdamore/encoding v1.0.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
|
||||
github.com/jlaffaye/ftp v0.1.0 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mdlayher/netlink v1.7.2 // indirect
|
||||
github.com/mdlayher/socket v0.4.1 // 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/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
golang.org/x/image v0.6.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/term v0.18.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/tools v0.17.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/text v0.17.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
231
go.sum
231
go.sum
@ -1,31 +1,66 @@
|
||||
b612.me/notify v1.2.5 h1:fASpzi8YAo78g6jKnefzfbsQz0nGNYFbClB2Bylj+MA=
|
||||
b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI=
|
||||
b612.me/starcrypto v0.0.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
|
||||
b612.me/astro v0.0.4 h1:i60vJ3Dq+9U16ryMqmnjE0uXB28poRBx8boIMbP8VH8=
|
||||
b612.me/astro v0.0.4/go.mod h1:yC1arIa79Q5ztQgWphj5mX5UvM/gddwTP+jGneojPt4=
|
||||
b612.me/bcap v0.0.4 h1:iY2Oz+uyG/mue6a/dJiU82ci5Xwkj4xHhre/q0O8G60=
|
||||
b612.me/bcap v0.0.4/go.mod h1:tpus+4iMpsnxb98Pck70s87Zt4sIWuRZjK23MmmzmoY=
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0 h1:PaLuNLMM0HBM7qPdk4igtNvqT2CDgdm52sL+KA9I3so=
|
||||
b612.me/notify v0.0.0-20240818092352-85803f75dfa0/go.mod h1:YyaYF4jj5ygIC/QbCMyhBWnsmmTL8kVs6UlHMGVKiY8=
|
||||
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.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
|
||||
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
|
||||
b612.me/stario v0.0.9 h1:bFDlejUJMwZ12a09snZJspQsOlkqpDAl9qKPEYOGWCk=
|
||||
b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
|
||||
b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI=
|
||||
b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE=
|
||||
b612.me/starmap v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
|
||||
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc=
|
||||
b612.me/starnet v0.1.8 h1:sTNytUFP38i2BFR9nha3lTSLYb7El3tvKpZplYCrhZk=
|
||||
b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
|
||||
b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g=
|
||||
b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40=
|
||||
b612.me/stario v0.0.0-20240818091810-d528a583f4b2/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk=
|
||||
b612.me/stario v0.0.10 h1:+cIyiDCBCjUfodMJDp4FLs+2E1jo7YENkN+sMEe6550=
|
||||
b612.me/stario v0.0.10/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk=
|
||||
b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84=
|
||||
b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk=
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062 h1:ImKEWAxzBYsS/YbqdVOPdUdv6b+i/lSGpipUGueXk7w=
|
||||
b612.me/starmap v0.0.0-20240818092703-ae61140c5062/go.mod h1:PhtO9wFrwPIHpry2CEdnVNZkrNOgfv77xrE0ZKQDkLM=
|
||||
b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
|
||||
b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
|
||||
b612.me/staros v1.1.8 h1:5Bpuf9q2nH75S2ekmieJuH3Y8LTqg/voxXCOiMAC3kk=
|
||||
b612.me/staros v1.1.8/go.mod h1:4KmokjKXFW5h1hbA4aIv5O+2FptVzBubCo7IPirfqm8=
|
||||
b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
|
||||
b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk=
|
||||
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA=
|
||||
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8=
|
||||
b612.me/win32api v0.0.2 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA=
|
||||
b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
|
||||
b612.me/wincmd v0.0.3 h1:GYrkYnNun39yfNcA2+u0h4VW/BYbTrJK39QW4W1LCYA=
|
||||
b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo=
|
||||
b612.me/wincmd v0.0.4 h1:fv9p1V8mw2HdUjaoZBWZy0T41JftueyLxAuch1MgtdI=
|
||||
b612.me/wincmd v0.0.4/go.mod h1:o3yPoE+DpVPHGKl/q1WT1C8OaIVwHEnpeNgMFqzlwD8=
|
||||
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/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/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
|
||||
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
|
||||
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
|
||||
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
|
||||
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
|
||||
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
|
||||
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
|
||||
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
|
||||
@ -34,92 +69,212 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1X
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
|
||||
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo=
|
||||
github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
|
||||
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
|
||||
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
|
||||
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
|
||||
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
|
||||
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E=
|
||||
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
|
||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
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/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-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
|
||||
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
|
||||
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
|
||||
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
|
||||
github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
|
||||
github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU=
|
||||
github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
|
||||
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
|
||||
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
|
||||
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE=
|
||||
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
|
||||
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
|
||||
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
|
||||
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
|
||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
||||
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/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
||||
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
||||
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
|
||||
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
|
||||
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@ -127,17 +282,33 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
|
||||
|
32
hash/hash.go
32
hash/hash.go
@ -29,7 +29,7 @@ var Cmd = &cobra.Command{
|
||||
var cumethod, method []string
|
||||
var result = make(map[string]string)
|
||||
var err error
|
||||
cumethod = []string{"md5", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
cumethod = []string{"md5", "crc32a", "crc32", "sha512", "sha384", "sha256", "sha224", "sha1", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
if ok, _ := this.Flags().GetBool("all"); ok {
|
||||
method = cumethod
|
||||
} else {
|
||||
@ -79,6 +79,7 @@ func init() {
|
||||
Cmd.Flags().BoolP("file", "f", false, "对指定文件进行校验")
|
||||
Cmd.Flags().BoolP("md5", "m", false, "进行MD5校验(默认)")
|
||||
Cmd.Flags().BoolP("crc32", "c", false, "进行CRC32校验")
|
||||
Cmd.Flags().Bool("crc32a", false, "进行CRC32A校验")
|
||||
Cmd.Flags().BoolP("sha512", "s", false, "进行SHA512校验")
|
||||
Cmd.Flags().Bool("sha384", false, "进行SHA384校验")
|
||||
Cmd.Flags().Bool("sha256", false, "进行SHA256校验")
|
||||
@ -98,10 +99,9 @@ func init() {
|
||||
func FileSumAll(filepath string, key string, method []string, shell func(float64)) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
methods := make(map[string]hash.Hash)
|
||||
var iscrc bool
|
||||
|
||||
if len(method) == 0 {
|
||||
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32a", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
}
|
||||
fp, err := os.Open(filepath)
|
||||
defer fp.Close()
|
||||
@ -115,6 +115,7 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64
|
||||
sum256 := sha256.New()
|
||||
sum224 := sha256.New224()
|
||||
sum1 := sha1.New()
|
||||
crc32a := crc32.New(crc32.MakeTable(0x04C11DB7))
|
||||
crcsum := crc32.NewIEEE()
|
||||
md5sum := md5.New()
|
||||
md4sum := md4.New()
|
||||
@ -130,8 +131,10 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64
|
||||
switch v {
|
||||
case "md5":
|
||||
methods["md5"] = md5sum
|
||||
case "crc32a":
|
||||
methods["crc32a"] = crc32a
|
||||
case "crc32":
|
||||
iscrc = true
|
||||
methods["crc32"] = crcsum
|
||||
case "sha1":
|
||||
methods["sha1"] = sum1
|
||||
case "sha224":
|
||||
@ -179,31 +182,25 @@ func FileSumAll(filepath string, key string, method []string, shell func(float64
|
||||
for _, v := range methods {
|
||||
v.Write(buf[0:n])
|
||||
}
|
||||
if iscrc {
|
||||
crcsum.Write(buf[0:n])
|
||||
}
|
||||
}
|
||||
for k, v := range methods {
|
||||
result[k] = hex.EncodeToString(v.Sum(nil))
|
||||
}
|
||||
if iscrc {
|
||||
result["crc32"] = hex.EncodeToString(crcsum.Sum(nil))
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func SumAll(data []byte, key string, method []string) (map[string][]byte, error) {
|
||||
result := make(map[string][]byte)
|
||||
methods := make(map[string]hash.Hash)
|
||||
var iscrc bool
|
||||
if len(method) == 0 {
|
||||
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
method = []string{"sha512", "sha256", "sha384", "sha224", "sha1", "crc32a", "crc32", "md5", "md4", "ripemd160", "hmacmd5", "hmacmd4", "hmacsha1", "hmacsha224", "hmacsha256", "hmacsha384", "hmacsha512"}
|
||||
}
|
||||
sum512 := sha512.New()
|
||||
sum384 := sha512.New384()
|
||||
sum256 := sha256.New()
|
||||
sum224 := sha256.New224()
|
||||
sum1 := sha1.New()
|
||||
crc32a := crc32.New(crc32.MakeTable(0x04C11DB7))
|
||||
crcsum := crc32.NewIEEE()
|
||||
md5sum := md5.New()
|
||||
md4sum := md4.New()
|
||||
@ -219,8 +216,10 @@ func SumAll(data []byte, key string, method []string) (map[string][]byte, error)
|
||||
switch v {
|
||||
case "md5":
|
||||
methods["md5"] = md5sum
|
||||
case "crc32a":
|
||||
methods["crc32a"] = crc32a
|
||||
case "crc32":
|
||||
iscrc = true
|
||||
methods["crc32"] = crcsum
|
||||
case "sha1":
|
||||
methods["sha1"] = sum1
|
||||
case "sha224":
|
||||
@ -254,15 +253,8 @@ func SumAll(data []byte, key string, method []string) (map[string][]byte, error)
|
||||
for _, v := range methods {
|
||||
v.Write(data)
|
||||
}
|
||||
if iscrc {
|
||||
crcsum.Write(data)
|
||||
}
|
||||
|
||||
for k, v := range methods {
|
||||
result[k] = v.Sum(nil)
|
||||
}
|
||||
if iscrc {
|
||||
result["crc32"] = crcsum.Sum(nil)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
@ -5,9 +5,12 @@ import (
|
||||
"b612.me/staros"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"os/signal"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -15,6 +18,7 @@ var s HttpServer
|
||||
|
||||
var daemon bool
|
||||
var hooks string
|
||||
var speedlimit string
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
|
||||
@ -34,8 +38,54 @@ func init() {
|
||||
Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址")
|
||||
Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址")
|
||||
Cmd.Flags().BoolVarP(&s.httpDebug, "debug", "D", false, "开启调试模式")
|
||||
Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/")
|
||||
Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password")
|
||||
Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致")
|
||||
Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速,如1M,意思是1MB/s")
|
||||
|
||||
Cmd.Flags().Bool("daeapplied", false, "")
|
||||
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
|
||||
Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址")
|
||||
|
||||
Cmd.Flags().MarkHidden("daeapplied")
|
||||
|
||||
}
|
||||
func parseSpeedString(speedString string) (uint64, error) {
|
||||
// 定义单位及其对应的字节值
|
||||
unitMultipliers := map[string]int64{
|
||||
"b": 1, "": 1,
|
||||
"k": 1024, "kb": 1024, "kib": 1024,
|
||||
"m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024,
|
||||
"g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024,
|
||||
"t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024,
|
||||
}
|
||||
|
||||
// 正则表达式匹配速度的格式
|
||||
re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`)
|
||||
matches := re.FindStringSubmatch(strings.ToLower(speedString))
|
||||
if matches == nil {
|
||||
return 0, fmt.Errorf("invalid speed string format")
|
||||
}
|
||||
|
||||
// 解析数值部分
|
||||
value, err := strconv.ParseFloat(matches[1], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid numeric value")
|
||||
}
|
||||
|
||||
// 获取单位部分
|
||||
unit := matches[2]
|
||||
if unit == "" {
|
||||
unit = "b"
|
||||
}
|
||||
|
||||
// 根据单位计算最终的字节每秒值
|
||||
multiplier, ok := unitMultipliers[unit]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid unit in speed string")
|
||||
}
|
||||
|
||||
return uint64(value * float64(multiplier)), nil
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
@ -43,6 +93,19 @@ var Cmd = &cobra.Command{
|
||||
Short: "HTTP文件服务器(HTTP File Browser Server)",
|
||||
Long: `HTTP文件服务器(HTTP File Browser Server)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if s.logpath != "" && starlog.GetWriter() == nil {
|
||||
starlog.SetLogFile(s.logpath, starlog.Std, true)
|
||||
}
|
||||
if speedlimit != "" {
|
||||
speed, err := parseSpeedString(speedlimit)
|
||||
if err != nil {
|
||||
starlog.Criticalln("Speed Limit Error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
s.speedlimit = speed
|
||||
starlog.Infoln("Speed Limit:(user in):\t", speedlimit)
|
||||
starlog.Infoln("Speed Limit (bytes/s):\t", speed)
|
||||
}
|
||||
if hooks != "" {
|
||||
if !staros.Exists(hooks) {
|
||||
starlog.Criticalln("hook file not exists")
|
||||
|
10
httpserver/goserver_test.go
Normal file
10
httpserver/goserver_test.go
Normal file
@ -0,0 +1,10 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHttpServer(t *testing.T) {
|
||||
http.ListenAndServe(":89", http.FileServer(http.Dir(`./`)))
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package httpserver
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/version"
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
@ -8,8 +9,10 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
@ -22,7 +25,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var version = "2.1.0"
|
||||
var ver = version.Version
|
||||
|
||||
type HttpServerCfgs func(cfg *HttpServerCfg)
|
||||
|
||||
@ -45,6 +48,14 @@ type HttpServerCfg struct {
|
||||
ctx context.Context
|
||||
hooks []ServerHook
|
||||
httpDebug bool
|
||||
noListPath []string
|
||||
listPwd map[string]string
|
||||
listSameForFile bool
|
||||
// speed limit means xx bytes/s
|
||||
speedlimit uint64
|
||||
|
||||
background string
|
||||
mobildBackground string
|
||||
}
|
||||
|
||||
type ServerHook struct {
|
||||
@ -67,219 +78,8 @@ var jquery []byte
|
||||
//go:embed upload.html
|
||||
var uploadPage []byte
|
||||
|
||||
var htmlTitle string = `<!DOCTYPE html>
|
||||
<html lang="zh_CN">
|
||||
<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>
|
||||
`
|
||||
//go:embed template.html
|
||||
var templateHtml []byte
|
||||
|
||||
func WithHooks(hooks []ServerHook) HttpServerCfgs {
|
||||
return func(cfg *HttpServerCfg) {
|
||||
@ -333,7 +133,7 @@ func (h *HttpServer) Run(ctx context.Context) error {
|
||||
server.Shutdown(ctx)
|
||||
}
|
||||
}()
|
||||
if h.logpath != "" {
|
||||
if h.logpath != "" && starlog.GetWriter() == nil {
|
||||
starlog.SetLogFile(h.logpath, starlog.Std, true)
|
||||
}
|
||||
netcards, err := net.Interfaces()
|
||||
@ -401,7 +201,14 @@ func (h *HttpServer) Page403(w http.ResponseWriter) {
|
||||
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) {
|
||||
@ -514,7 +321,7 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
|
||||
log.SetShowFuncName(false)
|
||||
log.SetShowOriginFile(false)
|
||||
w.Header().Set("X-Powered-By", "B612.ME")
|
||||
w.Header().Set("Server", "B612/"+version)
|
||||
w.Header().Set("Server", "B612/"+ver)
|
||||
if !h.BasicAuth(log, w, r) {
|
||||
return
|
||||
}
|
||||
@ -575,26 +382,25 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
|
||||
var rangeStart, rangeEnd int64
|
||||
rangeStart, rangeEnd = -1, -1
|
||||
for k, v := range r.Header {
|
||||
if strings.ToLower(k) == "range" {
|
||||
if strings.Contains(v[0], "bytes=") {
|
||||
v[0] = strings.Replace(v[0], "bytes=", "", -1)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
data := strings.Split(v[0], "-")
|
||||
if len(data) == 0 {
|
||||
break
|
||||
}
|
||||
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
|
||||
if len(data) > 1 {
|
||||
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
|
||||
}
|
||||
//w.WriteHeader(206) //206 支持断点续传
|
||||
break
|
||||
}
|
||||
var rangeStart, rangeEnd int64 = -1, -1
|
||||
ranges := r.Header.Get("Range")
|
||||
if ranges == "" {
|
||||
return rangeStart, rangeEnd
|
||||
}
|
||||
if !strings.Contains(ranges, "bytes=") {
|
||||
return rangeStart, rangeEnd
|
||||
}
|
||||
ranges = strings.TrimPrefix(ranges, "bytes=")
|
||||
data := strings.Split(ranges, "-")
|
||||
if len(data) == 0 {
|
||||
return rangeStart, rangeEnd
|
||||
}
|
||||
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
|
||||
if len(data) > 1 {
|
||||
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
|
||||
}
|
||||
if rangeEnd == 0 {
|
||||
rangeEnd = -1
|
||||
}
|
||||
return rangeStart, rangeEnd
|
||||
}
|
||||
@ -630,16 +436,19 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
|
||||
if _, ok := h.willHook(fullpath); ok {
|
||||
return nil
|
||||
}
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
|
||||
start, end := h.CalcRange(r)
|
||||
if start != -1 {
|
||||
if end == -1 {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10))
|
||||
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
|
||||
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
|
||||
} else {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
|
||||
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
|
||||
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
|
||||
}
|
||||
} else {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -666,11 +475,57 @@ func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
|
||||
|
||||
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
||||
if staros.IsFolder(fullpath) {
|
||||
if len(h.listPwd) != 0 {
|
||||
for k, v := range h.listPwd {
|
||||
if strings.HasPrefix(r.URL.Path, k) {
|
||||
if r.URL.Query().Get("list") == v {
|
||||
return h.getFolder(log, w, r, fullpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(h.noListPath) != 0 {
|
||||
for _, v := range h.noListPath {
|
||||
if strings.HasPrefix(r.URL.Path, v) {
|
||||
h.Page403(w)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return h.getFolder(log, w, r, fullpath)
|
||||
}
|
||||
if !h.listSameForFile {
|
||||
return h.getFile(log, w, r, fullpath)
|
||||
}
|
||||
|
||||
if len(h.listPwd) != 0 {
|
||||
for k, v := range h.listPwd {
|
||||
if strings.HasPrefix(r.URL.Path, k) {
|
||||
if r.URL.Query().Get("list") == v {
|
||||
return h.getFile(log, w, r, fullpath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(h.noListPath) != 0 {
|
||||
for _, v := range h.noListPath {
|
||||
if strings.HasPrefix(r.URL.Path, v) {
|
||||
h.Page403(w)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return h.getFile(log, w, r, fullpath)
|
||||
}
|
||||
|
||||
type FileData struct {
|
||||
Attr string `json:"attr"`
|
||||
Name string `json:"name"`
|
||||
Modified string `json:"modified"`
|
||||
Size int64 `json:"size"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
||||
dir, err := ioutil.ReadDir(fullpath)
|
||||
if err != nil {
|
||||
@ -688,14 +543,24 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
|
||||
if h.uploadFolder != "" {
|
||||
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 != "/" {
|
||||
p := r.URL.Path
|
||||
if p[len(p)-1:] != "/" {
|
||||
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>`,
|
||||
p+"..", "../", "-", "-", "上层文件夹")))
|
||||
fdatas = append(fdatas, FileData{
|
||||
Attr: p + ".." + attr,
|
||||
Name: "..",
|
||||
Modified: "-",
|
||||
Size: -1,
|
||||
Type: "上层文件夹",
|
||||
})
|
||||
}
|
||||
if r.URL.Path == "/" {
|
||||
r.URL.Path = ""
|
||||
@ -706,23 +571,104 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
|
||||
for _, v := range dir {
|
||||
if v.Name() != "." || v.Name() != ".." {
|
||||
if !v.IsDir() {
|
||||
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
|
||||
r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name()))))
|
||||
fdatas = append(fdatas, FileData{
|
||||
Name: v.Name(),
|
||||
Attr: r.URL.Path + "/" + v.Name(),
|
||||
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
|
||||
Size: v.Size(),
|
||||
Type: h.FileType(v.Name()),
|
||||
})
|
||||
} else {
|
||||
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
|
||||
r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
|
||||
fdatas = append(fdatas, FileData{
|
||||
Name: v.Name() + "/",
|
||||
Attr: r.URL.Path + "/" + v.Name() + attr,
|
||||
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
|
||||
Size: -1,
|
||||
Type: "文件夹",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Write([]byte(htmlTail))
|
||||
tmpt, err := template.New("index").Parse(string(templateHtml))
|
||||
if err != nil {
|
||||
log.Errorf("Parse Template Error:%v\n", err)
|
||||
w.WriteHeader(502)
|
||||
w.Write([]byte(`<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": ver,
|
||||
"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
|
||||
}
|
||||
|
||||
func (h *HttpServer) getSleepTime() time.Duration {
|
||||
if h.speedlimit == 0 {
|
||||
return 0
|
||||
}
|
||||
return time.Nanosecond * time.Duration(16384*1000*1000*1000/h.speedlimit) / 2
|
||||
|
||||
}
|
||||
|
||||
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
|
||||
if !staros.Exists(fullpath) {
|
||||
h.Page404(w)
|
||||
return errors.New("File Not Found! 404 ERROR")
|
||||
}
|
||||
var lastCount int64
|
||||
var lastDate time.Time = time.Now()
|
||||
var currentCount int64
|
||||
speedControl := func(count int) {
|
||||
if h.speedlimit == 0 {
|
||||
return
|
||||
}
|
||||
currentCount += int64(count)
|
||||
for {
|
||||
if time.Since(lastDate) < time.Second {
|
||||
if uint64(currentCount-lastCount) > h.speedlimit {
|
||||
time.Sleep(h.getSleepTime())
|
||||
} else {
|
||||
break
|
||||
}
|
||||
} else {
|
||||
lastDate = time.Now()
|
||||
lastCount = currentCount
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
//starlog.Debugln(r.Header)
|
||||
startRange, endRange := h.CalcRange(r)
|
||||
fp, err := os.Open(fullpath)
|
||||
@ -756,10 +702,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
if !needCurl {
|
||||
w.WriteHeader(200)
|
||||
for {
|
||||
buf := make([]byte, 1048576)
|
||||
buf := make([]byte, 16384)
|
||||
n, err := fp.Read(buf)
|
||||
if n != 0 {
|
||||
ns, err := w.Write(buf[0:n])
|
||||
speedControl(n)
|
||||
ns, err := w.Write(buf[:n])
|
||||
transferData += ns
|
||||
if err != nil {
|
||||
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
|
||||
@ -770,7 +717,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
log.Errorln("Read File %s Failed:%v\n", fullpath, err)
|
||||
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -784,13 +731,14 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
return err
|
||||
}
|
||||
b64 := base64.StdEncoding.EncodeToString(data)
|
||||
req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{
|
||||
"data": b64,
|
||||
"ip": r.RemoteAddr,
|
||||
}),
|
||||
req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
|
||||
"POST",
|
||||
starnet.WithBytes(starnet.BuildPostForm(map[string]string{
|
||||
"data": b64,
|
||||
"ip": r.RemoteAddr,
|
||||
})),
|
||||
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
|
||||
if err != nil || len(req.RecvData) == 0 {
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
|
||||
w.WriteHeader(200)
|
||||
ns, err := w.Write(data)
|
||||
@ -801,9 +749,10 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
}
|
||||
return nil
|
||||
}
|
||||
recvData := req.Body().Bytes()
|
||||
w.WriteHeader(200)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData)))
|
||||
ns, err := w.Write(req.RecvData)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(recvData)))
|
||||
ns, err := w.Write(recvData)
|
||||
transferData += ns
|
||||
if err != nil {
|
||||
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
|
||||
@ -816,7 +765,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
fp.Seek(int64(startRange), 0)
|
||||
count := startRange
|
||||
for {
|
||||
buf := make([]byte, 1048576)
|
||||
buf := make([]byte, 16384)
|
||||
n, err := fp.Read(buf)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
@ -825,8 +774,9 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
|
||||
return err
|
||||
}
|
||||
speedControl(n)
|
||||
if endRange == -1 {
|
||||
ns, err := w.Write(buf[0:n])
|
||||
ns, err := w.Write(buf[:n])
|
||||
transferData += ns
|
||||
if err != nil {
|
||||
log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
|
||||
@ -841,11 +791,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
|
||||
writeNum = int(endRange - count + 1)
|
||||
}
|
||||
ns, err := w.Write(buf[0:writeNum])
|
||||
transferData += ns
|
||||
if err != nil {
|
||||
log.Errorln("Transfer Error:", err)
|
||||
return err
|
||||
}
|
||||
transferData += ns
|
||||
count += int64(n)
|
||||
}
|
||||
}
|
||||
|
478
httpserver/template.html
Normal file
478
httpserver/template.html
Normal file
@ -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>
|
190
image/image.go
190
image/image.go
@ -1,15 +1,31 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"sort"
|
||||
|
||||
"b612.me/starlog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var useHex bool
|
||||
var useAlpha bool
|
||||
var useCount int
|
||||
var ckt uint8
|
||||
var fromRgb string
|
||||
var toRgb string
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(imgMirrorCmd)
|
||||
Cmd.AddCommand(imgMirrorCmd, imgRgbCountCmd, imgReplaceCmd, imgReverseCmd)
|
||||
imgRgbCountCmd.Flags().BoolVarP(&useHex, "hex", "x", false, "使用十六进制表示")
|
||||
imgRgbCountCmd.Flags().BoolVarP(&useAlpha, "alpha", "a", false, "统计alpha通道")
|
||||
imgRgbCountCmd.Flags().IntVarP(&useCount, "count", "c", 10, "显示数量")
|
||||
imgReplaceCmd.Flags().Uint8VarP(&ckt, "ckt", "k", 30, "颜色比较阈值")
|
||||
imgReplaceCmd.Flags().StringVarP(&fromRgb, "from", "f", "dfdfdf", "需要替换的颜色")
|
||||
imgReplaceCmd.Flags().StringVarP(&toRgb, "to", "t", "ffffff", "替换为的颜色")
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
@ -80,3 +96,175 @@ var imgAlpha = &cobra.Command{
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
||||
var imgRgbCountCmd = &cobra.Command{
|
||||
Use: "rgbcount",
|
||||
Short: "统计最多的rgb值",
|
||||
Long: "统计最多的RGB值",
|
||||
Run: func(this *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
starlog.Errorln("请指定需要统计的图像!")
|
||||
return
|
||||
}
|
||||
for _, v := range args {
|
||||
img, err := OpenImage(v)
|
||||
if err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
}
|
||||
size := img.Bounds()
|
||||
colorMap := make(map[string]int)
|
||||
for x := 0; x < size.Dx(); x++ {
|
||||
for y := 0; y < size.Dy(); y++ {
|
||||
color := img.At(x, y)
|
||||
r, g, b, a := color.RGBA()
|
||||
var key string
|
||||
if useAlpha {
|
||||
if !useHex {
|
||||
key = fmt.Sprintf("%d,%d,%d,%d", uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8))
|
||||
} else {
|
||||
key = fmt.Sprintf("%x%x%x%x", uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8))
|
||||
}
|
||||
} else {
|
||||
if !useHex {
|
||||
key = fmt.Sprintf("%d,%d,%d", uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
||||
} else {
|
||||
key = fmt.Sprintf("%x%x%x", uint8(r>>8), uint8(g>>8), uint8(b>>8))
|
||||
}
|
||||
}
|
||||
if _, ok := colorMap[key]; ok {
|
||||
colorMap[key]++
|
||||
} else {
|
||||
colorMap[key] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
colorSlice := make([]struct {
|
||||
string
|
||||
int
|
||||
}, 0, len(colorMap))
|
||||
for k, v := range colorMap {
|
||||
colorSlice = append(colorSlice, struct {
|
||||
string
|
||||
int
|
||||
}{k, v})
|
||||
}
|
||||
sort.Slice(colorSlice, func(i, j int) bool {
|
||||
return colorSlice[i].int > colorSlice[j].int
|
||||
})
|
||||
fmt.Println(v)
|
||||
for i := 0; i < useCount && i < len(colorSlice); i++ {
|
||||
fmt.Printf("%d. %s: %d\n", i+1, colorSlice[i].string, colorSlice[i].int)
|
||||
}
|
||||
fmt.Println("----------------")
|
||||
}
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
||||
var imgReplaceCmd = &cobra.Command{
|
||||
Use: "replace",
|
||||
Short: "图像RGB替换",
|
||||
Long: "图像RGB替换",
|
||||
Run: func(this *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
starlog.Errorln("请指定需要转换的图像!")
|
||||
return
|
||||
}
|
||||
r, g, b, err := HexToRGB(fromRgb)
|
||||
if err != nil {
|
||||
starlog.Errorln(err)
|
||||
return
|
||||
}
|
||||
nr, ng, nb, err := HexToRGB(toRgb)
|
||||
if err != nil {
|
||||
starlog.Errorln(err)
|
||||
return
|
||||
}
|
||||
for _, v := range args {
|
||||
img, err := OpenImage(v)
|
||||
if err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
}
|
||||
size := img.Bounds()
|
||||
nimg := image.NewRGBA(size)
|
||||
for x := 0; x < size.Dx(); x++ {
|
||||
for y := 0; y < size.Dy(); y++ {
|
||||
mR, mG, mB, ma := img.At(x, y).RGBA()
|
||||
mr, mg, mb := uint8(mR>>8), uint8(mG>>8), uint8(mB>>8)
|
||||
nimg.Set(x, y, img.At(x, y))
|
||||
if mr-r < ckt || r-mr < ckt {
|
||||
if mg-g < ckt || g-mg < ckt {
|
||||
if mb-b < ckt || b-mb < ckt {
|
||||
nimg.Set(x, y, color.NRGBA{nr, ng, nb, uint8(ma >> 8)})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := SavePhoto("new-"+v, nimg); err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
} else {
|
||||
fmt.Println(v, "转换已完成!")
|
||||
}
|
||||
}
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
||||
func HexToRGB(hexStr string) (uint8, uint8, uint8, error) {
|
||||
// 检查输入长度是否为6
|
||||
if len(hexStr) != 6 {
|
||||
return 0, 0, 0, fmt.Errorf("invalid hex color string: %s", hexStr)
|
||||
}
|
||||
|
||||
// 将16进制字符串解码为字节数组
|
||||
decoded, err := hex.DecodeString(hexStr)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("failed to decode hex string: %v", err)
|
||||
}
|
||||
|
||||
// 解码结果应该有3个字节
|
||||
if len(decoded) != 3 {
|
||||
return 0, 0, 0, fmt.Errorf("invalid hex color format: %s", hexStr)
|
||||
}
|
||||
|
||||
// 返回三个通道值
|
||||
return decoded[0], decoded[1], decoded[2], nil
|
||||
}
|
||||
|
||||
var imgReverseCmd = &cobra.Command{
|
||||
Use: "reverse",
|
||||
Short: "图像反色",
|
||||
Long: "图像反色",
|
||||
Run: func(this *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
starlog.Errorln("请指定需要转换的图像!")
|
||||
return
|
||||
}
|
||||
for _, v := range args {
|
||||
img, err := OpenImage(v)
|
||||
if err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
}
|
||||
size := img.Bounds()
|
||||
nimg := image.NewRGBA(size)
|
||||
for x := 0; x < size.Dx(); x++ {
|
||||
for y := 0; y < size.Dy(); y++ {
|
||||
r, g, b, a := img.At(x, y).RGBA()
|
||||
nimg.Set(x, y, color.NRGBA{uint8(255 - r>>8), uint8(255 - g>>8), uint8(255 - b>>8), uint8(a >> 8)})
|
||||
}
|
||||
}
|
||||
if err := SavePhoto("reverse-"+v, nimg); err != nil {
|
||||
starlog.Errorln(err, v)
|
||||
continue
|
||||
} else {
|
||||
fmt.Println(v, "转换已完成!")
|
||||
}
|
||||
}
|
||||
fmt.Println("任务完成!")
|
||||
},
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"b612.me/starlog"
|
||||
"b612.me/staros"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
@ -138,6 +139,12 @@ var CmdPub = &cobra.Command{
|
||||
case *ecdsa.PrivateKey:
|
||||
starlog.Infoln("found ecdsa private key")
|
||||
pub = n.Public()
|
||||
case ed25519.PrivateKey:
|
||||
starlog.Infoln("found ed25519 private key")
|
||||
pub = n.Public()
|
||||
case *ed25519.PrivateKey:
|
||||
starlog.Infoln("found ed25519 private key")
|
||||
pub = n.Public()
|
||||
default:
|
||||
starlog.Errorln("unknown private key type")
|
||||
os.Exit(1)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"b612.me/starcrypto"
|
||||
"b612.me/staros"
|
||||
"crypto"
|
||||
"crypto/ecdh"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/elliptic"
|
||||
@ -67,7 +68,7 @@ func (k *KeyGen) Gen() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "ecdsa", "ecdh":
|
||||
case "ecdsa":
|
||||
var cr elliptic.Curve
|
||||
switch k.Bits {
|
||||
case 224:
|
||||
@ -90,6 +91,33 @@ func (k *KeyGen) Gen() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "x25519":
|
||||
priv, err = ecdh.X25519().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case "ecdh":
|
||||
switch k.Bits {
|
||||
case 256:
|
||||
priv, err = ecdh.P256().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case 384:
|
||||
priv, err = ecdh.P384().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
case 521:
|
||||
priv, err = ecdh.P521().GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pub = priv.(*ecdh.PrivateKey).Public()
|
||||
}
|
||||
default:
|
||||
return errors.New("invalid key type,only support rsa,ecdsa")
|
||||
}
|
||||
@ -106,7 +134,7 @@ func (k *KeyGen) Gen() error {
|
||||
return err
|
||||
}
|
||||
var block *pem.Block
|
||||
if k.Encrypt != "" {
|
||||
if k.Encrypt == "" {
|
||||
block, err = ssh.MarshalPrivateKey(priv, "")
|
||||
} else {
|
||||
block, err = ssh.MarshalPrivateKeyWithPassphrase(priv, "", []byte(k.Encrypt))
|
||||
|
10
main.go
10
main.go
@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/aes"
|
||||
"b612.me/apps/b612/astro"
|
||||
"b612.me/apps/b612/attach"
|
||||
"b612.me/apps/b612/base64"
|
||||
"b612.me/apps/b612/base85"
|
||||
@ -21,16 +22,21 @@ import (
|
||||
"b612.me/apps/b612/image"
|
||||
"b612.me/apps/b612/keygen"
|
||||
"b612.me/apps/b612/merge"
|
||||
"b612.me/apps/b612/mget"
|
||||
"b612.me/apps/b612/net"
|
||||
"b612.me/apps/b612/nmon"
|
||||
"b612.me/apps/b612/rmt"
|
||||
"b612.me/apps/b612/search"
|
||||
"b612.me/apps/b612/smtpclient"
|
||||
"b612.me/apps/b612/smtpserver"
|
||||
"b612.me/apps/b612/socks5"
|
||||
"b612.me/apps/b612/split"
|
||||
"b612.me/apps/b612/tcm"
|
||||
"b612.me/apps/b612/tcping"
|
||||
"b612.me/apps/b612/tcpkill"
|
||||
"b612.me/apps/b612/tls"
|
||||
"b612.me/apps/b612/uac"
|
||||
"b612.me/apps/b612/version"
|
||||
"b612.me/apps/b612/vic"
|
||||
"b612.me/apps/b612/whois"
|
||||
"b612.me/stario"
|
||||
@ -41,7 +47,7 @@ import (
|
||||
|
||||
var cmdRoot = &cobra.Command{
|
||||
Use: "b612",
|
||||
Version: "2.1.0.beta.10",
|
||||
Version: version.Version,
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -50,7 +56,7 @@ func init() {
|
||||
base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd,
|
||||
ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd,
|
||||
calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd, smtpclient.Cmd,
|
||||
cert.Cmd, aes.Cmd, tls.Cmd)
|
||||
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
150
mget/cmd.go
Normal file
150
mget/cmd.go
Normal file
@ -0,0 +1,150 @@
|
||||
package mget
|
||||
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"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, user, pwd string
|
||||
var dialTimeout, timeout int
|
||||
|
||||
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().BoolVarP(&mg.NoWriteRedo, "no-redo", "N", false, "不写入redo文件")
|
||||
Cmd.Flags().IntVarP(&mg.RedoRPO, "safe", "s", 0, "安全校验点,0意味自动调整")
|
||||
Cmd.Flags().IntVarP(&mg.RedoMinSaveSec, "redo-min-sec", "m", 2, "redo文件最短写入间隔(秒)")
|
||||
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")
|
||||
Cmd.Flags().IntVarP(&dialTimeout, "dial-timeout", "d", 5, "连接网络超时时间,单位:秒")
|
||||
Cmd.Flags().IntVarP(&timeout, "timeout", "T", 0, "下载超时时间,单位:秒")
|
||||
Cmd.Flags().StringVarP(&user, "user", "u", "", "http basic认证用户")
|
||||
Cmd.Flags().StringVarP(&pwd, "passwd", "p", "", "http basic认证密码")
|
||||
}
|
||||
|
||||
func parseSpeedString(speedString string) (uint64, error) {
|
||||
// 定义单位及其对应的字节值
|
||||
unitMultipliers := map[string]int64{
|
||||
"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)
|
||||
}
|
||||
mg.Setting = *starnet.NewSimpleRequest(args[0], "GET")
|
||||
mg.OriginUri = args[0]
|
||||
if dialTimeout > 0 {
|
||||
mg.Setting.SetDialTimeout(time.Duration(dialTimeout) * time.Second)
|
||||
}
|
||||
if timeout > 0 {
|
||||
mg.Setting.SetTimeout(time.Duration(timeout) * time.Second)
|
||||
}
|
||||
if user != "" || pwd != "" {
|
||||
mg.Setting.RequestOpts.SetBasicAuth(user, pwd)
|
||||
}
|
||||
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)
|
||||
}
|
||||
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)
|
||||
if !mg.NoWriteRedo {
|
||||
mg.Redo.Save()
|
||||
}
|
||||
os.Exit(3)
|
||||
}
|
||||
}
|
104
mget/process.go
Normal file
104
mget/process.go
Normal file
@ -0,0 +1,104 @@
|
||||
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
|
||||
fmt.Fprintf(w, "\nSpeed:%v AvgSpeed:%v\n", m.Redo.FormatSpeed("MB"), m.Redo.FormatAvgSpeed("MB"))
|
||||
_, err := fmt.Fprintf(w, "Finished:%s Total Write:%d\n\n", m.Redo.FormatPercent(), m.Redo.Total())
|
||||
for k := range m.threads {
|
||||
v := m.threads[len(m.threads)-1-k]
|
||||
if v != nil {
|
||||
count++
|
||||
percent := v.FormatPercent()
|
||||
if m.Redo.Total() == m.Redo.ContentLength {
|
||||
percent = "100.00%"
|
||||
}
|
||||
res = fmt.Sprintf("Thread %v: %s %s\t", len(m.threads)-k, v.FormatSpeed("MB"), percent) + 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.AverageETA(decor.ET_STYLE_GO),
|
||||
decor.Name(" ] "),
|
||||
decor.AverageSpeed(decor.SizeB1024(0), "% .2f "),
|
||||
),
|
||||
)
|
||||
defer p.Wait()
|
||||
lastTime := time.Now()
|
||||
bar.SetRefill(int64(w.Redo.Total()))
|
||||
bar.DecoratorAverageAdjust(time.Now().Add(time.Millisecond * time.Duration(-w.TimeCost)))
|
||||
for {
|
||||
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()
|
||||
date := time.Now()
|
||||
bar.EwmaSetCurrent(int64(now), date.Sub(lastTime))
|
||||
lastTime = date
|
||||
if w.dynLength {
|
||||
bar.SetTotal(int64(w.Redo.ContentLength), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
84
mget/range.go
Normal file
84
mget/range.go
Normal file
@ -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
|
||||
}
|
30
mget/range_test.go
Normal file
30
mget/range_test.go
Normal file
@ -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())
|
||||
}
|
174
mget/redo.go
Normal file
174
mget/redo.go
Normal file
@ -0,0 +1,174 @@
|
||||
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"`
|
||||
TimeCost uint64 `json:"time_cost"`
|
||||
rangeUpdated bool
|
||||
startDate time.Time
|
||||
startCount uint64
|
||||
lastUpdate time.Time
|
||||
lastTotal uint64
|
||||
speed float64
|
||||
avgSpeed float64
|
||||
total uint64
|
||||
isRedo bool
|
||||
lastCallSave time.Time
|
||||
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()
|
||||
total = 0
|
||||
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
|
||||
if r.lastUpdate.IsZero() {
|
||||
r.startDate = time.Now()
|
||||
for _, v := range r.Range {
|
||||
r.startCount += v.Max - v.Min + 1
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
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)
|
||||
if !r.lastUpdate.IsZero() {
|
||||
r.TimeCost += uint64(now.Sub(r.lastUpdate).Milliseconds())
|
||||
}
|
||||
r.avgSpeed = float64(total-r.startCount) / (float64(now.Sub(r.startDate).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) FormatAvgSpeed(unit string) string {
|
||||
switch strings.ToLower(unit) {
|
||||
case "kb":
|
||||
return fmt.Sprintf("%.2f KB/s", r.avgSpeed/1024)
|
||||
case "mb":
|
||||
return fmt.Sprintf("%.2f MB/s", r.avgSpeed/1024/1024)
|
||||
case "gb":
|
||||
return fmt.Sprintf("%.2f GB/s", r.avgSpeed/1024/1024/1024)
|
||||
default:
|
||||
return fmt.Sprintf("%.2f B/s", r.avgSpeed)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Redo) Speed() float64 {
|
||||
return r.speed
|
||||
}
|
||||
|
||||
func (r *Redo) AverageSpeed() float64 {
|
||||
return r.avgSpeed
|
||||
}
|
||||
|
||||
func (r *Redo) Save() error {
|
||||
//defer recover()
|
||||
var err error
|
||||
err = r.reform()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.Lock()
|
||||
defer r.Unlock()
|
||||
r.lastCallSave = time.Now()
|
||||
if r.Filename != "" {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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))
|
||||
}
|
141
mget/util.go
Normal file
141
mget/util.go
Normal file
@ -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 *end != 0 && *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
|
||||
}
|
581
mget/wget.go
Normal file
581
mget/wget.go
Normal file
@ -0,0 +1,581 @@
|
||||
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
|
||||
NoWriteRedo bool
|
||||
RedoAutoRpo bool
|
||||
RedoMinSaveSec 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())
|
||||
if w.Setting.DialTimeout() > 0 {
|
||||
req.SetDialTimeout(w.Setting.DialTimeout())
|
||||
}
|
||||
if w.Setting.Timeout() > 0 {
|
||||
req.SetTimeout(w.Setting.Timeout())
|
||||
}
|
||||
if u, p := w.Setting.BasicAuth(); u != "" || p != "" {
|
||||
req.SetBasicAuth(u, p)
|
||||
}
|
||||
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)
|
||||
fmt.Println("IsDynLen:", w.dynLength)
|
||||
if !is206 {
|
||||
w.Thread = 1
|
||||
}
|
||||
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.SetUri(w.OriginUri)
|
||||
w.Setting.SetMethod("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 {
|
||||
go func() {
|
||||
w.writeEnable = true
|
||||
w.writeError = w.WriteServer()
|
||||
w.writeEnable = false
|
||||
}()
|
||||
var di = &downloader{
|
||||
alive: true,
|
||||
downloadinfo: &downloadinfo{
|
||||
Start: 0,
|
||||
End: w.TargetSize - 1,
|
||||
Size: w.TargetSize,
|
||||
},
|
||||
}
|
||||
if w.dynLength {
|
||||
di.End = 0
|
||||
}
|
||||
w.writeEnable = true
|
||||
w.threads[0] = di
|
||||
w.Thread = 1
|
||||
go w.Process()
|
||||
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 {
|
||||
w.writeEnable = false
|
||||
stario.WaitUntilTimeout(time.Second*2,
|
||||
func(c chan struct{}) error {
|
||||
for {
|
||||
if w.processEnable {
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
})
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
} 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)
|
||||
var once sync.Once
|
||||
for {
|
||||
if w.writeEnable {
|
||||
once.Do(w.fn)
|
||||
time.Sleep(time.Millisecond * 50)
|
||||
continue
|
||||
}
|
||||
if w.writeError != nil {
|
||||
if !w.NoWriteRedo {
|
||||
err = w.Redo.Save()
|
||||
}
|
||||
return fmt.Errorf("write error: %w %v", w.writeError, err)
|
||||
}
|
||||
break
|
||||
}
|
||||
once.Do(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 !w.NoWriteRedo {
|
||||
if len(r) == 0 {
|
||||
if staros.Exists(w.Tareget + ".bgrd") {
|
||||
return os.Remove(w.Tareget + ".bgrd")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return w.Redo.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
if w.RedoRPO == 0 {
|
||||
w.RedoAutoRpo = true
|
||||
w.RedoRPO = 1024 * 1024 * 1024
|
||||
}
|
||||
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 !w.NoWriteRedo && currentRange-lastUpdateRange >= w.RedoRPO && time.Now().After(w.Redo.lastCallSave.Add(time.Second*time.Duration(w.RedoMinSaveSec))) {
|
||||
w.tf.Sync()
|
||||
go w.Redo.Save()
|
||||
if w.RedoAutoRpo {
|
||||
w.RedoRPO = int(w.Redo.Speed()) * 2
|
||||
}
|
||||
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
|
||||
}
|
35
mget/wget_test.go
Normal file
35
mget/wget_test.go
Normal file
@ -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"
|
||||
}
|
45
net/cmd.go
45
net/cmd.go
@ -2,9 +2,14 @@ package net
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/netforward"
|
||||
"b612.me/apps/b612/tcm"
|
||||
"b612.me/apps/b612/tcping"
|
||||
"b612.me/apps/b612/tcpkill"
|
||||
"b612.me/starlog"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -33,6 +38,7 @@ var natt NatThroughs
|
||||
|
||||
var scanip ScanIP
|
||||
var scanport ScanPort
|
||||
var monitorip Monitor
|
||||
|
||||
func init() {
|
||||
CmdNatPClient.Flags().StringVarP(&natc.ServiceTarget, "target", "t", "", "forward server target address")
|
||||
@ -57,7 +63,7 @@ func init() {
|
||||
CmdNetTrace.Flags().IntVarP(&timeout, "timeout", "t", 800, "超时时间,单位毫秒")
|
||||
CmdNetTrace.Flags().IntVarP(&maxHop, "max-hop", "m", 32, "最大跳数")
|
||||
CmdNetTrace.Flags().BoolVarP(&disableIpInfo, "disable-ipinfo", "D", false, "禁用ip信息查询")
|
||||
CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "0.0.0.0", "绑定地址")
|
||||
CmdNetTrace.Flags().StringVarP(&bindAddr, "bind", "b", "", "绑定地址")
|
||||
CmdNetTrace.Flags().BoolVarP(&hideIncorrect, "hide-incorrect", "H", false, "隐藏错误节点")
|
||||
Cmd.AddCommand(CmdNetTrace, cmdSSHJar)
|
||||
|
||||
@ -81,6 +87,7 @@ func init() {
|
||||
CmdNatThrough.Flags().BoolVarP(&natt.AutoUPnP, "auto-upnp", "u", true, "自动UPnP")
|
||||
CmdNatThrough.Flags().IntVarP(&natt.WebPort, "web-port", "w", 8080, "web端口")
|
||||
CmdNatThrough.Flags().IntVarP(&natt.HealthCheckInterval, "health-check-interval", "H", 30, "健康检查间隔")
|
||||
CmdNatThrough.Flags().StringVarP(&natt.Type, "type", "t", "tcp", "穿透协议,tcp或udp")
|
||||
Cmd.AddCommand(CmdNatThrough)
|
||||
|
||||
CmdScanIP.Flags().StringVarP(&scanip.Host, "ip", "i", "", "扫描IP地址")
|
||||
@ -102,6 +109,18 @@ func init() {
|
||||
CmdScanPort.Flags().IntVarP(&scanport.Retry, "retry", "r", 2, "重试次数")
|
||||
Cmd.AddCommand(CmdScanPort)
|
||||
|
||||
CmdMonitorIP.Flags().StringSliceVarP(&monitorip.IPs, "ip", "i", []string{}, "扫描IP地址列表")
|
||||
CmdMonitorIP.Flags().IntVarP(&monitorip.Port, "port", "p", 80, "TCP模式扫描端口")
|
||||
CmdMonitorIP.Flags().IntVarP(&monitorip.Timeout, "timeout", "t", 1200, "超时时间,毫秒")
|
||||
CmdMonitorIP.Flags().IntVarP(&monitorip.Interval, "interval", "I", 1, "扫描间隔,秒")
|
||||
CmdMonitorIP.Flags().IntVarP(&monitorip.Threads, "threads", "m", 100, "最大线程数")
|
||||
CmdMonitorIP.Flags().StringVarP(&monitorip.Log, "log", "l", "", "日志文件地址")
|
||||
CmdMonitorIP.Flags().StringVarP(&monitorip.ScanType, "type", "T", "icmp", "扫描类型")
|
||||
CmdMonitorIP.Flags().IntVarP(&monitorip.Retry, "retry", "r", 1, "重试次数")
|
||||
CmdMonitorIP.Flags().BoolVarP(&monitorip.WithHostname, "with-hostname", "H", false, "显示主机名")
|
||||
Cmd.AddCommand(CmdMonitorIP)
|
||||
|
||||
Cmd.AddCommand(tcpkill.Cmd, tcping.Cmd, tcm.Cmd)
|
||||
}
|
||||
|
||||
var CmdNatPClient = &cobra.Command{
|
||||
@ -216,6 +235,30 @@ var CmdScanIP = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
var CmdMonitorIP = &cobra.Command{
|
||||
Use: "monitorip",
|
||||
Short: "监控IP",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(monitorip.IPs) == 0 {
|
||||
cmd.Help()
|
||||
return
|
||||
}
|
||||
monitorip.status = make(map[string]bool)
|
||||
monitorip.stopChan = make(chan struct{})
|
||||
sig := make(chan os.Signal)
|
||||
signal.Notify(sig, os.Interrupt, os.Kill)
|
||||
go func() {
|
||||
err := monitorip.Start()
|
||||
if err != nil {
|
||||
starlog.Errorln(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
<-sig
|
||||
monitorip.Stop()
|
||||
},
|
||||
}
|
||||
|
||||
var CmdScanPort = &cobra.Command{
|
||||
Use: "scanport",
|
||||
Short: "扫描端口",
|
||||
|
183
net/icmp.go
Normal file
183
net/icmp.go
Normal file
@ -0,0 +1,183 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"golang.org/x/net/ipv6"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
readBufferSize = 1500
|
||||
maxSeq = 0xffff
|
||||
)
|
||||
|
||||
type Pinger struct {
|
||||
icmpID int
|
||||
conn4 *icmp.PacketConn
|
||||
conn6 *icmp.PacketConn
|
||||
connOnce4 sync.Once
|
||||
connOnce6 sync.Once
|
||||
connErr4 error
|
||||
connErr6 error
|
||||
sequenceNum4 uint32
|
||||
sequenceNum6 uint32
|
||||
pending sync.Map
|
||||
closed chan struct{}
|
||||
}
|
||||
|
||||
type pendingKey struct {
|
||||
proto string
|
||||
seq int
|
||||
}
|
||||
|
||||
func NewPinger(icmpID int) *Pinger {
|
||||
return &Pinger{
|
||||
icmpID: icmpID & 0xffff,
|
||||
closed: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) Close() error {
|
||||
close(p.closed)
|
||||
var err error
|
||||
if p.conn4 != nil {
|
||||
if e := p.conn4.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
if p.conn6 != nil {
|
||||
if e := p.conn6.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *Pinger) Ping(ip string, timeout time.Duration) error {
|
||||
var conn *icmp.PacketConn
|
||||
var proto string
|
||||
var sequence *uint32
|
||||
dest, err := net.ResolveIPAddr("ip", ip)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolve IP address error: %w", err)
|
||||
}
|
||||
if dest.IP.To4() != nil {
|
||||
// IPv4处理
|
||||
p.connOnce4.Do(func() {
|
||||
p.conn4, p.connErr4 = icmp.ListenPacket("ip4:icmp", "0.0.0.0")
|
||||
if p.connErr4 == nil {
|
||||
go p.receiveLoop(p.conn4, "ip4")
|
||||
}
|
||||
})
|
||||
if p.connErr4 != nil {
|
||||
return fmt.Errorf("ICMPv4 connection error: %w", p.connErr4)
|
||||
}
|
||||
conn = p.conn4
|
||||
proto = "ip4"
|
||||
sequence = &p.sequenceNum4
|
||||
} else {
|
||||
// IPv6处理
|
||||
p.connOnce6.Do(func() {
|
||||
p.conn6, p.connErr6 = icmp.ListenPacket("ip6:ipv6-icmp", "::")
|
||||
if p.connErr6 == nil {
|
||||
go p.receiveLoop(p.conn6, "ip6")
|
||||
}
|
||||
})
|
||||
if p.connErr6 != nil {
|
||||
return fmt.Errorf("ICMPv6 connection error: %w", p.connErr6)
|
||||
}
|
||||
conn = p.conn6
|
||||
proto = "ip6"
|
||||
sequence = &p.sequenceNum6
|
||||
}
|
||||
|
||||
seq := int(atomic.AddUint32(sequence, 1) & maxSeq)
|
||||
key := pendingKey{proto, seq}
|
||||
resultChan := make(chan struct{})
|
||||
p.pending.Store(key, resultChan)
|
||||
defer p.pending.Delete(key)
|
||||
|
||||
var msgType icmp.Type
|
||||
if proto == "ip4" {
|
||||
msgType = ipv4.ICMPTypeEcho
|
||||
} else {
|
||||
msgType = ipv6.ICMPTypeEchoRequest
|
||||
}
|
||||
|
||||
msg := &icmp.Message{
|
||||
Type: msgType,
|
||||
Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: p.icmpID,
|
||||
Seq: seq,
|
||||
Data: []byte("HELLO-PING"),
|
||||
},
|
||||
}
|
||||
|
||||
packet, err := msg.Marshal(nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal error: %w", err)
|
||||
}
|
||||
|
||||
if _, err := conn.WriteTo(packet, dest); err != nil {
|
||||
return fmt.Errorf("write error: %w", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-resultChan:
|
||||
return nil
|
||||
case <-time.After(timeout):
|
||||
return fmt.Errorf("timeout")
|
||||
case <-p.closed:
|
||||
return fmt.Errorf("pinger closed")
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pinger) receiveLoop(conn *icmp.PacketConn, proto string) {
|
||||
buffer := make([]byte, readBufferSize)
|
||||
for {
|
||||
select {
|
||||
case <-p.closed:
|
||||
return
|
||||
default:
|
||||
n, _, err := conn.ReadFrom(buffer)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var expectedType icmp.Type
|
||||
var protocol int
|
||||
if proto == "ip4" {
|
||||
expectedType = ipv4.ICMPTypeEchoReply
|
||||
protocol = 1 // ICMPv4协议号
|
||||
} else {
|
||||
expectedType = ipv6.ICMPTypeEchoReply
|
||||
protocol = 58 // ICMPv6协议号
|
||||
}
|
||||
|
||||
msg, err := icmp.ParseMessage(protocol, buffer[:n])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if msg.Type != expectedType {
|
||||
continue
|
||||
}
|
||||
|
||||
echo, ok := msg.Body.(*icmp.Echo)
|
||||
if !ok || echo.ID != p.icmpID {
|
||||
continue
|
||||
}
|
||||
|
||||
key := pendingKey{proto, echo.Seq}
|
||||
if ch, exists := p.pending.LoadAndDelete(key); exists {
|
||||
close(ch.(chan struct{}))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
193
net/monitorip.go
Normal file
193
net/monitorip.go
Normal file
@ -0,0 +1,193 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"b612.me/apps/b612/netforward"
|
||||
"b612.me/starlog"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
IPs []string
|
||||
Port int
|
||||
ScanType string
|
||||
Timeout int
|
||||
Interval int
|
||||
Log string
|
||||
Retry int
|
||||
Threads int
|
||||
WithHostname bool
|
||||
pinger *Pinger
|
||||
status map[string]bool
|
||||
statusLock sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func NewMonitor(ips []string, scanType string) *Monitor {
|
||||
return &Monitor{
|
||||
IPs: ips,
|
||||
ScanType: scanType,
|
||||
status: make(map[string]bool),
|
||||
stopChan: make(chan struct{}),
|
||||
Timeout: 1000,
|
||||
Interval: 1,
|
||||
Retry: 1,
|
||||
Threads: 50,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) Start() error {
|
||||
if m.Log != "" {
|
||||
starlog.SetLogFile(m.Log, starlog.Std, true)
|
||||
}
|
||||
m.pinger = NewPinger(1127)
|
||||
// Initialize status
|
||||
m.statusLock.Lock()
|
||||
for _, ip := range m.IPs {
|
||||
m.status[ip] = false // Initial state as down
|
||||
}
|
||||
m.statusLock.Unlock()
|
||||
|
||||
ticker := time.NewTicker(time.Duration(m.Interval) * time.Second)
|
||||
defer ticker.Stop()
|
||||
m.checkAllIPs()
|
||||
m.displayStatus()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
m.checkAllIPs()
|
||||
m.displayStatus()
|
||||
case <-m.stopChan:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) Stop() {
|
||||
close(m.stopChan)
|
||||
}
|
||||
|
||||
func (m *Monitor) checkAllIPs() {
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, m.Threads)
|
||||
|
||||
for _, ip := range m.IPs {
|
||||
wg.Add(1)
|
||||
sem <- struct{}{}
|
||||
|
||||
go func(ip string) {
|
||||
defer func() {
|
||||
<-sem
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
currentStatus := m.checkIP(ip)
|
||||
m.updateStatus(ip, currentStatus)
|
||||
}(ip)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func (m *Monitor) checkIP(ip string) bool {
|
||||
for i := 0; i < m.Retry+1; i++ {
|
||||
var success bool
|
||||
var err error
|
||||
|
||||
switch m.ScanType {
|
||||
case "icmp":
|
||||
err = m.pinger.Ping(ip, time.Duration(m.Timeout)*time.Millisecond)
|
||||
success = err == nil
|
||||
case "tcp":
|
||||
dialer := net.Dialer{
|
||||
Timeout: time.Duration(m.Timeout) * time.Millisecond,
|
||||
Control: netforward.ControlSetReUseAddr,
|
||||
}
|
||||
conn, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, m.Port))
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
success = true
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
return true
|
||||
}
|
||||
|
||||
if i < m.Retry {
|
||||
time.Sleep(time.Duration(m.Timeout) * time.Millisecond / 2)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *Monitor) updateStatus(ip string, current bool) {
|
||||
m.statusLock.Lock()
|
||||
defer m.statusLock.Unlock()
|
||||
|
||||
previous := m.status[ip]
|
||||
if current != previous {
|
||||
m.logStatusChange(ip, previous, current)
|
||||
m.status[ip] = current
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Monitor) logStatusChange(ip string, from, to bool) {
|
||||
statusToStr := func(s bool) string {
|
||||
if s {
|
||||
return "UP"
|
||||
}
|
||||
return "DOWN"
|
||||
}
|
||||
|
||||
var hostname string
|
||||
if m.WithHostname {
|
||||
names, err := net.LookupAddr(ip)
|
||||
if err == nil && len(names) > 0 {
|
||||
hostname = names[0]
|
||||
}
|
||||
}
|
||||
|
||||
starlog.Infof("[Status Change] %s (%s): %s → %s\n",
|
||||
ip,
|
||||
hostname,
|
||||
statusToStr(from),
|
||||
statusToStr(to),
|
||||
)
|
||||
}
|
||||
|
||||
func (m *Monitor) displayStatus() {
|
||||
fmt.Print("\033[H\033[2J") // Clear screen
|
||||
fmt.Printf("Monitoring Status (%s)\n", time.Now().Format("2006-01-02 15:04:05"))
|
||||
fmt.Println("======================================")
|
||||
|
||||
m.statusLock.RLock()
|
||||
defer m.statusLock.RUnlock()
|
||||
|
||||
for _, ip := range m.IPs {
|
||||
status := m.status[ip]
|
||||
statusStr := "DOWN"
|
||||
if status {
|
||||
statusStr = "UP"
|
||||
}
|
||||
|
||||
var hostname string
|
||||
if m.WithHostname {
|
||||
names, err := net.LookupAddr(ip)
|
||||
if err == nil && len(names) > 0 {
|
||||
hostname = names[0]
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%-15s", ip)
|
||||
if m.WithHostname {
|
||||
fmt.Printf(" (%s)", hostname)
|
||||
}
|
||||
if statusStr == "UP" {
|
||||
starlog.Green(": [%s]\n", statusStr)
|
||||
} else {
|
||||
starlog.Red(": [%s]\n", statusStr)
|
||||
}
|
||||
}
|
||||
}
|
167
net/natt.go
167
net/natt.go
@ -3,6 +3,7 @@ package net
|
||||
import (
|
||||
"b612.me/apps/b612/netforward"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starmap"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
@ -27,6 +28,7 @@ type NatThroughs struct {
|
||||
STUN string
|
||||
Remote string
|
||||
HealthCheckInterval int
|
||||
Type string
|
||||
}
|
||||
|
||||
func (n *NatThroughs) Close() {
|
||||
@ -48,19 +50,23 @@ func (n *NatThroughs) Parse(reqs []string) error {
|
||||
if n.STUN == "" {
|
||||
n.STUN = "turn.b612.me:3478"
|
||||
}
|
||||
if n.Type == "" {
|
||||
n.Type = "tcp"
|
||||
}
|
||||
for _, v := range reqs {
|
||||
var req = NatThrough{
|
||||
Forward: netforward.NetForward{
|
||||
LocalAddr: "0.0.0.0",
|
||||
DialTimeout: time.Second * 5,
|
||||
UDPTimeout: time.Second * 20,
|
||||
UDPTimeout: time.Second * 30,
|
||||
KeepAlivePeriod: n.KeepAlivePeriod,
|
||||
KeepAliveIdel: n.KeepAliveIdel,
|
||||
KeepAliveCount: n.KeepAliveCount,
|
||||
UsingKeepAlive: true,
|
||||
EnableTCP: true,
|
||||
EnableUDP: true,
|
||||
},
|
||||
Type: "tcp",
|
||||
Type: n.Type,
|
||||
STUN: n.STUN,
|
||||
Remote: n.Remote,
|
||||
KeepAlivePeriod: n.KeepAlivePeriod,
|
||||
@ -72,7 +78,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
|
||||
strs := strings.Split(v, ",")
|
||||
switch len(strs) {
|
||||
case 1:
|
||||
req.Type = "tcp"
|
||||
req.Forward.RemoteURI = strs[0]
|
||||
case 2:
|
||||
ipport := strings.Split(strs[0], ":")
|
||||
@ -90,7 +95,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
|
||||
}
|
||||
req.Forward.LocalPort = port
|
||||
}
|
||||
req.Type = "tcp"
|
||||
req.Forward.RemoteURI = strs[1]
|
||||
case 3:
|
||||
ipport := strings.Split(strs[1], ":")
|
||||
@ -108,7 +112,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
|
||||
}
|
||||
req.Forward.LocalPort = port
|
||||
}
|
||||
req.Type = "tcp"
|
||||
req.Forward.RemoteURI = strs[2]
|
||||
req.Name = strs[0]
|
||||
case 4:
|
||||
@ -144,7 +147,7 @@ func (n *NatThroughs) Run() error {
|
||||
go func(v *NatThrough) {
|
||||
defer wg.Done()
|
||||
if err := v.Run(); err != nil {
|
||||
starlog.Errorf("Failed to run forward: %v\n", err)
|
||||
starlog.Errorf("Failed to run natThrough: %v\n", err)
|
||||
}
|
||||
v.HealthCheck()
|
||||
}(v)
|
||||
@ -262,6 +265,7 @@ func (c *NatThrough) Run() error {
|
||||
c.Forward.EnableTCP = false
|
||||
c.Forward.EnableUDP = true
|
||||
}
|
||||
starlog.Infof("NatThrough Type: %s\n", c.Type)
|
||||
starlog.Infof("Local Port: %d\n", c.Forward.LocalPort)
|
||||
starlog.Infof("Keepalive To: %s\n", c.Remote)
|
||||
starlog.Infof("Forward To: %s\n", c.Forward.RemoteURI)
|
||||
@ -294,7 +298,7 @@ func (c *NatThrough) Run() error {
|
||||
}
|
||||
go func() {
|
||||
if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil {
|
||||
starlog.Errorf("Failed to run forward: %v\n", err)
|
||||
starlog.Errorf("Failed to run keepalive: %v\n", err)
|
||||
c.Forward.Close()
|
||||
c.stopFn()
|
||||
}
|
||||
@ -319,6 +323,26 @@ func (c *NatThrough) Run() error {
|
||||
}
|
||||
|
||||
func (c *NatThrough) HealthCheck() {
|
||||
getIP := func(ip net.Addr) string {
|
||||
switch ip.(type) {
|
||||
case *net.TCPAddr:
|
||||
return ip.(*net.TCPAddr).IP.String()
|
||||
case *net.UDPAddr:
|
||||
return ip.(*net.UDPAddr).IP.String()
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
getPort := func(ip net.Addr) int {
|
||||
switch ip.(type) {
|
||||
case *net.TCPAddr:
|
||||
return ip.(*net.TCPAddr).Port
|
||||
case *net.UDPAddr:
|
||||
return ip.(*net.UDPAddr).Port
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
count := 0
|
||||
if c.HealthCheckInterval == 0 {
|
||||
c.HealthCheckInterval = 30
|
||||
@ -330,16 +354,30 @@ func (c *NatThrough) HealthCheck() {
|
||||
case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)):
|
||||
}
|
||||
if c.Type == "udp" {
|
||||
continue
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
|
||||
if err != nil {
|
||||
starlog.Warningf("Health Check Fail: %v\n", err)
|
||||
count++
|
||||
_, extIp, err := c.UdpKeppaliveSTUN(c.Forward.UdpListener(), c.STUN)
|
||||
if err != nil {
|
||||
count++
|
||||
starlog.Errorf("Health Check Error: %v\n", err)
|
||||
continue
|
||||
}
|
||||
extUrl := fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp))
|
||||
if c.ExtUrl != extUrl {
|
||||
count++
|
||||
} else {
|
||||
count = 0
|
||||
}
|
||||
starlog.Noticef("Health Check:Origin %s,Current %s\n", c.ExtUrl, extUrl)
|
||||
} else {
|
||||
starlog.Infof("Health Check Ok\n")
|
||||
conn.(*net.TCPConn).SetLinger(0)
|
||||
conn.Close()
|
||||
conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
|
||||
if err != nil {
|
||||
starlog.Warningf("Health Check Fail: %v\n", err)
|
||||
count++
|
||||
} else {
|
||||
count = 0
|
||||
starlog.Infof("Health Check Ok\n")
|
||||
conn.(*net.TCPConn).SetLinger(0)
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
if count >= 3 {
|
||||
count = 0
|
||||
@ -412,20 +450,20 @@ func (c *NatThrough) KeepAlive(localAddr string, localPort int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(localAddr), Port: localPort}, rmtUdpAddr)
|
||||
if err != nil {
|
||||
starlog.Errorf("Failed to dial remote: %v\n", err)
|
||||
if c.Forward.UdpListener() == nil {
|
||||
time.Sleep(time.Second * 5)
|
||||
continue
|
||||
}
|
||||
c.keepaliveConn = conn
|
||||
c.keepaliveConn = c.Forward.UdpListener()
|
||||
for {
|
||||
_, err = conn.Write([]byte("b612 tcp nat through"))
|
||||
_, err = c.Forward.UdpListener().WriteTo([]byte("b612 udp nat through"), rmtUdpAddr)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
c.keepaliveConn.Close()
|
||||
starlog.Warningf("Failed to keepalive remote: %v\n", err)
|
||||
time.Sleep(time.Second * 30)
|
||||
break
|
||||
}
|
||||
starlog.Infof("UDP Keepalive Ok! %v\n", rmtUdpAddr.String())
|
||||
time.Sleep(time.Second * 30)
|
||||
}
|
||||
}
|
||||
@ -482,6 +520,7 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
|
||||
conn.(*net.TCPConn).SetLinger(0)
|
||||
}
|
||||
if netType == "udp" {
|
||||
|
||||
conn, err = net.DialUDP(netType, &net.UDPAddr{IP: net.ParseIP(localip), Port: localPort}, stunAddr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
|
||||
@ -551,6 +590,90 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
|
||||
return innerAddr, outerAddr, nil
|
||||
}
|
||||
|
||||
func (c *NatThrough) UdpKeppaliveSTUN(conn *net.UDPConn, stunServer string) (net.Addr, net.Addr, error) {
|
||||
// 替换为你的 TURN 服务器地址
|
||||
var target *starmap.StarStack
|
||||
{
|
||||
tmpConn, err := net.Dial("udp", stunServer)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
|
||||
}
|
||||
if c.Forward.UdpHooks == nil {
|
||||
c.Forward.UdpHooks = make(map[string]*starmap.StarStack)
|
||||
}
|
||||
if c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] == nil {
|
||||
c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] = starmap.NewStarStack(16)
|
||||
}
|
||||
target = c.Forward.UdpHooks[tmpConn.RemoteAddr().String()]
|
||||
tmpConn.Close()
|
||||
}
|
||||
stunAddr, err := net.ResolveUDPAddr("udp", stunServer)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err)
|
||||
}
|
||||
innerAddr := conn.LocalAddr()
|
||||
|
||||
// Create STUN request
|
||||
transactionID := make([]byte, 12)
|
||||
rand.Read(transactionID)
|
||||
stunRequest := make([]byte, 20)
|
||||
binary.BigEndian.PutUint16(stunRequest[0:], 0x0001) // Message Type: Binding Request
|
||||
binary.BigEndian.PutUint16(stunRequest[2:], 0x0000) // Message Length
|
||||
copy(stunRequest[4:], []byte{0x21, 0x12, 0xa4, 0x42}) // Magic Cookie
|
||||
copy(stunRequest[8:], transactionID) // Transaction ID
|
||||
|
||||
_, err = conn.WriteToUDP(stunRequest, stunAddr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to send STUN request: %v", err)
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 2500)
|
||||
tmp, err := target.Pop()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err)
|
||||
}
|
||||
buf := tmp.([]byte)
|
||||
n := len(buf)
|
||||
|
||||
// Parse STUN response
|
||||
if n < 20 {
|
||||
return nil, nil, fmt.Errorf("invalid STUN response")
|
||||
}
|
||||
|
||||
payload := buf[20:n]
|
||||
var ip uint32
|
||||
var port uint16
|
||||
for len(payload) > 0 {
|
||||
attrType := binary.BigEndian.Uint16(payload[0:])
|
||||
attrLen := binary.BigEndian.Uint16(payload[2:])
|
||||
if len(payload) < int(4+attrLen) {
|
||||
return nil, nil, fmt.Errorf("invalid STUN attribute length")
|
||||
}
|
||||
|
||||
if attrType == 0x0001 || attrType == 0x0020 {
|
||||
port = binary.BigEndian.Uint16(payload[6:])
|
||||
ip = binary.BigEndian.Uint32(payload[8:])
|
||||
if attrType == 0x0020 {
|
||||
port ^= 0x2112
|
||||
ip ^= 0x2112a442
|
||||
}
|
||||
break
|
||||
}
|
||||
payload = payload[4+attrLen:]
|
||||
}
|
||||
|
||||
if ip == 0 || port == 0 {
|
||||
return nil, nil, fmt.Errorf("invalid STUN response")
|
||||
}
|
||||
|
||||
outerAddr := &net.UDPAddr{
|
||||
IP: net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)),
|
||||
Port: int(port),
|
||||
}
|
||||
|
||||
return innerAddr, outerAddr, nil
|
||||
}
|
||||
|
||||
func (c *NatThrough) GetMyOutIP() string {
|
||||
tmp, err := net.Dial("udp", "8.8.8.8:53")
|
||||
if err != nil {
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"b612.me/apps/b612/netforward"
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
@ -153,6 +152,7 @@ func (s *ScanIP) ICMP() error {
|
||||
}
|
||||
}()
|
||||
idx := 0
|
||||
pinger := NewPinger(1127)
|
||||
for {
|
||||
ip := firstIP.String()
|
||||
if ip == lastIP.String() {
|
||||
@ -166,7 +166,7 @@ func (s *ScanIP) ICMP() error {
|
||||
}()
|
||||
defer wg.Done()
|
||||
for i := 0; i < s.Retry+1; i++ {
|
||||
_, err := starnet.Ping(ip, idx, time.Duration(s.Timeout)*time.Millisecond)
|
||||
err := pinger.Ping(ip, time.Duration(s.Timeout)*time.Millisecond)
|
||||
if err == nil {
|
||||
atomic.AddInt32(&count, 1)
|
||||
if s.WithHostname {
|
||||
@ -255,8 +255,10 @@ func (s *ScanIP) TCP(port int) error {
|
||||
Timeout: time.Duration(s.Timeout) * time.Millisecond,
|
||||
Control: netforward.ControlSetReUseAddr,
|
||||
}
|
||||
_, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, port))
|
||||
conn, err := dialer.Dial("tcp", fmt.Sprintf("%s:%d", ip, port))
|
||||
if err == nil {
|
||||
conn.(*net.TCPConn).SetLinger(0)
|
||||
conn.Close()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if s.WithHostname {
|
||||
hostname, err := net.LookupAddr(ip)
|
||||
|
@ -3,6 +3,7 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
@ -33,3 +34,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
|
||||
}
|
||||
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 net
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"net"
|
||||
"syscall"
|
||||
)
|
||||
@ -37,3 +38,25 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
|
||||
}
|
||||
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
|
||||
}
|
||||
|
@ -11,6 +11,9 @@ import (
|
||||
)
|
||||
|
||||
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
|
||||
//windows上,这两个值是毫秒,linux上则是秒
|
||||
keepAlivePeriod *= 1000
|
||||
keepAliveIdel *= 1000
|
||||
if usingKeepAlive {
|
||||
rawConn, err := conn.SyscallConn()
|
||||
if err != nil {
|
||||
@ -31,3 +34,16 @@ func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlive
|
||||
}
|
||||
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
|
||||
}
|
||||
|
244
net/sshjar.go
244
net/sshjar.go
@ -9,6 +9,8 @@ import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@ -23,6 +25,9 @@ var (
|
||||
curlUrl string
|
||||
serverVersion string
|
||||
curlArg []string
|
||||
passwds []string
|
||||
allowAny bool
|
||||
logPath string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -31,6 +36,10 @@ func init() {
|
||||
cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码")
|
||||
cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件")
|
||||
cmdSSHJar.Flags().StringVarP(&serverVersion, "version", "v", "SSH-2.0-OpenSSH_8.0", "SSH版本")
|
||||
cmdSSHJar.Flags().StringVarP(&curlUrl, "curl", "c", "", "Curl URL")
|
||||
cmdSSHJar.Flags().StringSliceVarP(&passwds, "allow-passwds", "P", nil, "密码列表,格式:[用户名]:[密码]")
|
||||
cmdSSHJar.Flags().BoolVarP(&allowAny, "allow-any", "A", false, "允许任意密码登录")
|
||||
cmdSSHJar.Flags().StringVarP(&logPath, "log", "L", "", "日志文件")
|
||||
}
|
||||
|
||||
var cmdSSHJar = &cobra.Command{
|
||||
@ -38,11 +47,41 @@ var cmdSSHJar = &cobra.Command{
|
||||
Short: "SSH蜜罐",
|
||||
Long: "SSH蜜罐",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, serverVersion)
|
||||
var mypwds [][]string
|
||||
for _, v := range passwds {
|
||||
args := strings.SplitN(v, ":", 2)
|
||||
if len(args) == 2 {
|
||||
mypwds = append(mypwds, args)
|
||||
}
|
||||
}
|
||||
runSSHHoneyJar(SSHJar{
|
||||
listenAddr: listenAddr,
|
||||
keyFile: keyFile,
|
||||
keyPasswd: KeyPasswd,
|
||||
outpath: outpath,
|
||||
logpath: logPath,
|
||||
version: serverVersion,
|
||||
passwds: mypwds,
|
||||
allowAny: allowAny,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
|
||||
type SSHJar struct {
|
||||
listenAddr string
|
||||
keyFile string
|
||||
keyPasswd string
|
||||
outpath string
|
||||
logpath string
|
||||
version string
|
||||
passwds [][]string
|
||||
allowAny bool
|
||||
}
|
||||
|
||||
func runSSHHoneyJar(jar SSHJar) {
|
||||
if jar.logpath != "" {
|
||||
starlog.SetLogFile(jar.logpath, starlog.Std, true)
|
||||
}
|
||||
var f *os.File
|
||||
var err error
|
||||
if outpath != "" {
|
||||
@ -56,10 +95,10 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
|
||||
defer f.Close()
|
||||
defer conn.Flush()
|
||||
config := &ssh.ServerConfig{
|
||||
ServerVersion: version,
|
||||
ServerVersion: jar.version,
|
||||
// 密码验证回调函数
|
||||
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass))
|
||||
starlog.Infof("Login attempt from %s with %s by %s\n", c.RemoteAddr(), c.User(), string(pass))
|
||||
data := []string{time.Now().Format("2006-01-02 15:04:05"), c.RemoteAddr().String(), c.User(), string(pass)}
|
||||
if f != nil {
|
||||
conn.Write(data)
|
||||
@ -79,13 +118,34 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
|
||||
data[args[0]] = args[1]
|
||||
}
|
||||
}
|
||||
starnet.Curl(starnet.NewRequests(curlUrl, []byte(starnet.BuildQuery(data)), "POST"))
|
||||
starnet.NewSimpleRequest(curlUrl, "POST").SetBodyDataBytes([]byte(starnet.BuildQuery(data))).Do()
|
||||
}
|
||||
}()
|
||||
}
|
||||
perm := &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"user": c.User(),
|
||||
"passwd": string(pass),
|
||||
},
|
||||
}
|
||||
if jar.allowAny {
|
||||
return perm, nil
|
||||
}
|
||||
for _, v := range jar.passwds {
|
||||
if c.User() == v[0] && string(pass) == v[1] {
|
||||
return perm, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
},
|
||||
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
|
||||
if jar.allowAny {
|
||||
return &ssh.Permissions{
|
||||
Extensions: map[string]string{
|
||||
"user": conn.User(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("public key rejected for %q", conn.User())
|
||||
},
|
||||
}
|
||||
@ -132,9 +192,179 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
|
||||
}
|
||||
starlog.Infof("New connection from %s\n", conn.RemoteAddr())
|
||||
go func(conn net.Conn) {
|
||||
ssh.NewServerConn(conn, config)
|
||||
conn.Close()
|
||||
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
|
||||
if err != nil {
|
||||
starlog.Errorf("SSH handshake failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer sConn.Close()
|
||||
defer starlog.Noticef("Connection from %s closed\n", sConn.RemoteAddr())
|
||||
go ssh.DiscardRequests(reqs)
|
||||
for newChannel := range chans {
|
||||
if newChannel.ChannelType() != "session" {
|
||||
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
|
||||
continue
|
||||
}
|
||||
channel, requests, err := newChannel.Accept()
|
||||
if err != nil {
|
||||
starlog.Errorf("Failed to accept channel: %v", err)
|
||||
continue
|
||||
}
|
||||
go handleSession(channel, requests, sConn)
|
||||
}
|
||||
}(conn)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func handleSession(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) {
|
||||
defer channel.Close()
|
||||
term := terminal.NewTerminal(channel, "$ ") // 设置 shell 提示符
|
||||
term.AutoCompleteCallback = nil // 禁用自动补全
|
||||
for req := range requests {
|
||||
switch req.Type {
|
||||
case "pty-req":
|
||||
// 接受伪终端请求(攻击者希望获得交互式体验)
|
||||
req.Reply(true, nil)
|
||||
term.SetSize( // 简单设置终端尺寸
|
||||
24, // 行
|
||||
80, // 列
|
||||
)
|
||||
case "shell":
|
||||
req.Reply(true, nil)
|
||||
go func() {
|
||||
for {
|
||||
line, err := term.ReadLine()
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
starlog.Infof("[%s %s] Command: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], line)
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
term.Write([]byte(FakeCommand(line)))
|
||||
}
|
||||
}()
|
||||
case "exec":
|
||||
// 处理非交互式命令(如 ssh user@host 'ls -l')
|
||||
var payload struct{ Command string }
|
||||
ssh.Unmarshal(req.Payload, &payload)
|
||||
req.Reply(true, nil)
|
||||
|
||||
// 记录并返回假输出
|
||||
starlog.Infof("[%s %s] Exec: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], payload.Command)
|
||||
term.Write([]byte(FakeCommand(payload.Command)))
|
||||
channel.Close()
|
||||
default:
|
||||
req.Reply(false, nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func FakeCommand(cmd string) string {
|
||||
// 按命令类型分级模拟
|
||||
switch {
|
||||
//---------------- 系统信息探测类 ----------------
|
||||
case strings.Contains(cmd, "uname -a"):
|
||||
return "Linux core 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64 GNU/Linux\n\n"
|
||||
|
||||
case strings.Contains(cmd, "cat /etc/os-release"):
|
||||
return `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
|
||||
NAME="Debian GNU/Linux"
|
||||
VERSION_ID="11"
|
||||
VERSION="11 (bullseye)"
|
||||
VERSION_CODENAME=bullseye
|
||||
ID=debian
|
||||
HOME_URL="https://www.debian.org/"
|
||||
SUPPORT_URL="https://www.debian.org/support"
|
||||
BUG_REPORT_URL="https://bugs.debian.org/"
|
||||
`
|
||||
|
||||
case strings.Contains(cmd, "free -h"):
|
||||
return ` total used free shared buff/cache available
|
||||
Mem: 1022Gi 12Gi 3.0Gi 1.0Gi 47Gi 480Gi
|
||||
Swap: 0B 0B 0B
|
||||
`
|
||||
|
||||
//---------------- 敏感文件诱导类 ----------------
|
||||
case strings.Contains(cmd, "ls /home"):
|
||||
return "admin backup devops secret\n"
|
||||
|
||||
case strings.Contains(cmd, "ls /var/log"):
|
||||
return `auth.log apache2 payment_system.log database_backup.log
|
||||
`
|
||||
case strings.Contains(cmd, "ls"):
|
||||
return "password.txt\n"
|
||||
|
||||
case strings.Contains(cmd, "cat /etc/passwd"):
|
||||
return `root:x:0:0:root:/root:/bin/bash
|
||||
admin:x:1000:1000:,,,:/home/admin:/bin/bash
|
||||
mysql:x:106:113:MySQL Server,,,:/nonexistent:/bin/false
|
||||
core:x:0:0:,,,:/root:/bin/bash
|
||||
`
|
||||
|
||||
//---------------- 网络配置诱导类 ----------------
|
||||
case strings.Contains(cmd, "ifconfig") || strings.Contains(cmd, "ip addr"):
|
||||
return `eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
|
||||
inet 192.168.1.105 netmask 255.255.255.0 broadcast 192.168.1.255
|
||||
inet6 fe80::250:56ff:fec0:8888 prefixlen 64 scopeid 0x20<link>
|
||||
ether 00:50:56:c0:88:88 txqueuelen 1000 (Ethernet)
|
||||
RX packets 123456 bytes 123456789 (117.7 MiB)
|
||||
RX errors 0 dropped 0 overruns 0 frame 0
|
||||
TX packets 98765 bytes 9876543 (9.4 MiB)
|
||||
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
|
||||
|
||||
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
|
||||
inet 127.0.0.1 netmask 255.0.0.0
|
||||
loop txqueuelen 1000 (Local Loopback)
|
||||
`
|
||||
|
||||
case strings.Contains(cmd, "netstat -antp"):
|
||||
return `Active Internet connections (servers and established)
|
||||
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
|
||||
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
|
||||
tcp 0 0 192.168.1.105:5432 203.0.113.5:43892 ESTABLISHED 5678/postgres
|
||||
tcp6 0 0 :::8080 :::* LISTEN 91011/java
|
||||
`
|
||||
|
||||
//---------------- 凭证钓鱼类 ----------------
|
||||
case strings.Contains(cmd, "mysql -u root -p"):
|
||||
return "ERROR 1045 (28000): Access denied\n"
|
||||
|
||||
case strings.Contains(cmd, "sudo -l"):
|
||||
return `Matching Defaults entries for admin on this host:
|
||||
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
|
||||
|
||||
User admin may run the following commands on honeypot:
|
||||
(ALL) NOPASSWD: /usr/bin/vim /etc/shadow
|
||||
`
|
||||
|
||||
//---------------- 进程服务类 ----------------
|
||||
case strings.Contains(cmd, "ps aux"):
|
||||
return `USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
|
||||
root 1 0.0 0.1 169020 13184 ? Ss May01 0:12 /sbin/init
|
||||
admin 1234 0.3 2.1 1123456 178912 ? Sl May01 12:34 /opt/payment_system/payment_processor --debug
|
||||
`
|
||||
|
||||
//---------------- 定制化陷阱 ----------------
|
||||
case strings.Contains(cmd, "find / -name *.db"):
|
||||
return `/var/lib/mysql/transactions.db
|
||||
/home/backup/internal_users.db
|
||||
`
|
||||
|
||||
case strings.Contains(cmd, "curl"):
|
||||
return `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html><head>
|
||||
<title>404 Not Found</title>
|
||||
</head><body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL was not found on this server.</p>
|
||||
</body></html>
|
||||
`
|
||||
|
||||
default:
|
||||
// 模糊响应增加真实感
|
||||
if rand.Intn(100) > 70 { // 30%概率返回"command not found"
|
||||
return "sh: command not found: " + strings.Fields(cmd)[0] + "\n"
|
||||
}
|
||||
return "\n"
|
||||
}
|
||||
}
|
||||
|
@ -3,5 +3,5 @@ package net
|
||||
import "testing"
|
||||
|
||||
func TestSSHJar(t *testing.T) {
|
||||
runSSHHoneyJar("127.0.0.1:22", "", "", "./test.csv", "")
|
||||
//runSSHHoneyJar("127.0.0.1:22", "", "", "./test.csv", "")
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ import (
|
||||
)
|
||||
|
||||
type TcpClient struct {
|
||||
DialTimeout int
|
||||
LocalAddr string
|
||||
RemoteAddr string
|
||||
UsingKeepAlive bool
|
||||
KeepAlivePeriod int
|
||||
KeepAliveIdel int
|
||||
KeepAliveCount int
|
||||
UseLinger int
|
||||
Interactive bool
|
||||
UserTimeout int
|
||||
ShowRecv bool
|
||||
@ -155,17 +157,17 @@ func (s *TcpClient) Run() error {
|
||||
return fmt.Errorf("ResolveTCPAddr error: %w", err)
|
||||
}
|
||||
}
|
||||
remoteAddr, err := net.ResolveTCPAddr("tcp", s.RemoteAddr)
|
||||
if err != nil {
|
||||
starlog.Errorln("ResolveTCPAddr error:", err)
|
||||
return fmt.Errorf("ResolveTCPAddr error: %w", err)
|
||||
dialer := net.Dialer{
|
||||
LocalAddr: localAddr,
|
||||
Timeout: time.Duration(s.DialTimeout) * time.Second,
|
||||
Control: ControlSetReUseAddr,
|
||||
}
|
||||
|
||||
conn, err := net.DialTCP("tcp", localAddr, remoteAddr)
|
||||
tcpConn, err := dialer.Dial("tcp", s.RemoteAddr)
|
||||
if err != nil {
|
||||
starlog.Errorln("Dial TCP error:", err)
|
||||
return fmt.Errorf("Dial TCP error: %w", err)
|
||||
}
|
||||
conn := tcpConn.(*net.TCPConn)
|
||||
starlog.Infof("Connected to %s LocalAddr: %s\n", conn.RemoteAddr().String(), conn.LocalAddr().String())
|
||||
if s.Interactive {
|
||||
go s.handleInteractive()
|
||||
@ -200,6 +202,9 @@ func (s *TcpClient) handleConn(conn *TcpConn) {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if s.UseLinger >= 0 {
|
||||
conn.SetLinger(s.UseLinger)
|
||||
}
|
||||
log.Infof("SetKeepAlive success for %s\n", conn.RemoteAddr().String())
|
||||
log.Infof("KeepAlivePeriod: %d, KeepAliveIdel: %d, KeepAliveCount: %d, UserTimeout: %d\n", s.KeepAlivePeriod, s.KeepAliveIdel, s.KeepAliveCount, s.UserTimeout)
|
||||
if runtime.GOOS != "linux" {
|
||||
|
@ -25,6 +25,7 @@ func init() {
|
||||
CmdTcps.Flags().BoolVarP(&tcps.ShowAsHex, "show-hex", "H", false, "显示十六进制")
|
||||
CmdTcps.Flags().StringVarP(&tcps.SaveToFolder, "save", "s", "", "保存到文件夹")
|
||||
CmdTcps.Flags().StringVarP(&tcps.LogPath, "log", "L", "", "日志文件路径")
|
||||
CmdTcps.Flags().IntVarP(&tcpc.UseLinger, "linger", "g", -1, "Linger时间(秒)")
|
||||
Cmd.AddCommand(CmdTcps)
|
||||
|
||||
CmdTcpc.Flags().StringVarP(&tcpc.LocalAddr, "local", "l", "", "本地地址")
|
||||
@ -38,6 +39,8 @@ func init() {
|
||||
CmdTcpc.Flags().BoolVarP(&tcpc.ShowAsHex, "show-hex", "H", false, "显示十六进制")
|
||||
CmdTcpc.Flags().StringVarP(&tcpc.SaveToFolder, "save", "s", "", "保存到文件夹")
|
||||
CmdTcpc.Flags().StringVarP(&tcpc.LogPath, "log", "L", "", "日志文件路径")
|
||||
CmdTcpc.Flags().IntVarP(&tcpc.DialTimeout, "dial-timeout", "t", 5, "连接超时时间(秒)")
|
||||
CmdTcpc.Flags().IntVarP(&tcpc.UseLinger, "linger", "g", -1, "Linger时间(秒)")
|
||||
|
||||
Cmd.AddCommand(CmdTcpc)
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ type TcpServer struct {
|
||||
KeepAlivePeriod int
|
||||
KeepAliveIdel int
|
||||
KeepAliveCount int
|
||||
UseLinger int
|
||||
sync.Mutex
|
||||
Clients map[string]*TcpConn
|
||||
Interactive bool
|
||||
@ -233,6 +234,9 @@ func (s *TcpServer) handleConn(conn *TcpConn) {
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
if s.UseLinger >= 0 {
|
||||
conn.SetLinger(s.UseLinger)
|
||||
}
|
||||
log.Infof("SetKeepAlive success for %s\n", conn.RemoteAddr().String())
|
||||
log.Infof("KeepAlivePeriod: %d, KeepAliveIdel: %d, KeepAliveCount: %d, UserTimeout: %d\n", s.KeepAlivePeriod, s.KeepAliveIdel, s.KeepAliveCount, s.UserTimeout)
|
||||
if runtime.GOOS != "linux" {
|
||||
|
279
net/trace.go
279
net/trace.go
@ -4,7 +4,6 @@ import (
|
||||
"b612.me/starlog"
|
||||
"b612.me/starnet"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
@ -53,6 +52,7 @@ func Traceroute(address string, bindaddr string, dns string, maxHops int, timeou
|
||||
}
|
||||
traceroute(address, bindaddr, maxHops, timeout, ipinfoAddr, hideIncorrect)
|
||||
}
|
||||
|
||||
func traceroute(address string, bindaddr string, maxHops int, timeout time.Duration, ipinfoAddr string, hideIncorrect bool) {
|
||||
ipinfo := net.ParseIP(address)
|
||||
if ipinfo == nil {
|
||||
@ -60,26 +60,46 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat
|
||||
return
|
||||
}
|
||||
|
||||
var echoType icmp.Type = ipv4.ICMPTypeEcho
|
||||
var exceededType icmp.Type = ipv4.ICMPTypeTimeExceeded
|
||||
var replyType icmp.Type = ipv4.ICMPTypeEchoReply
|
||||
var proto = 1
|
||||
var network = "ip4:icmp"
|
||||
var resolveIP = "ip4"
|
||||
if ipinfo.To4() == nil {
|
||||
network = "ip6:ipv6-icmp"
|
||||
resolveIP = "ip6"
|
||||
var (
|
||||
echoType icmp.Type
|
||||
exceededType icmp.Type
|
||||
replyType icmp.Type
|
||||
unreachType icmp.Type
|
||||
proto int
|
||||
network string
|
||||
resolveIP string
|
||||
isIPv4 bool
|
||||
)
|
||||
|
||||
if ipinfo.To4() != nil {
|
||||
echoType = ipv4.ICMPTypeEcho
|
||||
exceededType = ipv4.ICMPTypeTimeExceeded
|
||||
replyType = ipv4.ICMPTypeEchoReply
|
||||
unreachType = ipv4.ICMPTypeDestinationUnreachable
|
||||
proto = 1
|
||||
network = "ip4:icmp"
|
||||
resolveIP = "ip4"
|
||||
isIPv4 = true
|
||||
} else {
|
||||
echoType = ipv6.ICMPTypeEchoRequest
|
||||
exceededType = ipv6.ICMPTypeTimeExceeded
|
||||
replyType = ipv6.ICMPTypeEchoReply
|
||||
unreachType = ipv6.ICMPTypeDestinationUnreachable
|
||||
proto = 58
|
||||
network = "ip6:ipv6-icmp"
|
||||
resolveIP = "ip6"
|
||||
isIPv4 = false
|
||||
}
|
||||
if bindaddr == "" {
|
||||
bindaddr = "0.0.0.0"
|
||||
if !isIPv4 {
|
||||
bindaddr = "::"
|
||||
}
|
||||
}
|
||||
|
||||
c, err := icmp.ListenPacket(network, bindaddr)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
starlog.Errorln("监听失败:", err)
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
@ -91,121 +111,179 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat
|
||||
if timeout == 0 {
|
||||
timeout = time.Second * 3
|
||||
}
|
||||
|
||||
exitfor:
|
||||
for i := 1; i <= maxHops; i++ {
|
||||
retry := 0
|
||||
doRetry:
|
||||
for ttl := 1; ttl <= maxHops; ttl++ {
|
||||
if atomic.LoadInt32(&firstTargetHop) <= int32(ttl) {
|
||||
return
|
||||
}
|
||||
|
||||
dst, err := net.ResolveIPAddr(resolveIP, address)
|
||||
if err != nil {
|
||||
starlog.Errorln("IP地址解析失败:", address, err)
|
||||
starlog.Errorln("解析失败:", address, err)
|
||||
return
|
||||
}
|
||||
if atomic.LoadInt32(&firstTargetHop) <= int32(i) {
|
||||
return
|
||||
}
|
||||
m := icmp.Message{
|
||||
|
||||
// 构造ICMP报文
|
||||
msg := icmp.Message{
|
||||
Type: echoType, Code: 0,
|
||||
Body: &icmp.Echo{
|
||||
ID: i, Seq: i,
|
||||
ID: ttl, // 使用TTL作为ID
|
||||
Seq: ttl, // 使用TTL作为序列号
|
||||
Data: []byte("B612.ME-ROUTER-TRACE"),
|
||||
},
|
||||
}
|
||||
|
||||
b, err := m.Marshal(nil)
|
||||
msgBytes, err := msg.Marshal(nil)
|
||||
if err != nil {
|
||||
fmt.Printf("%d\tMarshal error: %v\n", i, err)
|
||||
starlog.Warningf("%d\t封包失败: %v\n", ttl, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 设置TTL/HopLimit
|
||||
if network == "ip4:icmp" {
|
||||
if err := c.IPv4PacketConn().SetTTL(i); err != nil {
|
||||
fmt.Printf("%d\tSetTTL error: %v\n", i, err)
|
||||
if err := c.IPv4PacketConn().SetTTL(ttl); err != nil {
|
||||
starlog.Warningf("%d\t设置TTL失败: %v\n", ttl, err)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if err := c.IPv6PacketConn().SetHopLimit(i); err != nil {
|
||||
fmt.Printf("%d\tSetHopLimit error: %v\n", i, err)
|
||||
if err := c.IPv6PacketConn().SetHopLimit(ttl); err != nil {
|
||||
starlog.Warningf("%d\t设置HopLimit失败: %v\n", ttl, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
n, err := c.WriteTo(b, dst)
|
||||
if err != nil {
|
||||
fmt.Printf("%d\tWriteTo error: %v\n", i, err)
|
||||
continue
|
||||
} else if n != len(b) {
|
||||
fmt.Printf("%d\tWrite Short: %v Expected: %v\n", i, n, len(b))
|
||||
startTime := time.Now()
|
||||
if _, err := c.WriteTo(msgBytes, dst); err != nil {
|
||||
starlog.Warningf("%d\t发送失败: %v\n", ttl, err)
|
||||
continue
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
exitrecheck:
|
||||
for {
|
||||
reply := make([]byte, 1500)
|
||||
err = c.SetReadDeadline(time.Now().Add(timeout))
|
||||
if err != nil {
|
||||
fmt.Printf("%d\tSetReadDeadline error: %v\n", i, err)
|
||||
break
|
||||
}
|
||||
n, peer, err := c.ReadFrom(reply)
|
||||
if err != nil {
|
||||
fmt.Printf("%d\tReadFrom error: %v\n", i, err)
|
||||
break
|
||||
}
|
||||
duration := time.Since(start)
|
||||
// 接收响应处理
|
||||
timeoutCh := time.After(timeout)
|
||||
responsesReceived := 0
|
||||
|
||||
rm, err := icmp.ParseMessage(proto, reply[:n])
|
||||
if err != nil {
|
||||
fmt.Printf("%d\tParseMessage error: %v\n", i, err)
|
||||
break
|
||||
}
|
||||
|
||||
switch rm.Type {
|
||||
case exceededType:
|
||||
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
|
||||
break exitrecheck
|
||||
case replyType:
|
||||
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
|
||||
if peer.String() == dst.String() {
|
||||
break exitfor
|
||||
}
|
||||
case ipv4.ICMPTypeEcho, ipv6.ICMPTypeEchoRequest:
|
||||
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
|
||||
if retry < 1 {
|
||||
retry++
|
||||
goto doRetry
|
||||
}
|
||||
if !hideIncorrect {
|
||||
fmt.Printf("%d\tInvalid Echo Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
|
||||
}
|
||||
break exitrecheck
|
||||
}
|
||||
case ipv4.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeDestinationUnreachable:
|
||||
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
|
||||
if retry < 1 {
|
||||
retry++
|
||||
goto doRetry
|
||||
}
|
||||
if !hideIncorrect {
|
||||
fmt.Printf("%d\tInvalid DstInv Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
|
||||
}
|
||||
break exitrecheck
|
||||
recvLoop:
|
||||
for responsesReceived < 3 {
|
||||
select {
|
||||
case <-timeoutCh:
|
||||
if responsesReceived == 0 {
|
||||
fmt.Printf("%d\t*\n", ttl)
|
||||
}
|
||||
break recvLoop
|
||||
default:
|
||||
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
|
||||
if retry < 1 {
|
||||
retry++
|
||||
goto doRetry
|
||||
reply := make([]byte, 1500)
|
||||
if err := c.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
|
||||
break recvLoop
|
||||
}
|
||||
|
||||
n, peer, err := c.ReadFrom(reply)
|
||||
if err != nil {
|
||||
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
|
||||
continue
|
||||
}
|
||||
if !hideIncorrect {
|
||||
fmt.Printf("%d\tgot %+v from %v (%s) %s\n", i, rm.Type, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
|
||||
starlog.Debugf("%d\t接收错误: %v", ttl, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
rm, err := icmp.ParseMessage(proto, reply[:n])
|
||||
if err != nil {
|
||||
starlog.Debugf("%d\t解析错误: %v", ttl, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 验证响应匹配
|
||||
if match := checkResponseMatch(rm, ttl, peer.String() == dst.String(), isIPv4, exceededType, replyType, unreachType); match {
|
||||
duration := time.Since(startTime)
|
||||
fmt.Printf("%d\t%s\t%s\t%s\n",
|
||||
ttl,
|
||||
peer,
|
||||
duration.Round(time.Millisecond),
|
||||
GetIPInfo(peer.String(), ipinfoAddr),
|
||||
)
|
||||
responsesReceived++
|
||||
|
||||
if peer.String() == dst.String() {
|
||||
atomic.StoreInt32(&firstTargetHop, int32(ttl))
|
||||
break exitfor
|
||||
}
|
||||
break exitrecheck
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func checkResponseMatch(rm *icmp.Message, ttl int, isFinal bool, isIPv4 bool,
|
||||
exceededType, replyType, unreachType icmp.Type) bool {
|
||||
|
||||
switch {
|
||||
case rm.Type == exceededType:
|
||||
if body, ok := rm.Body.(*icmp.TimeExceeded); ok {
|
||||
return validateOriginalPacket(body.Data, ttl, isIPv4)
|
||||
}
|
||||
|
||||
case rm.Type == replyType:
|
||||
if isFinal {
|
||||
if body, ok := rm.Body.(*icmp.Echo); ok {
|
||||
return body.ID == ttl && body.Seq == ttl
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
case rm.Type == unreachType:
|
||||
if body, ok := rm.Body.(*icmp.DstUnreach); ok {
|
||||
return validateOriginalPacket(body.Data, ttl, isIPv4)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func validateOriginalPacket(data []byte, ttl int, isIPv4 bool) bool {
|
||||
var (
|
||||
proto byte
|
||||
header []byte
|
||||
)
|
||||
|
||||
if isIPv4 {
|
||||
if len(data) < 20+8 {
|
||||
return false
|
||||
}
|
||||
ihl := data[0] & 0x0F
|
||||
if ihl < 5 {
|
||||
return false
|
||||
}
|
||||
proto = data[9]
|
||||
header = data[:ihl*4]
|
||||
} else {
|
||||
if len(data) < 40+8 {
|
||||
return false
|
||||
}
|
||||
proto = data[6]
|
||||
header = data[:40]
|
||||
}
|
||||
|
||||
if proto != 1 && proto != 58 {
|
||||
return false
|
||||
}
|
||||
|
||||
payload := data[len(header):]
|
||||
if len(payload) < 8 {
|
||||
return false
|
||||
}
|
||||
|
||||
if isIPv4 {
|
||||
return payload[0] == 8 && // ICMP Echo Request
|
||||
payload[4] == byte(ttl>>8) &&
|
||||
payload[5] == byte(ttl) &&
|
||||
payload[6] == byte(ttl>>8) &&
|
||||
payload[7] == byte(ttl)
|
||||
} else {
|
||||
return payload[0] == 128 && // ICMPv6 Echo Request
|
||||
payload[4] == byte(ttl>>8) &&
|
||||
payload[5] == byte(ttl) &&
|
||||
payload[6] == byte(ttl>>8) &&
|
||||
payload[7] == byte(ttl)
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,16 +292,23 @@ func GetIPInfo(ip string, addr string) string {
|
||||
return ""
|
||||
}
|
||||
uri := strings.ReplaceAll(addr, "{ip}", ip)
|
||||
res, err := starnet.Curl(starnet.NewRequests(uri, nil, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3)))
|
||||
res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET",
|
||||
starnet.WithTimeout(time.Second*2),
|
||||
starnet.WithDialTimeout(time.Second*3)))
|
||||
|
||||
if err != nil {
|
||||
return "获取IP信息失败:" + err.Error()
|
||||
return "IP信息获取失败"
|
||||
}
|
||||
|
||||
var ipinfo IPInfo
|
||||
err = json.Unmarshal(res.RecvData, &ipinfo)
|
||||
if err != nil {
|
||||
return "解析IP信息失败:" + err.Error()
|
||||
if err := res.Body().Unmarshal(&ipinfo); err != nil {
|
||||
return "IP信息解析失败"
|
||||
}
|
||||
return fmt.Sprintf("%s %s %s %s %s", ipinfo.CountryName, ipinfo.RegionName, ipinfo.CityName, ipinfo.OwnerDomain, ipinfo.ISP)
|
||||
|
||||
return fmt.Sprintf("%s %s %s",
|
||||
ipinfo.CountryName,
|
||||
ipinfo.RegionName,
|
||||
ipinfo.ISP)
|
||||
}
|
||||
|
||||
type IPInfo struct {
|
||||
|
@ -29,6 +29,7 @@ func init() {
|
||||
CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count")
|
||||
CmdNetforward.Flags().IntVarP(&f.UserTimeout, "user-timeout", "U", 0, "user timeout (milliseconds)")
|
||||
CmdNetforward.Flags().BoolVarP(&f.IgnoreEof, "ignore-eof", "E", false, "ignore eof")
|
||||
CmdNetforward.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "verbose mode")
|
||||
}
|
||||
|
||||
var CmdNetforward = &cobra.Command{
|
||||
|
@ -3,6 +3,7 @@ package netforward
|
||||
import (
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starmap"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -31,12 +32,19 @@ type NetForward struct {
|
||||
stopCtx context.Context
|
||||
stopFn context.CancelFunc
|
||||
running int32
|
||||
UdpHooks map[string]*starmap.StarStack
|
||||
|
||||
KeepAlivePeriod int
|
||||
KeepAliveIdel int
|
||||
KeepAliveCount int
|
||||
UserTimeout int
|
||||
UsingKeepAlive bool
|
||||
Verbose bool
|
||||
udpListener *net.UDPConn
|
||||
}
|
||||
|
||||
func (n *NetForward) UdpListener() *net.UDPConn {
|
||||
return n.udpListener
|
||||
}
|
||||
|
||||
func (n *NetForward) Close() {
|
||||
@ -194,17 +202,17 @@ type UDPConn struct {
|
||||
lastbeat int64
|
||||
}
|
||||
|
||||
func (u UDPConn) Write(p []byte) (n int, err error) {
|
||||
func (u *UDPConn) Write(p []byte) (n int, err error) {
|
||||
u.lastbeat = time.Now().Unix()
|
||||
return u.Conn.Write(p)
|
||||
}
|
||||
|
||||
func (u UDPConn) Read(p []byte) (n int, err error) {
|
||||
func (u *UDPConn) Read(p []byte) (n int, err error) {
|
||||
u.lastbeat = time.Now().Unix()
|
||||
return u.Conn.Read(p)
|
||||
}
|
||||
|
||||
func (u UDPConn) Work(delay int) {
|
||||
func (u *UDPConn) Work(delay int, verbose bool) {
|
||||
buf := make([]byte, 8192)
|
||||
for {
|
||||
if delay > 0 {
|
||||
@ -216,7 +224,10 @@ func (u UDPConn) Work(delay int) {
|
||||
u.lastbeat = 0
|
||||
return
|
||||
}
|
||||
_, err = u.listen.Write(buf[0:count])
|
||||
if verbose {
|
||||
fmt.Printf("U %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), u.Conn.RemoteAddr().String(), u.remoteAddr.String(), buf[0:count])
|
||||
}
|
||||
_, err = u.listen.WriteTo(buf[0:count], u.remoteAddr)
|
||||
if err != nil {
|
||||
u.lastbeat = 0
|
||||
return
|
||||
@ -236,12 +247,13 @@ func (n *NetForward) runUDP() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n.udpListener = listen
|
||||
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
|
||||
go func() {
|
||||
<-n.stopCtx.Done()
|
||||
listen.Close()
|
||||
}()
|
||||
udpMap := make(map[string]UDPConn)
|
||||
udpMap := make(map[string]*UDPConn)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
@ -270,6 +282,22 @@ func (n *NetForward) runUDP() error {
|
||||
if err != nil || rmt.String() == n.RemoteURI {
|
||||
continue
|
||||
}
|
||||
{
|
||||
//hooks
|
||||
if n.UdpHooks != nil {
|
||||
if m, ok := n.UdpHooks[rmt.String()]; ok {
|
||||
if m.Free() > 0 {
|
||||
if n.Verbose {
|
||||
starlog.Noticef("Hooked UDP Data %s ==> %s %X\n", rmt.String(), n.RemoteURI, buf[0:count])
|
||||
} else {
|
||||
starlog.Noticef("Hooked UDP Data %s ==> %s\n", rmt.String(), n.RemoteURI)
|
||||
}
|
||||
m.Push(buf[0:count])
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
go func(data []byte, rmt *net.UDPAddr) {
|
||||
log := starlog.Std.NewFlag()
|
||||
mu.Lock()
|
||||
@ -282,20 +310,23 @@ func (n *NetForward) runUDP() error {
|
||||
mu.Unlock()
|
||||
return
|
||||
}
|
||||
addr = UDPConn{
|
||||
addr = &UDPConn{
|
||||
Conn: conn,
|
||||
remoteAddr: rmt,
|
||||
listen: listen,
|
||||
lastbeat: time.Now().Unix(),
|
||||
}
|
||||
udpMap[rmt.String()] = addr
|
||||
go addr.Work(n.DelayMilSec)
|
||||
go addr.Work(n.DelayMilSec, n.Verbose)
|
||||
log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
|
||||
}
|
||||
mu.Unlock()
|
||||
if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) {
|
||||
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
|
||||
}
|
||||
if n.Verbose {
|
||||
fmt.Printf("T %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), rmt.String(), n.RemoteURI, data)
|
||||
}
|
||||
_, err := addr.Write(data)
|
||||
if err != nil {
|
||||
mu.Lock()
|
||||
@ -308,6 +339,12 @@ func (n *NetForward) runUDP() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NetForward) showVerbose(toward, src, dst string, data []byte) {
|
||||
if n.Verbose {
|
||||
fmt.Printf("%s %v Recv Data %s ==> %s %X\n", toward, time.Now().Format("2006-01-02 15:04:05"), src, dst, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NetForward) copy(dst, src net.Conn) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
@ -324,6 +361,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
|
||||
src.Close()
|
||||
return
|
||||
}
|
||||
n.showVerbose("T", src.RemoteAddr().String(), dst.RemoteAddr().String(), bufsize[:count])
|
||||
_, err = dst.Write(bufsize[:count])
|
||||
if err != nil {
|
||||
src.Close()
|
||||
@ -348,6 +386,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
|
||||
dst.Close()
|
||||
return
|
||||
}
|
||||
n.showVerbose("U", dst.RemoteAddr().String(), src.RemoteAddr().String(), bufsize[:count])
|
||||
_, err = src.Write(bufsize[:count])
|
||||
if err != nil {
|
||||
src.Close()
|
||||
|
@ -11,6 +11,9 @@ import (
|
||||
)
|
||||
|
||||
func SetTcpInfo(conn *net.TCPConn, usingKeepAlive bool, keepAliveIdel, keepAlivePeriod, keepAliveCount, userTimeout int) error {
|
||||
//windows上,这两个值是毫秒,linux上则是秒
|
||||
keepAlivePeriod *= 1000
|
||||
keepAliveIdel *= 1000
|
||||
if usingKeepAlive {
|
||||
rawConn, err := conn.SyscallConn()
|
||||
if err != nil {
|
||||
|
409
nmon/mon.go
Normal file
409
nmon/mon.go
Normal file
@ -0,0 +1,409 @@
|
||||
package nmon
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
"github.com/shirou/gopsutil/v4/cpu"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/load"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
refreshInterval time.Duration
|
||||
savePath string
|
||||
app *tview.Application
|
||||
showCores bool
|
||||
noTUI bool
|
||||
diskIOHistory = make(map[string]disk.IOCountersStat)
|
||||
netStats = make(map[string]*NetStat)
|
||||
fileMutex sync.Mutex
|
||||
)
|
||||
|
||||
type NetStat struct {
|
||||
LastBytesSent uint64
|
||||
LastBytesRecv uint64
|
||||
LastTime time.Time
|
||||
CurrentSentRate float64
|
||||
CurrentRecvRate float64
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "nmon",
|
||||
Short: "System Monitoring Tool",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if noTUI {
|
||||
runTextMode()
|
||||
return
|
||||
}
|
||||
startTUI()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().BoolVarP(&noTUI, "no-tui", "t", false, "Run in text mode")
|
||||
Cmd.Flags().BoolVarP(&showCores, "cores", "c", false, "Show per-core CPU usage")
|
||||
Cmd.Flags().DurationVarP(&refreshInterval, "interval", "i", time.Second, "Refresh interval")
|
||||
Cmd.Flags().StringVarP(&savePath, "save", "s", "", "Save monitoring data to file (nmon format)")
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := Cmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func createClickableTextView(title string) *tview.TextView {
|
||||
view := tview.NewTextView().SetDynamicColors(true)
|
||||
view.SetBorder(true).SetTitle(title)
|
||||
return view
|
||||
}
|
||||
|
||||
func startTUI() {
|
||||
app = tview.NewApplication()
|
||||
flex := tview.NewFlex().SetDirection(tview.FlexRow)
|
||||
|
||||
cpuView := tview.NewTextView().SetDynamicColors(true)
|
||||
memView := tview.NewTextView().SetDynamicColors(true)
|
||||
netView := tview.NewTextView().SetDynamicColors(true)
|
||||
diskView := tview.NewTextView().SetDynamicColors(true)
|
||||
|
||||
// 动态布局更新函数
|
||||
ioStats, _ := disk.IOCounters()
|
||||
updateLayout := func() {
|
||||
flex.Clear()
|
||||
cpuLines := 2 // 固定基础2行(CPU Load + Load Avg)
|
||||
if showCores {
|
||||
perCPU, _ := cpu.Percent(0, true)
|
||||
cpuLines = 2 + len(perCPU)
|
||||
}
|
||||
flex.AddItem(cpuView, cpuLines, 1, false)
|
||||
flex.AddItem(memView, 3, 1, false)
|
||||
flex.AddItem(diskView, len(ioStats), 1, false)
|
||||
flex.AddItem(netView, 3, 1, false)
|
||||
}
|
||||
|
||||
// 初始化数据
|
||||
updateLayout()
|
||||
|
||||
app.SetMouseCapture(func(event *tcell.EventMouse, action tview.MouseAction) (*tcell.EventMouse, tview.MouseAction) {
|
||||
// 示例:打印点击坐标
|
||||
if action == tview.MouseLeftClick {
|
||||
showCores = !showCores
|
||||
app.QueueUpdateDraw(updateLayout)
|
||||
}
|
||||
return event, action
|
||||
})
|
||||
|
||||
go updateLoop(cpuView, memView, netView, diskView)
|
||||
|
||||
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
|
||||
if event.Key() == tcell.KeyCtrlC {
|
||||
app.Stop()
|
||||
}
|
||||
return event
|
||||
})
|
||||
|
||||
if err := app.SetRoot(flex, true).Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLoop(cpuView, memView, netView, diskView *tview.TextView) {
|
||||
var lastSave time.Time
|
||||
for {
|
||||
app.QueueUpdateDraw(func() {
|
||||
updateCPU(cpuView)
|
||||
updateMemory(memView)
|
||||
updateNetwork(netView)
|
||||
updateDiskIO(diskView)
|
||||
})
|
||||
|
||||
if savePath != "" && time.Since(lastSave) > time.Minute {
|
||||
go saveNMONData()
|
||||
lastSave = time.Now()
|
||||
}
|
||||
|
||||
time.Sleep(refreshInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func updateCPU(view *tview.TextView) {
|
||||
percent, _ := cpu.Percent(time.Second, false)
|
||||
loadAvg, _ := load.Avg()
|
||||
|
||||
text := fmt.Sprintf("[red]CPU Load: [white]%.2f%%\n[red]Load Avg: [white]%.2f, %.2f, %.2f",
|
||||
percent[0],
|
||||
loadAvg.Load1,
|
||||
loadAvg.Load5,
|
||||
loadAvg.Load15)
|
||||
|
||||
if showCores {
|
||||
perCPU, _ := cpu.Percent(time.Second, true)
|
||||
for i, p := range perCPU {
|
||||
text += fmt.Sprintf("\nCore %d: [green]%s[white] %.2f%%",
|
||||
i+1,
|
||||
progressBar(p, 20),
|
||||
p)
|
||||
}
|
||||
}
|
||||
view.SetText(text)
|
||||
}
|
||||
|
||||
func updateMemory(view *tview.TextView) {
|
||||
memStat, _ := mem.VirtualMemory()
|
||||
text := fmt.Sprintf("[red]Memory: [white]%.2f%% Used (%.2f GB / %.2f GB)",
|
||||
memStat.UsedPercent,
|
||||
float64(memStat.Used)/1024/1024/1024,
|
||||
float64(memStat.Total)/1024/1024/1024)
|
||||
view.SetText(text + "\n" + progressBar(memStat.UsedPercent, 50))
|
||||
}
|
||||
|
||||
func updateNetwork(view *tview.TextView) {
|
||||
interfaces, _ := net.IOCounters(true)
|
||||
now := time.Now()
|
||||
var totalSent, totalRecv float64
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Name == "lo" {
|
||||
continue
|
||||
}
|
||||
|
||||
stat, exists := netStats[iface.Name]
|
||||
if !exists {
|
||||
stat = &NetStat{
|
||||
LastBytesSent: iface.BytesSent,
|
||||
LastBytesRecv: iface.BytesRecv,
|
||||
LastTime: now,
|
||||
}
|
||||
netStats[iface.Name] = stat
|
||||
continue
|
||||
}
|
||||
|
||||
timeDiff := now.Sub(stat.LastTime).Seconds()
|
||||
sentDiff := float64(iface.BytesSent - stat.LastBytesSent)
|
||||
recvDiff := float64(iface.BytesRecv - stat.LastBytesRecv)
|
||||
|
||||
stat.CurrentSentRate = sentDiff / timeDiff
|
||||
stat.CurrentRecvRate = recvDiff / timeDiff
|
||||
stat.LastBytesSent = iface.BytesSent
|
||||
stat.LastBytesRecv = iface.BytesRecv
|
||||
stat.LastTime = now
|
||||
|
||||
totalSent += stat.CurrentSentRate
|
||||
totalRecv += stat.CurrentRecvRate
|
||||
}
|
||||
|
||||
view.SetText(fmt.Sprintf("[red]Network: [white]↑%s ↓%s",
|
||||
formatSpeed(totalSent),
|
||||
formatSpeed(totalRecv)))
|
||||
}
|
||||
|
||||
func updateDiskIO(view *tview.TextView) {
|
||||
ioStats, _ := disk.IOCounters()
|
||||
text := "[red]Disk I/O:\n"
|
||||
|
||||
for name, io := range ioStats {
|
||||
last, exists := diskIOHistory[name]
|
||||
if exists {
|
||||
interval := float64(refreshInterval.Seconds())
|
||||
readSpeed := float64(io.ReadBytes-last.ReadBytes) / interval
|
||||
writeSpeed := float64(io.WriteBytes-last.WriteBytes) / interval
|
||||
|
||||
text += fmt.Sprintf(" %-8s R:%s W:%s\n",
|
||||
name,
|
||||
formatSpeed(readSpeed),
|
||||
formatSpeed(writeSpeed))
|
||||
}
|
||||
diskIOHistory[name] = io
|
||||
}
|
||||
view.SetText(text)
|
||||
}
|
||||
|
||||
func progressBar(percent float64, width int) string {
|
||||
filled := int(percent / 100 * float64(width))
|
||||
bar := "[green]"
|
||||
for i := 0; i < width; i++ {
|
||||
if i < filled {
|
||||
bar += "■"
|
||||
} else {
|
||||
bar += "□"
|
||||
}
|
||||
}
|
||||
return bar + "[white]"
|
||||
}
|
||||
|
||||
func formatSpeed(speed float64) string {
|
||||
const (
|
||||
KB = 1024
|
||||
MB = KB * 1024
|
||||
)
|
||||
|
||||
switch {
|
||||
case speed >= MB:
|
||||
return fmt.Sprintf("%7.3f MB/s", speed/MB)
|
||||
case speed >= KB:
|
||||
return fmt.Sprintf("%7.3f KB/s", speed/KB)
|
||||
default:
|
||||
return fmt.Sprintf("%7.3f B/s", speed)
|
||||
}
|
||||
}
|
||||
|
||||
func saveNMONData() {
|
||||
fileMutex.Lock()
|
||||
defer fileMutex.Unlock()
|
||||
|
||||
f, err := os.OpenFile(savePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
cpuPercent, _ := cpu.Percent(0, false)
|
||||
memStat, _ := mem.VirtualMemory()
|
||||
ioStats, _ := disk.IOCounters()
|
||||
|
||||
fmt.Fprintf(f, "%s,CPU_ALL,%.2f\n", now, cpuPercent[0])
|
||||
fmt.Fprintf(f, "%s,MEM,%.2f,%.2f\n", now, memStat.UsedPercent, memStat.Available)
|
||||
|
||||
for name, io := range ioStats {
|
||||
fmt.Fprintf(f, "%s,DISKIO,%s,%d,%d\n",
|
||||
now, name, io.ReadBytes, io.WriteBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 文本模式相关函数
|
||||
func runTextMode() {
|
||||
|
||||
ticker := time.NewTicker(refreshInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
fmt.Println(getCPUInfo())
|
||||
fmt.Println(getMemoryInfo())
|
||||
fmt.Println(getNetworkInfo())
|
||||
fmt.Println(getDiskIOInfo())
|
||||
|
||||
if savePath != "" {
|
||||
go saveNMONData()
|
||||
}
|
||||
<-ticker.C
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 数据获取函数
|
||||
func getCPUInfo() string {
|
||||
|
||||
percent, _ := cpu.Percent(time.Second, false)
|
||||
loadAvg, _ := load.Avg()
|
||||
fmt.Print("\033[H\033[2J")
|
||||
fmt.Println("=== System Monitor ===")
|
||||
text := fmt.Sprintf("[CPU]\nLoad: %.2f%% Avg: %.2f, %.2f, %.2f",
|
||||
percent[0],
|
||||
loadAvg.Load1,
|
||||
loadAvg.Load5,
|
||||
loadAvg.Load15)
|
||||
|
||||
if showCores {
|
||||
perCPU, _ := cpu.Percent(time.Second, true)
|
||||
for i, p := range perCPU {
|
||||
text += fmt.Sprintf("\nCore %d: %s %.2f%%",
|
||||
i+1,
|
||||
textProgressBar(p, 20),
|
||||
p)
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func getMemoryInfo() string {
|
||||
memStat, _ := mem.VirtualMemory()
|
||||
return fmt.Sprintf("[Memory]\nUsed: %.2f%% (%.2fG/%.2fG)\n%s",
|
||||
memStat.UsedPercent,
|
||||
float64(memStat.Used)/1024/1024/1024,
|
||||
float64(memStat.Total)/1024/1024/1024,
|
||||
textProgressBar(memStat.UsedPercent, 50))
|
||||
}
|
||||
|
||||
func getNetworkInfo() string {
|
||||
interfaces, _ := net.IOCounters(true)
|
||||
now := time.Now()
|
||||
var sent, recv float64
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Name == "lo" {
|
||||
continue
|
||||
}
|
||||
|
||||
stat, exists := netStats[iface.Name]
|
||||
if !exists {
|
||||
stat = &NetStat{
|
||||
LastBytesSent: iface.BytesSent,
|
||||
LastBytesRecv: iface.BytesRecv,
|
||||
LastTime: now,
|
||||
}
|
||||
netStats[iface.Name] = stat
|
||||
continue
|
||||
}
|
||||
|
||||
timeDiff := now.Sub(stat.LastTime).Seconds()
|
||||
sentDiff := float64(iface.BytesSent - stat.LastBytesSent)
|
||||
recvDiff := float64(iface.BytesRecv - stat.LastBytesRecv)
|
||||
|
||||
stat.CurrentSentRate = sentDiff / timeDiff
|
||||
stat.CurrentRecvRate = recvDiff / timeDiff
|
||||
stat.LastBytesSent = iface.BytesSent
|
||||
stat.LastBytesRecv = iface.BytesRecv
|
||||
stat.LastTime = now
|
||||
|
||||
sent += stat.CurrentSentRate
|
||||
recv += stat.CurrentRecvRate
|
||||
}
|
||||
|
||||
return fmt.Sprintf("[Network]\nUpload: %s\nDownload: %s",
|
||||
formatSpeed(sent),
|
||||
formatSpeed(recv))
|
||||
}
|
||||
|
||||
func getDiskIOInfo() string {
|
||||
ioStats, _ := disk.IOCounters()
|
||||
text := "[Disk I/O]"
|
||||
|
||||
for name, io := range ioStats {
|
||||
last, exists := diskIOHistory[name]
|
||||
if exists {
|
||||
interval := float64(refreshInterval.Seconds())
|
||||
read := float64(io.ReadBytes-last.ReadBytes) / interval
|
||||
write := float64(io.WriteBytes-last.WriteBytes) / interval
|
||||
|
||||
text += fmt.Sprintf("\n%-8s Read: %s Write: %s",
|
||||
name,
|
||||
formatSpeed(read),
|
||||
formatSpeed(write))
|
||||
}
|
||||
diskIOHistory[name] = io
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func textProgressBar(percent float64, width int) string {
|
||||
filled := int(percent / 100 * float64(width))
|
||||
bar := ""
|
||||
for i := 0; i < width; i++ {
|
||||
if i < filled {
|
||||
bar += "■"
|
||||
} else {
|
||||
bar += "□"
|
||||
}
|
||||
}
|
||||
return bar
|
||||
}
|
41
tcm/cmd_unix.go
Normal file
41
tcm/cmd_unix.go
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build !windows
|
||||
|
||||
package tcm
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var nf = NewNfCap()
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tcm",
|
||||
Short: "TCP连接监视工具",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
nf.Run()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().IntVarP(&nf.monitorPort, "port", "p", 0, "nft转发端口地址,如果启用此参数,将会自动设置iptables,需要安装iptables")
|
||||
Cmd.Flags().StringSliceVarP(&nf.target, "target", "t", []string{}, "监控的ip地址,可多个,本工具各类延迟等tcp操作仅对此类ip生效")
|
||||
Cmd.Flags().StringSliceVarP(&nf.targetCmd, "cmd", "c", []string{}, "触发报文drop的关键词,utf8格式,如:show variables")
|
||||
Cmd.Flags().BoolVarP(&nf.targetAsHex, "cmd-as-hex", "x", false, "启用此选项,cmd选项请传入hex字符,而不是utf-8")
|
||||
Cmd.Flags().StringVarP(&nf.saveFile, "save", "w", "", "保存文件路径,将会保存所有报文到此文件")
|
||||
Cmd.Flags().BoolVarP(&nf.interactive, "interactive", "i", false, "启用交互模式,可输入命令:allow <ip>,drop <ip>,delay <ms>,loss <number%>")
|
||||
Cmd.Flags().BoolVarP(&nf.showAll, "display-all", "D", false, "显示所有报文,包括非target对象")
|
||||
Cmd.Flags().BoolVarP(&nf.showAsHex, "as-hex", "a", false, "显示报文的hex内容")
|
||||
Cmd.Flags().BoolVarP(&nf.showPayload, "show-payload", "S", false, "显示报文的payload")
|
||||
Cmd.Flags().IntVarP(&nf.maxShowPayloadSize, "payload-maxlen", "m", 200, "显示payload的最大长度")
|
||||
Cmd.Flags().BoolVarP(&nf.noShowMode, "no-show", "N", false, "不显示任何tcp报文,只统计数量")
|
||||
Cmd.Flags().Float64VarP(&nf.loss, "loss", "l", 0, "丢包率,0-100之间,如10表示10%丢包")
|
||||
Cmd.Flags().IntVarP(&nf.delay, "delay", "d", 0, "延迟时间,单位ms")
|
||||
Cmd.Flags().IntVarP(&nf.packetDelay, "packet-delay-num", "n", 0, "触发封禁关键词后,延迟n个包再封禁")
|
||||
Cmd.Flags().BoolVarP(&nf.useRST, "rst", "r", false, "触发封禁关键词后,同步发送RST报文")
|
||||
Cmd.Flags().StringVarP(&nf.rstMode, "rstmode", "R", "reverse", "RST报文发送模式,可选值:both,target,reverse")
|
||||
Cmd.Flags().BoolVarP(&nf.fastMode, "fastmode", "F", false, "快速模式,仅在模拟延迟或丢包时使用")
|
||||
Cmd.Flags().IntVarP(&nf.NFQNums, "nfqueue-num", "q", 2, "nfqueue队列号")
|
||||
Cmd.Flags().BoolVarP(&nf.allowRandomAck, "random-ack", "A", false, "允许并行乱序处理报文,如果需要模拟延迟,此选项需开启,但封禁功能可能受到乱序影响")
|
||||
Cmd.Flags().BoolVarP(&nf.singlePacketMode, "signle-packet", "o", false, "仅对匹配到的单报文操作,此模式下,packetDelay会失效")
|
||||
Cmd.Flags().StringSliceVarP(&nf.cuePktMethod, "cue-pkt-method", "O", []string{}, "单报文匹配下,执行的报文操作,可选值:drop,delay ms,allow,reset")
|
||||
Cmd.Flags().StringSliceVarP(&nf.Flags, "flags", "f", nil, "tcp flags匹配,如:SYN,ACK")
|
||||
Cmd.Flags().IntVarP(&nf.CapFileCacheNum, "write-cache", "W", 0, "命中匹配写入文件报文缓存,如果为0 ,则忽略匹配条件")
|
||||
}
|
16
tcm/cmd_winarm64.go
Normal file
16
tcm/cmd_winarm64.go
Normal file
@ -0,0 +1,16 @@
|
||||
//go:build windows && arm64
|
||||
|
||||
package tcm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tcm",
|
||||
Short: "TCP连接监视工具",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("windows on arm is not supported yet")
|
||||
},
|
||||
}
|
35
tcm/cmd_windows.go
Normal file
35
tcm/cmd_windows.go
Normal file
@ -0,0 +1,35 @@
|
||||
//go:build windows && !arm64
|
||||
|
||||
package tcm
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var nf = NewLibpcap()
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tcm",
|
||||
Short: "TCP连接监视工具",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
nf.Run()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().StringSliceVarP(&nf.target, "target", "t", []string{}, "监控的ip地址,可多个,本工具各类延迟等tcp操作仅对此类ip生效")
|
||||
Cmd.Flags().StringSliceVarP(&nf.targetCmd, "cmd", "c", []string{}, "触发报文drop的关键词,utf8格式,如:show variables")
|
||||
Cmd.Flags().BoolVarP(&nf.targetAsHex, "cmd-as-hex", "x", false, "启用此选项,cmd选项请传入hex字符,而不是utf-8")
|
||||
Cmd.Flags().StringVarP(&nf.saveFile, "save", "w", "", "保存文件路径,将会保存所有报文到此文件")
|
||||
//Cmd.Flags().BoolVarP(&nf.interactive, "interactive", "i", false, "启用交互模式,可输入命令:allow <ip>,drop <ip>,delay <ms>,loss <number%>")
|
||||
Cmd.Flags().BoolVarP(&nf.showAll, "display-all", "D", false, "显示所有报文,包括非target对象")
|
||||
Cmd.Flags().BoolVarP(&nf.showAsHex, "as-hex", "a", false, "显示报文的hex内容")
|
||||
Cmd.Flags().BoolVarP(&nf.showPayload, "show-payload", "S", false, "显示报文的payload")
|
||||
Cmd.Flags().IntVarP(&nf.maxShowPayloadSize, "payload-maxlen", "m", 200, "显示payload的最大长度")
|
||||
Cmd.Flags().BoolVarP(&nf.noShowMode, "no-show", "N", false, "不显示任何tcp报文,只统计数量")
|
||||
Cmd.Flags().BoolVarP(&nf.useRST, "rst", "r", false, "触发封禁关键词后,同步发送RST报文")
|
||||
Cmd.Flags().StringVarP(&nf.rstMode, "rstmode", "R", "reverse", "RST报文发送模式,可选值:both,target,reverse")
|
||||
Cmd.Flags().StringVarP(&nf.eth, "eth", "e", "", "监听网卡名,如eth0")
|
||||
Cmd.Flags().StringVarP(&nf.bpf, "bpf", "b", "tcp", "BPF过滤,如tcp port 80")
|
||||
Cmd.Flags().StringVarP(&nf.host, "host", "i", "", "监听主机名,如127.0.0.1")
|
||||
Cmd.Flags().StringSliceVarP(&nf.Flags, "flags", "f", nil, "tcp flags匹配,如:SYN,ACK")
|
||||
Cmd.Flags().IntVarP(&nf.CapFileCacheNum, "write-cache", "W", 0, "命中匹配写入文件报文缓存,如果为0 ,则忽略匹配条件")
|
||||
|
||||
}
|
852
tcm/tcpmonitor_unix.go
Normal file
852
tcm/tcpmonitor_unix.go
Normal file
@ -0,0 +1,852 @@
|
||||
//go:build !windows
|
||||
|
||||
package tcm
|
||||
|
||||
import (
|
||||
"b612.me/bcap"
|
||||
"b612.me/bcap/nfq"
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starmap"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/florianl/go-nfqueue/v2"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcapgo"
|
||||
"math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type NfCap struct {
|
||||
count uint64
|
||||
// 按连接数最后一个历史报文
|
||||
cap *bcap.Packets
|
||||
// 监控目标ip地址列表
|
||||
target []string
|
||||
// 将军,下达命令吧!
|
||||
// 监控命令列表
|
||||
targetCmd []string
|
||||
targetAsHex bool
|
||||
//保存为pcap格式的数据文件
|
||||
saveFile string
|
||||
// 写缓存
|
||||
packetCache chan gopacket.Packet
|
||||
//保存封禁的map
|
||||
blockMap sync.Map
|
||||
//交互模式
|
||||
interactive bool
|
||||
//展示所有报文信息,包括未在追踪列表的
|
||||
showAll bool
|
||||
//展示payload报文(utf-8直接输出)
|
||||
showPayload bool
|
||||
//payload展示为hex
|
||||
showAsHex bool
|
||||
//payload允许展示的最大字符
|
||||
maxShowPayloadSize int
|
||||
//不展示任何信息
|
||||
noShowMode bool
|
||||
// 全链路丢包率,百分比
|
||||
loss float64
|
||||
// 报文延迟抵达时间,毫秒
|
||||
delay int
|
||||
// 触发封禁词后,再过N个包再封禁
|
||||
packetDelay int
|
||||
// 触发封禁词后,使用RST重置链路
|
||||
useRST bool
|
||||
// RST模式,target=目标端单向RST,reverse=对端反向RST,both=双向RST
|
||||
rstMode string
|
||||
fastMode bool //自探测
|
||||
printColor []*starlog.Color
|
||||
logCache chan loged
|
||||
monitorPort int
|
||||
cache []string
|
||||
NFQNums int
|
||||
ctx context.Context
|
||||
fn context.CancelFunc
|
||||
requests chan *handler
|
||||
allowRandomAck bool
|
||||
singlePacketMode bool
|
||||
cuePktMethod []string
|
||||
CapFileCacheNum int
|
||||
Flags []string
|
||||
}
|
||||
|
||||
type loged struct {
|
||||
str string
|
||||
stateLevel int
|
||||
logLevel string
|
||||
}
|
||||
|
||||
func (n *NfCap) inactve() {
|
||||
for {
|
||||
f := strings.Fields(stario.MessageBox("", "").MustString())
|
||||
if len(f) < 2 {
|
||||
continue
|
||||
}
|
||||
switch f[0] {
|
||||
case "allow":
|
||||
for _, v := range f[1:] {
|
||||
n.blockMap.Delete(v)
|
||||
starlog.Infof("允许%s报文\n", v)
|
||||
}
|
||||
case "drop":
|
||||
for _, v := range f[1:] {
|
||||
n.blockMap.Store(v, true)
|
||||
starlog.Infof("封禁%s报文\n", v)
|
||||
}
|
||||
case "delay":
|
||||
tmp, err := strconv.Atoi(f[1])
|
||||
if err != nil {
|
||||
starlog.Errorln("输入延迟无效:%v\n", err)
|
||||
continue
|
||||
}
|
||||
n.delay = tmp
|
||||
starlog.Infof("延迟生效:%v ms\n", n.delay)
|
||||
case "loss":
|
||||
tmp, err := strconv.ParseFloat(f[1], 64)
|
||||
if err != nil {
|
||||
starlog.Errorln("输入丢包百分比无效:%v\n", err)
|
||||
continue
|
||||
}
|
||||
n.loss = tmp
|
||||
starlog.Infof("丢包百分比生效:%v ms\n", n.loss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NfCap) doIptables() error {
|
||||
if t.monitorPort != 0 {
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "PREROUTING", "-p", "tcp", "--dport", strconv.Itoa(t.monitorPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D PREROUTING -p tcp --dport "+strconv.Itoa(t.monitorPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "OUTPUT", "-p", "tcp", "--sport", strconv.Itoa(t.monitorPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D OUTPUT -p tcp --sport "+strconv.Itoa(t.monitorPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "PREROUTING", "-p", "tcp", "--sport", strconv.Itoa(t.monitorPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D PREROUTING -p tcp --sport "+strconv.Itoa(t.monitorPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "OUTPUT", "-p", "tcp", "--dport", strconv.Itoa(t.monitorPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D OUTPUT -p tcp --dport "+strconv.Itoa(t.monitorPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *NfCap) undoIptables() {
|
||||
for _, cmd := range t.cache {
|
||||
exec.Command("iptables", strings.Fields(cmd)...).CombinedOutput()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NfCap) Run() error {
|
||||
stopSignal := make(chan os.Signal)
|
||||
starlog.Noticef("Starting nfqueue capture\n")
|
||||
nf := nfq.NewNfQueue(t.ctx, uint16(t.NFQNums), 65535)
|
||||
nf.SetRecall(t.handleRoute)
|
||||
if t.targetAsHex {
|
||||
for k, v := range t.targetCmd {
|
||||
tmp, _ := hex.DecodeString(v)
|
||||
t.targetCmd[k] = string(tmp)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
if err := nf.Run(); err != nil {
|
||||
starlog.Errorln(err)
|
||||
stopSignal <- syscall.SIGKILL
|
||||
}
|
||||
}()
|
||||
if t.saveFile != "" {
|
||||
f, err := os.Create(t.saveFile)
|
||||
if err != nil {
|
||||
starlog.Errorln("创建写入文件失败", err)
|
||||
os.Exit(4)
|
||||
}
|
||||
defer f.Close()
|
||||
go t.pcapWriter(t.ctx, f)
|
||||
}
|
||||
if t.monitorPort != 0 {
|
||||
if _, err := exec.Command("iptables", "-V").CombinedOutput(); err != nil {
|
||||
starlog.Warningln("iptables not found, cannot auto set iptables")
|
||||
return fmt.Errorf("iptables not found")
|
||||
}
|
||||
defer t.undoIptables()
|
||||
if err := t.doIptables(); err != nil {
|
||||
starlog.Errorf("Failed to set iptables:%v\n", err)
|
||||
return err
|
||||
}
|
||||
starlog.Infof("Set iptables success\n")
|
||||
}
|
||||
signal.Notify(stopSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
go t.handleNfResult()
|
||||
go t.logPrint(t.ctx)
|
||||
select {
|
||||
//todo need nf.Stop()
|
||||
case <-stopSignal:
|
||||
starlog.Warningf("Received stop signal\n")
|
||||
|
||||
case <-t.ctx.Done():
|
||||
starlog.Infoln("TCPMonitor Task Finished")
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func NewNfCap() *NfCap {
|
||||
var nf = new(NfCap)
|
||||
nf.packetCache = make(chan gopacket.Packet, 8192)
|
||||
nf.logCache = make(chan loged, 2048)
|
||||
nf.printColor = []*starlog.Color{
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgHiCyan), //1=tcp_connect_1,
|
||||
starlog.NewColor(starlog.FgHiCyan), //2=tcp_connect_2,
|
||||
starlog.NewColor(starlog.FgHiCyan), //3=tcp_connect_3,
|
||||
starlog.NewColor(starlog.FgCyan), //4=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //5=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //6=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //7=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //8=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgGreen), //9=tcp_ok
|
||||
starlog.NewColor(starlog.BgRed, starlog.FgYellow), //10=tcp_retrans
|
||||
starlog.NewColor(starlog.FgHiMagenta), //ece
|
||||
starlog.NewColor(starlog.FgHiMagenta), //cwr
|
||||
starlog.NewColor(starlog.FgRed), //rst
|
||||
starlog.NewColor(starlog.FgHiGreen), //keepalive
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //20=udp
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
}
|
||||
nf.cap = bcap.NewPackets()
|
||||
nf.ctx, nf.fn = context.WithCancel(context.Background())
|
||||
nf.requests = make(chan *handler, 8192)
|
||||
return nf
|
||||
}
|
||||
func (t *NfCap) handleNfResult() {
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case info := <-t.requests:
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
if t.allowRandomAck {
|
||||
go func() {
|
||||
info.p.SetVerdict(info.id, <-info.fin)
|
||||
}()
|
||||
break
|
||||
}
|
||||
info.p.SetVerdict(info.id, <-info.fin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NfCap) handleRoute(id uint32, q *nfqueue.Nfqueue, p nfq.Packet) {
|
||||
if t.requests == nil {
|
||||
q.SetVerdict(id, nfqueue.NfAccept)
|
||||
return
|
||||
}
|
||||
info, err := t.cap.ParsePacket(p.Packet)
|
||||
if err != nil {
|
||||
q.SetVerdict(id, nfqueue.NfAccept)
|
||||
return
|
||||
}
|
||||
fin := make(chan int)
|
||||
t.requests <- &handler{
|
||||
id: id,
|
||||
p: q,
|
||||
packet: p.Packet,
|
||||
attr: p.Attr,
|
||||
fin: fin,
|
||||
}
|
||||
go func() {
|
||||
fin <- t.handlePacket(info, p)
|
||||
}()
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
id uint32
|
||||
p *nfqueue.Nfqueue
|
||||
packet gopacket.Packet
|
||||
attr nfqueue.Attribute
|
||||
fin chan int
|
||||
}
|
||||
|
||||
func (n *NfCap) logPrint(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case l := <-n.logCache:
|
||||
if n.noShowMode {
|
||||
fmt.Printf("已捕获报文数量:%v个\r", n.count)
|
||||
continue
|
||||
}
|
||||
switch l.logLevel {
|
||||
case "info":
|
||||
starlog.Info(l.str)
|
||||
case "notice":
|
||||
starlog.Notice(l.str)
|
||||
case "warning":
|
||||
starlog.Warning(l.str)
|
||||
case "error":
|
||||
starlog.Error(l.str)
|
||||
case "critical":
|
||||
starlog.Critical(l.str)
|
||||
case "debug":
|
||||
starlog.Debug(l.str)
|
||||
case "payload":
|
||||
fmt.Println(l.str)
|
||||
default:
|
||||
n.printColor[l.stateLevel].Print(l.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NfCap) ipInRange(ip string) bool {
|
||||
if len(n.target) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range n.target {
|
||||
if v == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *NfCap) strInRange(str string) bool {
|
||||
if len(n.targetCmd) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range n.targetCmd {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(str, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *NfCap) strInRangeIdx(str string) int {
|
||||
if len(n.targetCmd) == 0 {
|
||||
return -1
|
||||
}
|
||||
for k, v := range n.targetCmd {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(str, v) {
|
||||
return k
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (n *NfCap) handlePacket(info bcap.PacketInfo, nfp nfq.Packet) int {
|
||||
p := nfp.Packet
|
||||
n.count++
|
||||
if n.saveFile != "" {
|
||||
n.packetCache <- p
|
||||
}
|
||||
var layer gopacket.Layer
|
||||
for _, layerType := range []gopacket.LayerType{
|
||||
layers.LayerTypeTCP, layers.LayerTypeUDP, layers.LayerTypeICMPv4, layers.LayerTypeICMPv6,
|
||||
layers.LayerTypeIPv4, layers.LayerTypeIPv6, layers.LayerTypeARP,
|
||||
} {
|
||||
layer = p.Layer(layerType)
|
||||
if layer == nil {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if layer == nil {
|
||||
n.logCache <- loged{
|
||||
str: "无法定义layer类型\n",
|
||||
stateLevel: 0,
|
||||
logLevel: "error",
|
||||
}
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
var shouldDisallow bool
|
||||
if n.fastMode {
|
||||
if n.delay > 0 || n.loss > 0 {
|
||||
if n.delay > 0 && (n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP)) {
|
||||
time.Sleep(time.Millisecond * time.Duration(n.delay))
|
||||
}
|
||||
if n.loss > 0 && (n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP)) {
|
||||
if rand.Intn(10000) < int(n.loss*100) {
|
||||
shouldDisallow = true
|
||||
}
|
||||
}
|
||||
}
|
||||
if shouldDisallow {
|
||||
return nfqueue.NfDrop
|
||||
}
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
if n.loss > 0 && (n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP)) {
|
||||
if rand.Intn(10000) < int(n.loss*100) {
|
||||
shouldDisallow = true
|
||||
}
|
||||
}
|
||||
if n.delay > 0 && (n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP)) {
|
||||
time.Sleep(time.Millisecond * time.Duration(n.delay))
|
||||
}
|
||||
var dec string
|
||||
switch info.StateDescript() {
|
||||
case 0:
|
||||
dec = "未知状态"
|
||||
case 1:
|
||||
dec = "SYN tcp建立一次握手"
|
||||
case 2:
|
||||
dec = "SYN,ACK tcp建立二次握手"
|
||||
case 3:
|
||||
dec = "ACK tcp建立三次握手"
|
||||
case 4:
|
||||
dec = "FIN tcp断开一次挥手"
|
||||
case 5:
|
||||
dec = "ACK tcp断开二次挥手"
|
||||
case 6:
|
||||
dec = "FIN,ACK tcp断开二次三次挥手"
|
||||
case 7:
|
||||
dec = "FIN tcp断开三次挥手"
|
||||
case 8:
|
||||
dec = "ACK tcp断开四次挥手"
|
||||
case 9:
|
||||
dec = "tcp报文"
|
||||
case 10:
|
||||
dec = "TCP重传"
|
||||
case 11:
|
||||
dec = "TCP ece"
|
||||
case 12:
|
||||
dec = "TCP cwr"
|
||||
case 13:
|
||||
dec = "TCP RST重置"
|
||||
case 14:
|
||||
dec = "TCP Keepalive"
|
||||
}
|
||||
if len(n.Flags) > 0 && !n.writerMatch(p, true) {
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
if n.showAll || n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP) {
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("%s %v:%v -> %v:%v %s seq=%v ack=%v win=%v len=%v\n", time.Now().Format("2006-01-02 15:04:05.000000"), info.SrcIP, info.SrcPort,
|
||||
info.DstIP, info.DstPort, dec, info.TcpSeq(), info.TcpAck(), info.TcpWindow(), info.TcpPayloads()),
|
||||
stateLevel: int(info.StateDescript()),
|
||||
logLevel: "",
|
||||
}
|
||||
if n.showPayload {
|
||||
str := string(layer.LayerPayload())
|
||||
if n.maxShowPayloadSize > 0 {
|
||||
if len(str) > n.maxShowPayloadSize {
|
||||
str = str[:n.maxShowPayloadSize]
|
||||
}
|
||||
}
|
||||
if n.showAsHex {
|
||||
str = hex.EncodeToString([]byte(str))
|
||||
}
|
||||
n.logCache <- loged{
|
||||
str: str,
|
||||
stateLevel: int(info.StateDescript()),
|
||||
logLevel: "payload",
|
||||
}
|
||||
}
|
||||
}
|
||||
if res := n.cuePacket(info, layer); res != -1 {
|
||||
return res
|
||||
}
|
||||
if shouldDisallow {
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("Block(loss) TCP %v:%v -> %v:%v,LEN=%d\n", info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpPayloads()),
|
||||
stateLevel: 0,
|
||||
logLevel: "warning",
|
||||
}
|
||||
return nfqueue.NfDrop
|
||||
}
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
|
||||
func (n *NfCap) cuePacket(info bcap.PacketInfo, layer gopacket.Layer) int {
|
||||
if info.Comment() != "" || n.cap.Key(info.ReverseKey).Comment() != "" {
|
||||
tmp := info.Comment()
|
||||
if tmp == "" {
|
||||
tmp = n.cap.Key(info.ReverseKey).Comment()
|
||||
}
|
||||
pkg, _ := strconv.Atoi(tmp)
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("current delay count:%v\n", pkg-1),
|
||||
stateLevel: 0,
|
||||
logLevel: "warning",
|
||||
}
|
||||
if pkg-1 <= 0 {
|
||||
if n.useRST {
|
||||
RealSendRST(info, n.rstMode, 3)
|
||||
}
|
||||
if n.ipInRange(info.SrcIP) {
|
||||
n.blockMap.Store(info.SrcIP, true)
|
||||
} else {
|
||||
n.blockMap.Store(info.DstIP, true)
|
||||
}
|
||||
n.cap.SetComment(info.Key, "")
|
||||
n.cap.SetComment(info.ReverseKey, "")
|
||||
} else {
|
||||
n.cap.SetComment(info.Key, strconv.Itoa(pkg-1))
|
||||
n.cap.SetComment(info.ReverseKey, strconv.Itoa(pkg-1))
|
||||
}
|
||||
}
|
||||
if len(n.targetCmd) > 0 && (n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP)) {
|
||||
if idx := n.strInRangeIdx(string(layer.LayerPayload())); idx >= 0 {
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("%s:%s -> %s:%s !!Match Keyword,will block!!\n", info.SrcIP, info.SrcPort,
|
||||
info.DstIP, info.DstPort),
|
||||
stateLevel: 0,
|
||||
logLevel: "warning",
|
||||
}
|
||||
if n.singlePacketMode {
|
||||
return n.singlePacket(info, idx)
|
||||
}
|
||||
if n.packetDelay > 0 && info.Comment() == "" {
|
||||
n.cap.SetComment(info.Key, strconv.Itoa(n.packetDelay))
|
||||
n.cap.SetComment(info.ReverseKey, strconv.Itoa(n.packetDelay))
|
||||
} else {
|
||||
if n.useRST {
|
||||
RealSendRST(info, n.rstMode, 3)
|
||||
}
|
||||
if n.ipInRange(info.SrcIP) {
|
||||
n.blockMap.Store(info.SrcIP, true)
|
||||
} else {
|
||||
n.blockMap.Store(info.DstIP, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_, ok1 := n.blockMap.Load(info.DstIP)
|
||||
_, ok2 := n.blockMap.Load(info.SrcIP)
|
||||
if ok1 || ok2 {
|
||||
if n.useRST && info.StateDescript() == 13 {
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("Block TCP %v:%v -> %v:%v,LEN=%d\n", info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpPayloads()),
|
||||
stateLevel: 0,
|
||||
logLevel: "warning",
|
||||
}
|
||||
return nfqueue.NfDrop
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (n *NfCap) singlePacket(info bcap.PacketInfo, idx int) int {
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("Handle TCP %v:%v -> %v:%v,LEN=%d\n", info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpPayloads()),
|
||||
stateLevel: 0,
|
||||
logLevel: "warning",
|
||||
}
|
||||
if len(n.cuePktMethod) == 0 {
|
||||
return nfqueue.NfDrop
|
||||
}
|
||||
task := n.cuePktMethod[idx]
|
||||
for _, v := range strings.Split(task, ";") {
|
||||
v = strings.TrimSpace(v)
|
||||
tasks := strings.Fields(v)
|
||||
switch strings.ToLower(tasks[0]) {
|
||||
case "delay":
|
||||
if len(tasks) < 2 {
|
||||
continue
|
||||
}
|
||||
tmp, err := strconv.Atoi(tasks[1])
|
||||
if err != nil {
|
||||
fmt.Printf("输入延迟无效:%v\n", err)
|
||||
continue
|
||||
}
|
||||
time.Sleep(time.Millisecond * time.Duration(tmp))
|
||||
case "drop":
|
||||
return nfqueue.NfDrop
|
||||
case "allow":
|
||||
return nfqueue.NfAccept
|
||||
case "reset":
|
||||
RealSendRST(info, n.rstMode, 3)
|
||||
default:
|
||||
starlog.Warningf("未知命令:%s\n", v)
|
||||
}
|
||||
}
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
|
||||
func (n *NfCap) pcapWriter(stopCtx context.Context, fp *os.File) error {
|
||||
w := pcapgo.NewWriter(fp)
|
||||
err := w.WriteFileHeader(65535, layers.LinkTypeEthernet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := starmap.NewStarChanStack(uint64(n.CapFileCacheNum))
|
||||
avail := 0
|
||||
for {
|
||||
select {
|
||||
case <-stopCtx.Done():
|
||||
return nil
|
||||
case p := <-n.packetCache:
|
||||
if n.CapFileCacheNum == 0 {
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
continue
|
||||
}
|
||||
if avail > 0 {
|
||||
avail--
|
||||
if n.writerMatch(p, false) {
|
||||
avail = n.CapFileCacheNum
|
||||
}
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
continue
|
||||
}
|
||||
if buf.Free() == 0 {
|
||||
buf.Pop()
|
||||
}
|
||||
if !n.writerMatch(p, false) {
|
||||
buf.Push(p)
|
||||
continue
|
||||
}
|
||||
for buf.Len() > 0 {
|
||||
hp, err := buf.Pop()
|
||||
if err == nil {
|
||||
w.WritePacket(hp.(gopacket.Packet).Metadata().CaptureInfo, hp.(gopacket.Packet).Data())
|
||||
}
|
||||
}
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
avail = n.CapFileCacheNum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NfCap) writerMatch(pkt gopacket.Packet, onlyFlags bool) bool {
|
||||
tcpLayer := pkt.Layer(layers.LayerTypeTCP)
|
||||
if !onlyFlags {
|
||||
var src, dst string
|
||||
if nw := pkt.NetworkLayer(); nw != nil {
|
||||
srcp, dstp := nw.NetworkFlow().Endpoints()
|
||||
src = srcp.String()
|
||||
dst = dstp.String()
|
||||
}
|
||||
if !(n.ipInRange(src) || n.ipInRange(dst)) {
|
||||
return false
|
||||
}
|
||||
if tcpLayer == nil {
|
||||
if len(n.targetCmd) != 0 && !n.strInRange(string(pkt.TransportLayer().LayerPayload())) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if len(n.targetCmd) != 0 && !n.strInRange(string(tcpLayer.LayerPayload())) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(n.Flags) == 0 {
|
||||
return true
|
||||
}
|
||||
if tcpLayer == nil {
|
||||
return false
|
||||
}
|
||||
tl := tcpLayer.(*layers.TCP)
|
||||
for _, seq := range n.Flags {
|
||||
notMatch := false
|
||||
bkfor:
|
||||
for _, v := range strings.Split(strings.ToUpper(seq), ",") {
|
||||
switch strings.TrimSpace(v) {
|
||||
case "SYN":
|
||||
if !tl.SYN {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "ACK":
|
||||
if !tl.ACK {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "FIN":
|
||||
if !tl.FIN {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "RST":
|
||||
if !tl.RST {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "CWR":
|
||||
if !tl.CWR {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "ECE":
|
||||
if !tl.ECE {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "NS":
|
||||
if !tl.NS {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "PSH":
|
||||
if !tl.PSH {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "URG":
|
||||
if !tl.URG {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
}
|
||||
}
|
||||
if !notMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func RealSendRST(info bcap.PacketInfo, target string, number int) {
|
||||
for i := 0; i < number; i++ {
|
||||
if target == "both" || target == "target" {
|
||||
SendRST(info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpSeq()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
if target == "both" || target == "reverse" {
|
||||
SendRST(info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendRST(srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
return sendIPv6(srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
|
||||
func SendSYN(srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
return sendIPv6(srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
|
||||
func sendIPv4(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
iPv4 := layers.IPv4{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 4,
|
||||
TTL: 64,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err = tcp.SetNetworkLayerForChecksum(&iPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := syscall.SockaddrInet4{
|
||||
Port: dPort,
|
||||
Addr: [4]byte{dstNetIP.To4()[0], dstNetIP.To4()[1], dstNetIP.To4()[2], dstNetIP.To4()[3]},
|
||||
}
|
||||
return syscall.Sendto(fd, buffer.Bytes(), 0, &addr)
|
||||
}
|
||||
|
||||
func sendIPv6(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
iPv6 := layers.IPv6{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 6,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: 64,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err := tcp.SetNetworkLayerForChecksum(&iPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := syscall.SockaddrInet6{
|
||||
Port: 0,
|
||||
Addr: [16]byte(dstNetIP.To16()),
|
||||
}
|
||||
return syscall.Sendto(fd, buffer.Bytes(), 0, &addr)
|
||||
}
|
596
tcm/tcpmonitor_windows.go
Normal file
596
tcm/tcpmonitor_windows.go
Normal file
@ -0,0 +1,596 @@
|
||||
//go:build windows && !arm64
|
||||
|
||||
package tcm
|
||||
|
||||
import (
|
||||
"b612.me/bcap"
|
||||
"b612.me/bcap/libpcap"
|
||||
"b612.me/starlog"
|
||||
"b612.me/starmap"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
"github.com/google/gopacket/pcapgo"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Libpcap struct {
|
||||
count uint64
|
||||
// 按连接数最后一个历史报文
|
||||
cap *bcap.Packets
|
||||
// 监控目标ip地址列表
|
||||
target []string
|
||||
// 将军,下达命令吧!
|
||||
// 监控命令列表
|
||||
targetCmd []string
|
||||
targetAsHex bool
|
||||
//保存为pcap格式的数据文件
|
||||
saveFile string
|
||||
// 写缓存
|
||||
packetCache chan gopacket.Packet
|
||||
//交互模式
|
||||
interactive bool
|
||||
//展示所有报文信息,包括未在追踪列表的
|
||||
showAll bool
|
||||
//展示payload报文(utf-8直接输出)
|
||||
showPayload bool
|
||||
//payload展示为hex
|
||||
showAsHex bool
|
||||
//payload允许展示的最大字符
|
||||
maxShowPayloadSize int
|
||||
//不展示任何信息
|
||||
noShowMode bool
|
||||
// 触发封禁词后,使用RST重置链路
|
||||
useRST bool
|
||||
// RST模式,target=目标端单向RST,reverse=对端反向RST,both=双向RST
|
||||
rstMode string
|
||||
printColor []*starlog.Color
|
||||
logCache chan loged
|
||||
ctx context.Context
|
||||
fn context.CancelFunc
|
||||
bpf string
|
||||
eth string
|
||||
host string
|
||||
handle *libpcap.NetCatch
|
||||
blockMap sync.Map
|
||||
CapFileCacheNum int
|
||||
Flags []string
|
||||
}
|
||||
|
||||
type loged struct {
|
||||
str string
|
||||
stateLevel int
|
||||
logLevel string
|
||||
}
|
||||
|
||||
func (t *Libpcap) Run() error {
|
||||
var err error
|
||||
stopSignal := make(chan os.Signal)
|
||||
starlog.Noticef("Starting libpcap capture\n")
|
||||
if t.bpf == "" {
|
||||
starlog.Errorln("请输入一个抓包模式")
|
||||
os.Exit(3)
|
||||
}
|
||||
if t.host == "" && t.eth == "" {
|
||||
starlog.Errorln("请输入eth网卡名或host名")
|
||||
ifs, err := libpcap.FindAllDevs()
|
||||
if err == nil {
|
||||
fmt.Println("网卡名如下:\n----------\n")
|
||||
for k, v := range ifs {
|
||||
var ips []string
|
||||
for _, vv := range v.Addresses {
|
||||
ips = append(ips, vv.IP.String())
|
||||
}
|
||||
fmt.Printf("%d.\t%v\t%s\t%v\n", k+1, v.Name, strings.Join(ips, " , "), v.Description)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
return fmt.Errorf("请输入eth网卡名或host名")
|
||||
}
|
||||
if t.host == "" {
|
||||
t.handle, err = libpcap.NewCatchEth(t.eth, t.bpf)
|
||||
} else {
|
||||
t.handle, err = libpcap.NewCatch(t.host, t.bpf)
|
||||
}
|
||||
if err != nil {
|
||||
starlog.Errorln("failed to listen:", err)
|
||||
return err
|
||||
}
|
||||
t.handle.SetRecall(t.handlePacket)
|
||||
if t.targetAsHex {
|
||||
for k, v := range t.targetCmd {
|
||||
tmp, _ := hex.DecodeString(v)
|
||||
t.targetCmd[k] = string(tmp)
|
||||
}
|
||||
}
|
||||
go func() {
|
||||
if err = t.handle.Run(); err != nil {
|
||||
starlog.Errorln(err)
|
||||
stopSignal <- syscall.SIGKILL
|
||||
}
|
||||
}()
|
||||
if t.saveFile != "" {
|
||||
f, err := os.Create(t.saveFile)
|
||||
if err != nil {
|
||||
starlog.Errorln("创建写入文件失败", err)
|
||||
os.Exit(4)
|
||||
}
|
||||
defer f.Close()
|
||||
go t.pcapWriter(t.ctx, f)
|
||||
}
|
||||
signal.Notify(stopSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
go t.logPrint(t.ctx)
|
||||
select {
|
||||
//todo need nf.Stop()
|
||||
case <-stopSignal:
|
||||
starlog.Warningf("Received stop signal\n")
|
||||
|
||||
case <-t.ctx.Done():
|
||||
starlog.Infoln("TCPMonitor Task Finished")
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func NewLibpcap() *Libpcap {
|
||||
var nf = new(Libpcap)
|
||||
nf.packetCache = make(chan gopacket.Packet, 2048)
|
||||
nf.logCache = make(chan loged, 2048)
|
||||
nf.printColor = []*starlog.Color{
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgHiCyan), //1=tcp_connect_1,
|
||||
starlog.NewColor(starlog.FgHiCyan), //2=tcp_connect_2,
|
||||
starlog.NewColor(starlog.FgHiCyan), //3=tcp_connect_3,
|
||||
starlog.NewColor(starlog.FgCyan), //4=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //5=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //6=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //7=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgCyan), //8=tcp_bye_bye
|
||||
starlog.NewColor(starlog.FgGreen), //9=tcp_ok
|
||||
starlog.NewColor(starlog.BgRed, starlog.FgYellow), //10=tcp_retrans
|
||||
starlog.NewColor(starlog.FgHiMagenta), //ece
|
||||
starlog.NewColor(starlog.FgHiMagenta), //cwr
|
||||
starlog.NewColor(starlog.FgRed), //rst
|
||||
starlog.NewColor(starlog.FgHiGreen), //keepalive
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
starlog.NewColor(starlog.FgWhite), //20=udp
|
||||
starlog.NewColor(starlog.FgWhite), //0=unknown
|
||||
}
|
||||
nf.cap = bcap.NewPackets()
|
||||
nf.ctx, nf.fn = context.WithCancel(context.Background())
|
||||
return nf
|
||||
}
|
||||
|
||||
func (n *Libpcap) logPrint(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case l := <-n.logCache:
|
||||
if n.noShowMode {
|
||||
fmt.Printf("已捕获报文数量:%v个\r", n.count)
|
||||
continue
|
||||
}
|
||||
switch l.logLevel {
|
||||
case "info":
|
||||
starlog.Info(l.str)
|
||||
case "notice":
|
||||
starlog.Notice(l.str)
|
||||
case "warning":
|
||||
starlog.Warning(l.str)
|
||||
case "error":
|
||||
starlog.Error(l.str)
|
||||
case "critical":
|
||||
starlog.Critical(l.str)
|
||||
case "debug":
|
||||
starlog.Debug(l.str)
|
||||
case "payload":
|
||||
fmt.Println(l.str)
|
||||
default:
|
||||
n.printColor[l.stateLevel].Print(l.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Libpcap) ipInRange(ip string) bool {
|
||||
if len(n.target) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range n.target {
|
||||
if v == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *Libpcap) strInRange(str string) bool {
|
||||
if len(n.targetCmd) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, v := range n.targetCmd {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(str, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *Libpcap) handlePacket(p gopacket.Packet) {
|
||||
n.count++
|
||||
if n.saveFile != "" {
|
||||
n.packetCache <- p
|
||||
}
|
||||
var layer gopacket.Layer
|
||||
for _, layerType := range []gopacket.LayerType{
|
||||
layers.LayerTypeTCP, layers.LayerTypeUDP, layers.LayerTypeICMPv4, layers.LayerTypeICMPv6,
|
||||
layers.LayerTypeIPv4, layers.LayerTypeIPv6, layers.LayerTypeARP,
|
||||
} {
|
||||
layer = p.Layer(layerType)
|
||||
if layer == nil {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if layer == nil {
|
||||
n.logCache <- loged{
|
||||
str: "无法定义layer类型\n",
|
||||
stateLevel: 0,
|
||||
logLevel: "error",
|
||||
}
|
||||
return
|
||||
}
|
||||
info, err := n.cap.ParsePacket(p)
|
||||
if err != nil {
|
||||
starlog.Errorln(err)
|
||||
return
|
||||
}
|
||||
var dec string
|
||||
switch info.StateDescript() {
|
||||
case 0:
|
||||
dec = "未知状态"
|
||||
case 1:
|
||||
dec = "SYN tcp建立一次握手"
|
||||
case 2:
|
||||
dec = "SYN,ACK tcp建立二次握手"
|
||||
case 3:
|
||||
dec = "ACK tcp建立三次握手"
|
||||
case 4:
|
||||
dec = "FIN tcp断开一次挥手"
|
||||
case 5:
|
||||
dec = "ACK tcp断开二次挥手"
|
||||
case 6:
|
||||
dec = "FIN,ACK tcp断开二次三次挥手"
|
||||
case 7:
|
||||
dec = "FIN tcp断开三次挥手"
|
||||
case 8:
|
||||
dec = "ACK tcp断开四次挥手"
|
||||
case 9:
|
||||
dec = "tcp报文"
|
||||
case 10:
|
||||
dec = "TCP重传"
|
||||
case 11:
|
||||
dec = "TCP ece"
|
||||
case 12:
|
||||
dec = "TCP cwr"
|
||||
case 13:
|
||||
dec = "TCP RST重置"
|
||||
case 14:
|
||||
dec = "TCP Keepalive"
|
||||
}
|
||||
if !n.showAll && !n.ipInRange(info.SrcIP) && !n.ipInRange(info.DstIP) {
|
||||
return
|
||||
}
|
||||
if len(n.Flags) > 0 && !n.writerMatch(p, true) {
|
||||
return
|
||||
}
|
||||
n.logCache <- loged{
|
||||
str: fmt.Sprintf("%s %v:%v -> %v:%v %s seq=%v ack=%v win=%v len=%v\n", time.Now().Format("2006-01-02 15:04:05.000000"), info.SrcIP, info.SrcPort,
|
||||
info.DstIP, info.DstPort, dec, info.TcpSeq(), info.TcpAck(), info.TcpWindow(), info.TcpPayloads()),
|
||||
stateLevel: int(info.StateDescript()),
|
||||
logLevel: "",
|
||||
}
|
||||
|
||||
if n.showPayload {
|
||||
str := string(layer.LayerPayload())
|
||||
if n.maxShowPayloadSize > 0 {
|
||||
if len(str) > n.maxShowPayloadSize {
|
||||
str = str[:n.maxShowPayloadSize]
|
||||
}
|
||||
}
|
||||
if n.showAsHex {
|
||||
str = hex.EncodeToString([]byte(str))
|
||||
}
|
||||
n.logCache <- loged{
|
||||
str: str,
|
||||
stateLevel: int(info.StateDescript()),
|
||||
logLevel: "payload",
|
||||
}
|
||||
}
|
||||
if n.ipInRange(info.SrcIP) || n.ipInRange(info.DstIP) {
|
||||
_, ok := n.blockMap.Load(info.Key)
|
||||
if n.useRST && (ok || n.strInRange(string(layer.LayerPayload()))) {
|
||||
n.blockMap.Store(info.Key, true)
|
||||
starlog.Warningf("触发封禁关键词 RST重置\n")
|
||||
RealSendRST(n.handle.Handle, info, n.rstMode, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RealSendRST(p *pcap.Handle, info bcap.PacketInfo, target string, number int) {
|
||||
for i := 0; i < number; i++ {
|
||||
if target == "both" || target == "target" {
|
||||
SendRST(p, info.SrcMac, info.DstMac, info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpSeq()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
//SendRST(p, info.DstMac, info.SrcMac, info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpSeq()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
if target == "both" || target == "reverse" {
|
||||
SendRST(p, info.DstMac, info.SrcMac, info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
//SendRST(p, info.SrcMac, info.DstMac, info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Libpcap) pcapWriter(stopCtx context.Context, fp *os.File) error {
|
||||
w := pcapgo.NewWriter(fp)
|
||||
err := w.WriteFileHeader(65535, layers.LinkTypeEthernet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buf := starmap.NewStarChanStack(uint64(n.CapFileCacheNum))
|
||||
avail := 0
|
||||
for {
|
||||
select {
|
||||
case <-stopCtx.Done():
|
||||
return nil
|
||||
case p := <-n.packetCache:
|
||||
if n.CapFileCacheNum == 0 {
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
continue
|
||||
}
|
||||
if avail > 0 {
|
||||
avail--
|
||||
if n.writerMatch(p, false) {
|
||||
avail = n.CapFileCacheNum
|
||||
}
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
continue
|
||||
}
|
||||
if buf.Free() == 0 {
|
||||
buf.Pop()
|
||||
}
|
||||
if !n.writerMatch(p, false) {
|
||||
buf.Push(p)
|
||||
continue
|
||||
}
|
||||
for buf.Len() > 0 {
|
||||
hp, err := buf.Pop()
|
||||
if err == nil {
|
||||
w.WritePacket(hp.(gopacket.Packet).Metadata().CaptureInfo, hp.(gopacket.Packet).Data())
|
||||
}
|
||||
}
|
||||
w.WritePacket(p.Metadata().CaptureInfo, p.Data())
|
||||
avail = n.CapFileCacheNum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Libpcap) writerMatch(pkt gopacket.Packet, onlyFlags bool) bool {
|
||||
tcpLayer := pkt.Layer(layers.LayerTypeTCP)
|
||||
if !onlyFlags {
|
||||
var src, dst string
|
||||
if nw := pkt.NetworkLayer(); nw != nil {
|
||||
srcp, dstp := nw.NetworkFlow().Endpoints()
|
||||
src = srcp.String()
|
||||
dst = dstp.String()
|
||||
}
|
||||
if !(n.ipInRange(src) || n.ipInRange(dst)) {
|
||||
return false
|
||||
}
|
||||
if tcpLayer == nil {
|
||||
if len(n.targetCmd) != 0 && !n.strInRange(string(pkt.TransportLayer().LayerPayload())) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if len(n.targetCmd) != 0 && !n.strInRange(string(tcpLayer.LayerPayload())) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if len(n.Flags) == 0 {
|
||||
return true
|
||||
}
|
||||
if tcpLayer == nil {
|
||||
return false
|
||||
}
|
||||
tl := tcpLayer.(*layers.TCP)
|
||||
for _, seq := range n.Flags {
|
||||
notMatch := false
|
||||
bkfor:
|
||||
for _, v := range strings.Split(strings.ToUpper(seq), ",") {
|
||||
switch strings.TrimSpace(v) {
|
||||
case "SYN":
|
||||
if !tl.SYN {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "ACK":
|
||||
if !tl.ACK {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "FIN":
|
||||
if !tl.FIN {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "RST":
|
||||
if !tl.RST {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "CWR":
|
||||
if !tl.CWR {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "ECE":
|
||||
if !tl.ECE {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "NS":
|
||||
if !tl.NS {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "PSH":
|
||||
if !tl.PSH {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
case "URG":
|
||||
if !tl.URG {
|
||||
notMatch = true
|
||||
break bkfor
|
||||
}
|
||||
}
|
||||
}
|
||||
if !notMatch {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func SendRST(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
return sendIPv6(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
|
||||
func SendSYN(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
return sendIPv6(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
|
||||
func sendIPv4(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
eth := layers.Ethernet{
|
||||
SrcMAC: srcMac,
|
||||
DstMAC: dstMac,
|
||||
EthernetType: layers.EthernetTypeIPv4,
|
||||
}
|
||||
iPv4 := layers.IPv4{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 4,
|
||||
TTL: 64,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err = tcp.SetNetworkLayerForChecksum(&iPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if srcMac == nil && dstMac == nil {
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = gopacket.SerializeLayers(buffer, options, ð, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return p.WritePacketData(buffer.Bytes())
|
||||
}
|
||||
|
||||
func sendIPv6(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
eth := layers.Ethernet{
|
||||
SrcMAC: srcMac,
|
||||
DstMAC: dstMac,
|
||||
EthernetType: layers.EthernetTypeIPv6,
|
||||
}
|
||||
iPv6 := layers.IPv6{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 6,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: 64,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
if err := tcp.SetNetworkLayerForChecksum(&iPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if srcMac == nil && dstMac == nil {
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = gopacket.SerializeLayers(buffer, options, ð, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return p.WritePacketData(buffer.Bytes())
|
||||
}
|
41
tcpkill/cmd.go
Normal file
41
tcpkill/cmd.go
Normal file
@ -0,0 +1,41 @@
|
||||
//go:build !(windows && arm64)
|
||||
|
||||
package tcpkill
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
var tck = &TCPKill{}
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().StringVarP(&tck.SrcIP, "src-ip", "s", "", "本地源IP")
|
||||
Cmd.Flags().IntVarP(&tck.SrcPort, "src-port", "p", 0, "本地源端口")
|
||||
Cmd.Flags().StringVarP(&tck.DstIP, "dst-ip", "d", "", "目标IP")
|
||||
Cmd.Flags().IntVarP(&tck.DstPort, "dst-port", "P", 0, "目标端口")
|
||||
Cmd.Flags().StringVarP(&tck.Status, "status", "S", "", "匹配的连接状态,如ESTABLISHED,CLOSE_WAIT等,为空则匹配所有")
|
||||
Cmd.Flags().IntVarP(&tck.RstNumbers, "rst-numbers", "n", 3, "RST包数量")
|
||||
Cmd.Flags().BoolVarP(&tck.WaitMode, "wait", "w", false, "等待模式")
|
||||
Cmd.Flags().StringVarP(&tck.KillType, "kill-type", "t", "both", "RST通知类型,both=都通知 target=目标地址通知 reverse=来源地址通知")
|
||||
if runtime.GOOS != "windows" {
|
||||
Cmd.Flags().BoolVarP(&tck.AutoIptables, "auto-iptables", "a", true, "自动设置iptables")
|
||||
Cmd.Flags().IntVarP(&tck.NFQNums, "nfq-nums", "q", 0, "NFQ队列号")
|
||||
} else {
|
||||
Cmd.Flags().StringVarP(&tck.Eth, "eth", "e", "", "网卡")
|
||||
Cmd.Flags().StringVarP(&tck.BPF, "bpf", "b", "", "BPF过滤")
|
||||
Cmd.Flags().StringVarP(&tck.Host, "host", "i", "", "主机")
|
||||
}
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tcpkill",
|
||||
Short: "杀掉那个TCP连接",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := tck.Run()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
16
tcpkill/cmd_winarm64.go
Normal file
16
tcpkill/cmd_winarm64.go
Normal file
@ -0,0 +1,16 @@
|
||||
//go:build windows && arm64
|
||||
|
||||
package tcpkill
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "tcpkill",
|
||||
Short: "杀掉那个TCP连接",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("windows on arm is not supported yet")
|
||||
},
|
||||
}
|
108
tcpkill/tcpkill.go
Normal file
108
tcpkill/tcpkill.go
Normal file
@ -0,0 +1,108 @@
|
||||
//go:build !(windows && arm64)
|
||||
|
||||
package tcpkill
|
||||
|
||||
import (
|
||||
"b612.me/bcap"
|
||||
"b612.me/starlog"
|
||||
"context"
|
||||
"fmt"
|
||||
netm "github.com/shirou/gopsutil/v4/net"
|
||||
"net"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func (t *TCPKill) PreRun() error {
|
||||
if t.cap == nil {
|
||||
t.cap = bcap.NewPackets()
|
||||
t.ctx, t.stopFn = context.WithCancel(context.Background())
|
||||
}
|
||||
t.requests = make(chan *handler, 1024)
|
||||
if t.KillType == "" {
|
||||
t.KillType = "both"
|
||||
}
|
||||
conns, err := netm.Connections("tcp")
|
||||
if err != nil {
|
||||
starlog.Errorf("Failed to get system connections:%v\n", err)
|
||||
return err
|
||||
}
|
||||
starlog.Infof("Got %d connections\n", len(conns))
|
||||
for _, conn := range conns {
|
||||
//fmt.Printf("Connection: %v => %v PID=%v Status=%v\n", conn.Laddr, conn.Raddr, conn.Pid, conn.Status)
|
||||
if t.Match(conn) {
|
||||
fmt.Printf("Matched connection: %v:%v => %v:%v PID=%v Status=%v\n", conn.Laddr.IP, conn.Laddr.Port, conn.Raddr.IP, conn.Raddr.Port, conn.Pid, conn.Status)
|
||||
t.matchConns.Store(key(conn), conn)
|
||||
t.matchCount++
|
||||
}
|
||||
}
|
||||
if t.matchCount == 0 && !t.WaitMode {
|
||||
starlog.Warningln("No matched connection")
|
||||
return fmt.Errorf("No matched connection")
|
||||
}
|
||||
starlog.Infof("Matched %d connections\n", t.matchCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TCPKill) Match(info netm.ConnectionStat) bool {
|
||||
if info.Status != "PCAP" {
|
||||
if t.Status != "" && t.Status != info.Status {
|
||||
return false
|
||||
}
|
||||
if info.Status == "DELETE" || info.Status == "CLOSED" || info.Status == "LISTEN" {
|
||||
return false
|
||||
}
|
||||
if runtime.GOOS == "windows" && info.Status == "TIME_WAIT" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if _, ok := t.matchConns.Load(key(info)); ok {
|
||||
return true
|
||||
}
|
||||
if t.SrcIP == "" && t.SrcPort == 0 && t.DstIP == "" && t.DstPort == 0 {
|
||||
if t.Status != "" && info.Status != "PCAP" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
innerCheck := func(srcIP string, srcPort int, conns netm.Addr) bool {
|
||||
sIp := net.ParseIP(srcIP)
|
||||
if sIp == nil {
|
||||
return false
|
||||
}
|
||||
lAddr := net.ParseIP(conns.IP)
|
||||
if lAddr != nil {
|
||||
if sIp.To16() != nil && lAddr.To16() != nil && !lAddr.To16().Equal(sIp.To16()) {
|
||||
return false
|
||||
}
|
||||
if sIp.To4() != nil && lAddr.To4() != nil && !lAddr.To4().Equal(sIp.To4()) {
|
||||
return false
|
||||
}
|
||||
if (sIp.To4() != nil && lAddr.To4() == nil) || (sIp.To4() == nil && lAddr.To4() != nil) {
|
||||
return false
|
||||
}
|
||||
if srcPort != 0 && uint32(srcPort) != conns.Port {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
if t.SrcIP != "" {
|
||||
if !innerCheck(t.SrcIP, t.SrcPort, info.Laddr) {
|
||||
return false
|
||||
}
|
||||
} else if t.SrcPort != 0 && t.SrcPort != int(info.Laddr.Port) {
|
||||
return false
|
||||
}
|
||||
if t.DstIP != "" {
|
||||
if !innerCheck(t.DstIP, t.DstPort, info.Raddr) {
|
||||
return false
|
||||
}
|
||||
} else if t.DstPort != 0 && t.DstPort != int(info.Raddr.Port) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func key(i netm.ConnectionStat) string {
|
||||
return fmt.Sprintf("tcp://%v:%v-%v:%v", i.Laddr.IP, i.Laddr.Port, i.Raddr.IP, i.Raddr.Port)
|
||||
}
|
372
tcpkill/tcpkill_unix.go
Normal file
372
tcpkill/tcpkill_unix.go
Normal file
@ -0,0 +1,372 @@
|
||||
//go:build !windows
|
||||
|
||||
package tcpkill
|
||||
|
||||
import (
|
||||
"b612.me/bcap"
|
||||
"b612.me/bcap/nfq"
|
||||
"b612.me/starlog"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/florianl/go-nfqueue/v2"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
netm "github.com/shirou/gopsutil/v4/net"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCPKill struct {
|
||||
NFQNums int
|
||||
AutoIptables bool
|
||||
SrcIP string
|
||||
SrcPort int
|
||||
DstIP string
|
||||
DstPort int
|
||||
Status string
|
||||
KillType string
|
||||
RstNumbers int
|
||||
WaitMode bool
|
||||
matchConns sync.Map
|
||||
matchCount uint
|
||||
sync.Mutex
|
||||
cache []string
|
||||
cap *bcap.Packets
|
||||
ctx context.Context
|
||||
stopFn context.CancelFunc
|
||||
requests chan *handler
|
||||
BPF string
|
||||
Eth string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (t *TCPKill) doIptables() error {
|
||||
if t.SrcPort != 0 {
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "PREROUTING", "-p", "tcp", "--dport", strconv.Itoa(t.SrcPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D PREROUTING -p tcp --dport "+strconv.Itoa(t.SrcPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "OUTPUT", "-p", "tcp", "--sport", strconv.Itoa(t.SrcPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D OUTPUT -p tcp --sport "+strconv.Itoa(t.SrcPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
return nil
|
||||
}
|
||||
if t.DstPort != 0 {
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "PREROUTING", "-p", "tcp", "--sport", strconv.Itoa(t.DstPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D PREROUTING -p tcp --sport "+strconv.Itoa(t.DstPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
if _, err := exec.Command("iptables", "-t", "raw", "-A", "OUTPUT", "-p", "tcp", "--dport", strconv.Itoa(t.DstPort), "-j", "NFQUEUE", "--queue-num", strconv.Itoa(t.NFQNums)).CombinedOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.cache = append(t.cache, "-t raw -D OUTPUT -p tcp --dport "+strconv.Itoa(t.DstPort)+" -j NFQUEUE --queue-num "+strconv.Itoa(t.NFQNums))
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("No src or dst port detect,it is too dangerous to set iptables automatically without port,please operate manually")
|
||||
}
|
||||
|
||||
func (t *TCPKill) undoIptables() {
|
||||
for _, cmd := range t.cache {
|
||||
exec.Command("iptables", strings.Fields(cmd)...).CombinedOutput()
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPKill) Run() error {
|
||||
if err := t.PreRun(); err != nil {
|
||||
return err
|
||||
}
|
||||
stopSignal := make(chan os.Signal)
|
||||
starlog.Noticef("Starting nfqueue capture\n")
|
||||
nf := nfq.NewNfQueue(t.ctx, uint16(t.NFQNums), 65535)
|
||||
nf.SetRecall(t.handleRoute)
|
||||
go func() {
|
||||
if err := nf.Run(); err != nil {
|
||||
starlog.Errorln(err)
|
||||
stopSignal <- syscall.SIGKILL
|
||||
}
|
||||
}()
|
||||
if t.AutoIptables {
|
||||
if _, err := exec.Command("iptables", "-V").CombinedOutput(); err != nil {
|
||||
starlog.Warningln("iptables not found, cannot auto set iptables")
|
||||
return fmt.Errorf("iptables not found")
|
||||
}
|
||||
defer t.undoIptables()
|
||||
if err := t.doIptables(); err != nil {
|
||||
starlog.Errorf("Failed to set iptables:%v\n", err)
|
||||
return err
|
||||
}
|
||||
starlog.Infof("Set iptables success\n")
|
||||
}
|
||||
signal.Notify(stopSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
go t.SynSent()
|
||||
go t.handleNfResult()
|
||||
select {
|
||||
//todo need nf.Stop()
|
||||
case <-stopSignal:
|
||||
starlog.Warningf("Received stop signal\n")
|
||||
|
||||
case <-t.ctx.Done():
|
||||
starlog.Infoln("TCPKILL Task Finished")
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPKill) handleNfResult() {
|
||||
for {
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case info := <-t.requests:
|
||||
if info == nil {
|
||||
continue
|
||||
}
|
||||
info.p.SetVerdict(info.id, <-info.fin)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPKill) handleRoute(id uint32, q *nfqueue.Nfqueue, p nfq.Packet) {
|
||||
if t.requests == nil {
|
||||
q.SetVerdict(id, nfqueue.NfAccept)
|
||||
return
|
||||
}
|
||||
info, err := t.cap.ParsePacket(p.Packet)
|
||||
if err != nil {
|
||||
q.SetVerdict(id, nfqueue.NfAccept)
|
||||
return
|
||||
}
|
||||
fin := make(chan int)
|
||||
t.requests <- &handler{
|
||||
id: id,
|
||||
p: q,
|
||||
packet: p.Packet,
|
||||
attr: p.Attr,
|
||||
fin: fin,
|
||||
}
|
||||
go func() {
|
||||
fin <- t.handlePacket(info, p)
|
||||
}()
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
id uint32
|
||||
p *nfqueue.Nfqueue
|
||||
packet gopacket.Packet
|
||||
attr nfqueue.Attribute
|
||||
fin chan int
|
||||
}
|
||||
|
||||
func (t *TCPKill) handlePacket(info bcap.PacketInfo, p nfq.Packet) int {
|
||||
if p.Packet == nil {
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
tcpLayer := p.Packet.Layer(layers.LayerTypeTCP)
|
||||
if tcpLayer == nil {
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
tcp := tcpLayer.(*layers.TCP)
|
||||
if tcp.SYN && !tcp.ACK {
|
||||
//starlog.Debugf("SYN packet:%v\n", p.Packet)
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
if tcp.RST {
|
||||
starlog.Warningf("RST packet:%v <==> %v\n", info.SrcIP+":"+info.SrcPort, info.DstIP+":"+info.DstPort)
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
if !t.Match(netm.ConnectionStat{
|
||||
Status: "PCAP",
|
||||
Laddr: netm.Addr{
|
||||
IP: info.SrcIP,
|
||||
Port: uint32(tcp.SrcPort),
|
||||
},
|
||||
Raddr: netm.Addr{
|
||||
IP: info.DstIP,
|
||||
Port: uint32(tcp.DstPort),
|
||||
},
|
||||
}) && !t.Match(netm.ConnectionStat{
|
||||
Status: "PCAP",
|
||||
Raddr: netm.Addr{
|
||||
IP: info.SrcIP,
|
||||
Port: uint32(tcp.SrcPort),
|
||||
},
|
||||
Laddr: netm.Addr{
|
||||
IP: info.DstIP,
|
||||
Port: uint32(tcp.DstPort),
|
||||
},
|
||||
}) {
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
RealSendRST(info, t.KillType, t.RstNumbers)
|
||||
return nfqueue.NfAccept
|
||||
}
|
||||
|
||||
func (t *TCPKill) SynSent() {
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 2500)
|
||||
realConns, err := netm.Connections("tcp")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
t.matchConns.Range(func(key, value any) bool {
|
||||
t.matchConns.Delete(key)
|
||||
t.matchCount--
|
||||
return true
|
||||
})
|
||||
for _, conn := range realConns {
|
||||
if t.Match(conn) {
|
||||
t.matchConns.Store(key(conn), conn)
|
||||
t.matchCount++
|
||||
}
|
||||
}
|
||||
if t.matchCount == 0 && !t.WaitMode {
|
||||
starlog.Warningln("No matched connection anymore")
|
||||
t.stopFn()
|
||||
return
|
||||
}
|
||||
t.matchConns.Range(func(k, v any) bool {
|
||||
conn := v.(netm.ConnectionStat)
|
||||
if err := SendSYN(conn.Laddr.IP, strconv.Itoa(int(conn.Laddr.Port)), conn.Raddr.IP, strconv.Itoa(int(conn.Raddr.Port)), 1127); err != nil {
|
||||
starlog.Errorf("Failed to send SYN:%v\n", err)
|
||||
}
|
||||
if err := SendSYN(conn.Raddr.IP, strconv.Itoa(int(conn.Raddr.Port)), conn.Laddr.IP, strconv.Itoa(int(conn.Laddr.Port)), 1127); err != nil {
|
||||
starlog.Errorf("Failed to send SYN:%v\n", err)
|
||||
}
|
||||
starlog.Infof("Send SYN to %v <==> %v\n", conn.Laddr.String(), conn.Raddr.String())
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RealSendRST(info bcap.PacketInfo, target string, number int) {
|
||||
for i := 0; i < number; i++ {
|
||||
if target == "both" || target == "target" {
|
||||
seq := uint32(info.TcpPayloads()) + info.TcpSeq() + (uint32(i) * uint32(info.TcpWindow()))
|
||||
SendRST(info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, seq)
|
||||
}
|
||||
if target == "both" || target == "reverse" {
|
||||
SendRST(info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendRST(srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
return sendIPv6(srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
|
||||
func SendSYN(srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
return sendIPv6(srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
|
||||
func sendIPv4(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
iPv4 := layers.IPv4{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 4,
|
||||
TTL: 64,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err = tcp.SetNetworkLayerForChecksum(&iPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := syscall.SockaddrInet4{
|
||||
Port: dPort,
|
||||
Addr: [4]byte{dstNetIP.To4()[0], dstNetIP.To4()[1], dstNetIP.To4()[2], dstNetIP.To4()[3]},
|
||||
}
|
||||
return syscall.Sendto(fd, buffer.Bytes(), 0, &addr)
|
||||
}
|
||||
|
||||
func sendIPv6(srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
iPv6 := layers.IPv6{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 6,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: 64,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err := tcp.SetNetworkLayerForChecksum(&iPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
addr := syscall.SockaddrInet6{
|
||||
Port: 0,
|
||||
Addr: [16]byte(dstNetIP.To16()),
|
||||
}
|
||||
return syscall.Sendto(fd, buffer.Bytes(), 0, &addr)
|
||||
}
|
390
tcpkill/tcpkill_windows.go
Normal file
390
tcpkill/tcpkill_windows.go
Normal file
@ -0,0 +1,390 @@
|
||||
//go:build windows && (amd64 || 386)
|
||||
|
||||
package tcpkill
|
||||
|
||||
import (
|
||||
"b612.me/bcap"
|
||||
"b612.me/bcap/libpcap"
|
||||
"b612.me/starlog"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
netm "github.com/shirou/gopsutil/v4/net"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TCPKill struct {
|
||||
NFQNums int
|
||||
AutoIptables bool
|
||||
BPF string
|
||||
Eth string
|
||||
Host string
|
||||
SrcIP string
|
||||
SrcPort int
|
||||
DstIP string
|
||||
DstPort int
|
||||
KillType string
|
||||
RstNumbers int
|
||||
WaitMode bool
|
||||
Status string
|
||||
matchConns sync.Map
|
||||
matchCount uint
|
||||
sync.Mutex
|
||||
cache []string
|
||||
cap *bcap.Packets
|
||||
ctx context.Context
|
||||
stopFn context.CancelFunc
|
||||
requests chan *handler
|
||||
pc *libpcap.NetCatch
|
||||
macCache map[string]net.HardwareAddr
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
}
|
||||
|
||||
func (t *TCPKill) presetWindows() {
|
||||
t.macCache = make(map[string]net.HardwareAddr)
|
||||
if t.BPF == "" {
|
||||
if t.DstIP != "" {
|
||||
t.BPF = fmt.Sprintf("tcp and host %s", t.DstIP)
|
||||
} else if t.SrcIP != "" {
|
||||
t.BPF = fmt.Sprintf("tcp and host %s", t.SrcIP)
|
||||
} else if t.SrcPort != 0 {
|
||||
t.BPF = fmt.Sprintf("tcp and port %d", t.SrcPort)
|
||||
} else if t.DstPort != 0 {
|
||||
t.BPF = fmt.Sprintf("tcp and port %d", t.DstPort)
|
||||
} else {
|
||||
t.BPF = "tcp"
|
||||
}
|
||||
}
|
||||
if t.Eth == "" && t.Host == "" {
|
||||
allDevice, err := pcap.FindAllDevs()
|
||||
if err != nil {
|
||||
starlog.Errorf("get pcap devices failed:%v\n", err)
|
||||
return
|
||||
}
|
||||
if t.SrcIP == "" {
|
||||
defer func() {
|
||||
t.SrcIP = ""
|
||||
}()
|
||||
ip := t.DstIP
|
||||
if ip == "" {
|
||||
ip = "223.6.6.6"
|
||||
}
|
||||
if strings.Contains(ip, ":") {
|
||||
ip = "[" + ip + "]"
|
||||
}
|
||||
l, err := net.Dial("udp", ip+":53")
|
||||
if err == nil {
|
||||
t.SrcIP = l.LocalAddr().(*net.UDPAddr).IP.String()
|
||||
starlog.Infof("No eth or ip detected,use %s as source ip\n", t.SrcIP)
|
||||
l.Close()
|
||||
} else {
|
||||
starlog.Errorf("Failed to get source ip:%v\n", err)
|
||||
}
|
||||
}
|
||||
for _, v := range allDevice {
|
||||
if t.SrcIP == "127.0.0.1" && v.Name == "\\Device\\NPF_Loopback" {
|
||||
t.Eth = v.Name
|
||||
return
|
||||
}
|
||||
for _, addr := range v.Addresses {
|
||||
if addr.IP.String() == t.SrcIP {
|
||||
t.Eth = v.Name
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPKill) Run() error {
|
||||
var err error
|
||||
if err = t.PreRun(); err != nil {
|
||||
return err
|
||||
}
|
||||
t.presetWindows()
|
||||
stopSignal := make(chan os.Signal)
|
||||
starlog.Noticef("Starting capture\n")
|
||||
signal.Notify(stopSignal, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
|
||||
if t.Eth != "" {
|
||||
t.pc, err = libpcap.NewCatchEth(t.Eth, t.BPF)
|
||||
if err != nil {
|
||||
starlog.Errorf("Failed to create pcap handle:%v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.Host != "" {
|
||||
t.pc, err = libpcap.NewCatch(t.Host, t.BPF)
|
||||
if err != nil {
|
||||
starlog.Errorf("Failed to create pcap handle:%v\n", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if t.pc == nil {
|
||||
starlog.Errorf("No pcap handle\n")
|
||||
return fmt.Errorf("No pcap handle")
|
||||
}
|
||||
t.pc.SetRecall(t.handlePacket)
|
||||
go func() {
|
||||
if err := t.pc.Run(); err != nil {
|
||||
starlog.Errorln(err)
|
||||
stopSignal <- syscall.SIGKILL
|
||||
}
|
||||
}()
|
||||
go t.SynSent()
|
||||
select {
|
||||
//todo need nf.Stop()
|
||||
case <-stopSignal:
|
||||
starlog.Warningf("Received stop signal\n")
|
||||
|
||||
case <-t.ctx.Done():
|
||||
starlog.Infoln("TCPKILL Task Finished")
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (t *TCPKill) SynSent() {
|
||||
for {
|
||||
time.Sleep(time.Millisecond * 2500)
|
||||
realConns, err := netm.Connections("tcp")
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
t.matchConns.Range(func(key, value any) bool {
|
||||
t.matchConns.Delete(key)
|
||||
t.matchCount--
|
||||
return true
|
||||
})
|
||||
for _, conn := range realConns {
|
||||
if t.Match(conn) {
|
||||
t.matchConns.Store(key(conn), conn)
|
||||
t.matchCount++
|
||||
}
|
||||
}
|
||||
if t.matchCount == 0 && !t.WaitMode {
|
||||
starlog.Warningln("No matched connection anymore")
|
||||
t.stopFn()
|
||||
return
|
||||
}
|
||||
t.matchConns.Range(func(k, v any) bool {
|
||||
conn := v.(netm.ConnectionStat)
|
||||
if t.macCache[conn.Laddr.IP] == nil || t.macCache[conn.Raddr.IP] == nil {
|
||||
target := conn.Raddr.IP + ":" + strconv.Itoa(int(conn.Raddr.Port))
|
||||
if strings.Contains(conn.Raddr.IP, ":") {
|
||||
target = "[" + conn.Raddr.IP + "]:" + strconv.Itoa(int(conn.Raddr.Port))
|
||||
}
|
||||
starlog.Warningf("No mac address for %v or %v,try send syn to %v\n", conn.Laddr.IP, conn.Raddr.IP, target)
|
||||
tcpConn, err := net.DialTimeout("tcp", target, time.Millisecond*800)
|
||||
if err == nil {
|
||||
tcpConn.(*net.TCPConn).SetLinger(0)
|
||||
tcpConn.Close()
|
||||
}
|
||||
time.Sleep(time.Millisecond * 600)
|
||||
}
|
||||
t.Lock()
|
||||
if err := SendSYN(t.pc.Handle, t.macCache[conn.Laddr.IP], t.macCache[conn.Raddr.IP], conn.Laddr.IP, strconv.Itoa(int(conn.Laddr.Port)), conn.Raddr.IP, strconv.Itoa(int(conn.Raddr.Port)), 1127); err != nil {
|
||||
starlog.Errorf("Failed to send SYN:%v\n", err)
|
||||
}
|
||||
if err := SendSYN(t.pc.Handle, t.macCache[conn.Raddr.IP], t.macCache[conn.Laddr.IP], conn.Raddr.IP, strconv.Itoa(int(conn.Raddr.Port)), conn.Laddr.IP, strconv.Itoa(int(conn.Laddr.Port)), 1127); err != nil {
|
||||
starlog.Errorf("Failed to send SYN:%v\n", err)
|
||||
}
|
||||
t.Unlock()
|
||||
starlog.Infof("Send SYN to %v <==> %v\n", conn.Laddr.String(), conn.Raddr.String())
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TCPKill) handlePacket(p gopacket.Packet) {
|
||||
t.Lock()
|
||||
info, err := t.cap.ParsePacket(p)
|
||||
t.Unlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tcpLayer := p.Layer(layers.LayerTypeTCP)
|
||||
if tcpLayer == nil {
|
||||
return
|
||||
}
|
||||
tcp := tcpLayer.(*layers.TCP)
|
||||
if tcp.SYN && !tcp.ACK {
|
||||
return
|
||||
}
|
||||
t.Lock()
|
||||
//fmt.Println(info.SrcIP, hex.EncodeToString(info.SrcMac), info.DstIP, hex.EncodeToString(info.DstMac))
|
||||
t.macCache[info.SrcIP] = info.SrcMac
|
||||
t.macCache[info.DstIP] = info.DstMac
|
||||
t.Unlock()
|
||||
if tcp.RST {
|
||||
starlog.Warningf("RST packet:%v <==> %v\n", info.SrcIP+":"+info.SrcPort, info.DstIP+":"+info.DstPort)
|
||||
return
|
||||
}
|
||||
//starlog.Debugf("packet:%v <==> %v\n", info.SrcIP+":"+info.SrcPort, info.DstIP+":"+info.DstPort)
|
||||
if !t.Match(netm.ConnectionStat{
|
||||
Status: "PCAP",
|
||||
Laddr: netm.Addr{
|
||||
IP: info.SrcIP,
|
||||
Port: uint32(tcp.SrcPort),
|
||||
},
|
||||
Raddr: netm.Addr{
|
||||
IP: info.DstIP,
|
||||
Port: uint32(tcp.DstPort),
|
||||
},
|
||||
}) && !t.Match(netm.ConnectionStat{
|
||||
Status: "PCAP",
|
||||
Raddr: netm.Addr{
|
||||
IP: info.SrcIP,
|
||||
Port: uint32(tcp.SrcPort),
|
||||
},
|
||||
Laddr: netm.Addr{
|
||||
IP: info.DstIP,
|
||||
Port: uint32(tcp.DstPort),
|
||||
},
|
||||
}) {
|
||||
return
|
||||
}
|
||||
starlog.Noticef("Sending RST... %v:%v <==> %v:%v SEQ=%d ACK=%d\n", info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpSeq(), info.TcpAck())
|
||||
RealSendRST(t.pc.Handle, info, t.KillType, t.RstNumbers)
|
||||
}
|
||||
|
||||
func RealSendRST(p *pcap.Handle, info bcap.PacketInfo, target string, number int) {
|
||||
for i := 0; i < number; i++ {
|
||||
if target == "both" || target == "target" {
|
||||
seq := uint32(info.TcpPayloads()) + info.TcpSeq() + (uint32(i) * uint32(info.TcpWindow()))
|
||||
SendRST(p, info.SrcMac, info.DstMac, info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, seq)
|
||||
//SendRST(p, info.DstMac, info.SrcMac, info.SrcIP, info.SrcPort, info.DstIP, info.DstPort, info.TcpSeq()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
|
||||
}
|
||||
if target == "both" || target == "reverse" {
|
||||
SendRST(p, info.DstMac, info.SrcMac, info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
//SendRST(p, info.SrcMac, info.DstMac, info.DstIP, info.DstPort, info.SrcIP, info.SrcPort, info.TcpAck()+(uint32(i)*uint32(info.TcpWindow())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func SendRST(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
return sendIPv6(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, false)
|
||||
}
|
||||
|
||||
func SendSYN(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32) error {
|
||||
if net.ParseIP(dstIP).To4() != nil {
|
||||
return sendIPv4(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
return sendIPv6(p, srcMac, dstMac, srcIP, srcPort, dstIP, dstPort, seq, true)
|
||||
}
|
||||
|
||||
func sendIPv4(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
eth := layers.Ethernet{
|
||||
SrcMAC: srcMac,
|
||||
DstMAC: dstMac,
|
||||
EthernetType: layers.EthernetTypeIPv4,
|
||||
}
|
||||
iPv4 := layers.IPv4{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 4,
|
||||
TTL: 64,
|
||||
Protocol: layers.IPProtocolTCP,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
|
||||
if err = tcp.SetNetworkLayerForChecksum(&iPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if srcMac == nil && dstMac == nil {
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = gopacket.SerializeLayers(buffer, options, ð, &iPv4, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return p.WritePacketData(buffer.Bytes())
|
||||
}
|
||||
|
||||
func sendIPv6(p *pcap.Handle, srcMac, dstMac []byte, srcIP, srcPort, dstIP, dstPort string, seq uint32, isSyn bool) error {
|
||||
dstNetIP := net.ParseIP(dstIP)
|
||||
eth := layers.Ethernet{
|
||||
SrcMAC: srcMac,
|
||||
DstMAC: dstMac,
|
||||
EthernetType: layers.EthernetTypeIPv6,
|
||||
}
|
||||
iPv6 := layers.IPv6{
|
||||
SrcIP: net.ParseIP(srcIP),
|
||||
DstIP: dstNetIP,
|
||||
Version: 6,
|
||||
NextHeader: layers.IPProtocolTCP,
|
||||
HopLimit: 64,
|
||||
}
|
||||
sPort, err := strconv.Atoi(srcPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dPort, err := strconv.Atoi(dstPort)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcp := layers.TCP{
|
||||
SrcPort: layers.TCPPort(sPort),
|
||||
DstPort: layers.TCPPort(dPort),
|
||||
Seq: seq,
|
||||
RST: !isSyn,
|
||||
SYN: isSyn,
|
||||
}
|
||||
if err := tcp.SetNetworkLayerForChecksum(&iPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
buffer := gopacket.NewSerializeBuffer()
|
||||
options := gopacket.SerializeOptions{
|
||||
FixLengths: true,
|
||||
ComputeChecksums: true,
|
||||
}
|
||||
if srcMac == nil && dstMac == nil {
|
||||
if err = gopacket.SerializeLayers(buffer, options, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err = gopacket.SerializeLayers(buffer, options, ð, &iPv6, &tcp); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return p.WritePacketData(buffer.Bytes())
|
||||
}
|
@ -3,5 +3,5 @@ package tls
|
||||
import "testing"
|
||||
|
||||
func TestCert(t *testing.T) {
|
||||
showTls("139.199.163.65:443", true, "")
|
||||
//showTls("139.199.163.65:443", true, "")
|
||||
}
|
||||
|
3
version/version.go
Normal file
3
version/version.go
Normal file
@ -0,0 +1,3 @@
|
||||
package version
|
||||
|
||||
var Version = "2.1.0.beta.17"
|
267
whois/cmd.go
267
whois/cmd.go
@ -1,13 +1,18 @@
|
||||
package whois
|
||||
|
||||
import (
|
||||
"b612.me/sdk/whois"
|
||||
"b612.me/stario"
|
||||
"b612.me/starlog"
|
||||
"b612.me/staros"
|
||||
"github.com/likexian/whois"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/proxy"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -16,13 +21,37 @@ var output string
|
||||
var whoisServer []string
|
||||
var socks5 string
|
||||
var socks5Auth string
|
||||
var showFull bool
|
||||
var hideFormat bool
|
||||
var useDict bool
|
||||
var hideExists bool
|
||||
var hideNoExists bool
|
||||
var thread int
|
||||
var suffix string
|
||||
var retries int
|
||||
|
||||
func init() {
|
||||
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
|
||||
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹")
|
||||
Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
|
||||
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080")
|
||||
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证,示例:username:password")
|
||||
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password")
|
||||
Cmd.Flags().BoolVarP(&showFull, "full", "f", false, "显示完整信息")
|
||||
Cmd.Flags().BoolVarP(&hideFormat, "hide-format", "g", false, "隐藏格式化信息")
|
||||
|
||||
CmdExists.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
|
||||
CmdExists.Flags().StringVarP(&output, "output", "o", "", "输出文件")
|
||||
CmdExists.Flags().BoolVarP(&useDict, "use-dict", "d", false, "使用字典查询")
|
||||
CmdExists.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
|
||||
CmdExists.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理,示例:127.0.0.1:1080")
|
||||
CmdExists.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证,示例:username:password")
|
||||
CmdExists.Flags().BoolVarP(&hideExists, "hide-exists", "e", false, "隐藏存在的域名")
|
||||
CmdExists.Flags().BoolVarP(&hideNoExists, "hide-no-exists", "n", false, "隐藏不存在的域名")
|
||||
CmdExists.Flags().IntVarP(&thread, "thread", "m", 10, "并发查询数")
|
||||
CmdExists.Flags().StringVarP(&suffix, "suffix", "x", "", "域名后缀")
|
||||
CmdExists.Flags().IntVarP(&retries, "retries", "r", 3, "重试次数")
|
||||
|
||||
Cmd.AddCommand(CmdExists)
|
||||
}
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
@ -35,7 +64,6 @@ var Cmd = &cobra.Command{
|
||||
return
|
||||
}
|
||||
if !staros.Exists(output) {
|
||||
cmd.Println("输出文件夹不存在,将使用标准输出")
|
||||
output = ""
|
||||
}
|
||||
c := whois.NewClient()
|
||||
@ -70,9 +98,238 @@ var Cmd = &cobra.Command{
|
||||
cmd.Println("-----------------------------------------------------")
|
||||
continue
|
||||
}
|
||||
cmd.Println(data)
|
||||
if !hideFormat && !data.Exists() {
|
||||
fmt.Printf("域名:\t%s 不存在\n", data.Domain())
|
||||
}
|
||||
if !hideFormat && data.Exists() {
|
||||
fmt.Printf("域名名称:\t%s\n", data.Domain())
|
||||
fmt.Printf("注册商:\t%s\n", data.Registar())
|
||||
if data.HasRegisterDate() {
|
||||
fmt.Printf("注册时间:\t%s\n", data.RegisterDate())
|
||||
}
|
||||
if data.HasExpireDate() {
|
||||
fmt.Printf("到期时间:\t%s\n", data.ExpireDate())
|
||||
}
|
||||
if data.HasUpdateDate() {
|
||||
fmt.Printf("更新时间:\t%s\n", data.UpdateDate())
|
||||
}
|
||||
for _, v := range data.Status() {
|
||||
fmt.Printf("域名状态:\t%s\n", v)
|
||||
}
|
||||
if data.IanaID() != "" {
|
||||
fmt.Printf("IANA ID:\t%s\n", data.IanaID())
|
||||
}
|
||||
if data.Dnssec() != "" {
|
||||
fmt.Printf("DNSSEC:\t%s\n", data.Dnssec())
|
||||
}
|
||||
isShowContact := false
|
||||
if data.RegisterInfo().Name != "" {
|
||||
fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.RegisterInfo().State != "" {
|
||||
fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.RegisterInfo().Country != "" {
|
||||
fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.RegisterInfo().Email != "" {
|
||||
fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
|
||||
isShowContact = true
|
||||
}
|
||||
if !isShowContact {
|
||||
if data.AdminInfo().Name != "" {
|
||||
fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.AdminInfo().State != "" {
|
||||
fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.AdminInfo().Country != "" {
|
||||
fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
|
||||
isShowContact = true
|
||||
}
|
||||
if data.AdminInfo().Email != "" {
|
||||
fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
|
||||
isShowContact = true
|
||||
}
|
||||
}
|
||||
for _, v := range data.NsServers() {
|
||||
fmt.Printf("NS服务器:\t%s\n", v)
|
||||
}
|
||||
for _, v := range data.NsIps() {
|
||||
fmt.Printf("NS IP:\t%s\n", v)
|
||||
}
|
||||
}
|
||||
if showFull {
|
||||
fmt.Println("")
|
||||
fmt.Println(data.RawData())
|
||||
}
|
||||
cmd.Println("-----------------------------------------------------")
|
||||
os.WriteFile(output+"/"+v+".txt", []byte(data), 0644)
|
||||
os.WriteFile(output+"/"+v+".txt", []byte(data.RawData()), 0644)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var CmdExists = &cobra.Command{
|
||||
Use: "exists",
|
||||
Short: "域名是否存在查询",
|
||||
Long: "域名是否存在查询,可使用字典",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.Help()
|
||||
return
|
||||
}
|
||||
var of *os.File
|
||||
var err error
|
||||
if output != "" {
|
||||
of, err = os.Create(output)
|
||||
if err != nil {
|
||||
starlog.Errorln("创建输出文件失败", err)
|
||||
return
|
||||
}
|
||||
defer of.Close()
|
||||
}
|
||||
c := whois.NewClient()
|
||||
if socks5 != "" {
|
||||
var auth *proxy.Auth
|
||||
if socks5Auth != "" {
|
||||
up := strings.SplitN(socks5Auth, ":", 2)
|
||||
if len(up) == 2 {
|
||||
auth = &proxy.Auth{
|
||||
User: up[0],
|
||||
Password: up[1],
|
||||
}
|
||||
} else {
|
||||
starlog.Errorln("socks5认证格式错误")
|
||||
return
|
||||
}
|
||||
}
|
||||
s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct)
|
||||
if err == nil {
|
||||
c.SetDialer(s5Dial)
|
||||
} else {
|
||||
starlog.Errorln("socks5代理错误:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
c.SetTimeout(time.Second * time.Duration(timeout))
|
||||
var d domainList
|
||||
if !useDict {
|
||||
err = d.New("stdin", args)
|
||||
} else {
|
||||
err = d.New("file", args)
|
||||
}
|
||||
if err != nil {
|
||||
starlog.Errorln("初始化域名列表失败", err)
|
||||
return
|
||||
}
|
||||
if thread == 0 {
|
||||
thread = 1
|
||||
}
|
||||
var wg = stario.NewWaitGroup(thread)
|
||||
fin := false
|
||||
for !fin {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
domain, err := d.Next()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
fin = true
|
||||
}
|
||||
return
|
||||
}
|
||||
if suffix != "" {
|
||||
domain = domain + "." + suffix
|
||||
}
|
||||
var data whois.Result
|
||||
for i := 0; i < retries; i++ {
|
||||
data, err = c.Whois(domain, whoisServer...)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
starlog.Errorln("查询失败:", domain, err)
|
||||
return
|
||||
}
|
||||
if !hideExists && data.Exists() {
|
||||
fmt.Println(domain, "已注册")
|
||||
if of != nil {
|
||||
of.WriteString(domain + " 已注册\n")
|
||||
}
|
||||
}
|
||||
if !hideNoExists && !data.Exists() {
|
||||
fmt.Println(domain, "不存在")
|
||||
if of != nil {
|
||||
of.WriteString(domain + " 不存在\n")
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
},
|
||||
}
|
||||
|
||||
type domainList struct {
|
||||
typed string
|
||||
args []string
|
||||
idx int
|
||||
mu sync.Mutex
|
||||
f *os.File
|
||||
scanner *bufio.Reader
|
||||
}
|
||||
|
||||
func (d *domainList) New(types string, args []string) error {
|
||||
var err error
|
||||
d.typed = types
|
||||
d.args = args
|
||||
switch d.typed {
|
||||
case "file":
|
||||
d.f, err = os.OpenFile(d.args[0], os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.scanner = bufio.NewReader(d.f)
|
||||
case "stdin":
|
||||
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *domainList) Next() (string, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
for {
|
||||
switch d.typed {
|
||||
case "file":
|
||||
l, _, err := d.scanner.ReadLine()
|
||||
if err == io.EOF {
|
||||
if d.idx+1 >= len(d.args) {
|
||||
d.f.Close()
|
||||
return "", err
|
||||
}
|
||||
d.idx++
|
||||
d.f.Close()
|
||||
d.f, err = os.OpenFile(d.args[d.idx], os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d.scanner = bufio.NewReader(d.f)
|
||||
continue
|
||||
}
|
||||
return strings.TrimSpace(string(l)), nil
|
||||
case "stdin":
|
||||
if d.idx >= len(d.args) {
|
||||
return "", io.EOF
|
||||
}
|
||||
d.idx++
|
||||
return d.args[d.idx-1], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user