Compare commits

...

5 Commits

48 changed files with 8001 additions and 196 deletions

74
astro/astro.go Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

253
astro/cmd.go Normal file
View 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
View File

@ -0,0 +1 @@
package astro

130
astro/moon.go Normal file
View 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
View 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
View 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)
}
}

View File

@ -105,9 +105,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -116,8 +116,8 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
@ -237,9 +237,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -378,9 +378,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -640,7 +640,7 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥位数:%d\n", n.Size())
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
@ -760,8 +760,8 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
case *rsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
case *ecdsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDSA私钥\n")

32
go.mod
View File

@ -1,16 +1,16 @@
module b612.me/apps/b612
go 1.21.2
toolchain go1.22.4
go 1.20
require (
b612.me/notify v1.2.6
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.10
b612.me/starlog v1.3.4
b612.me/starmap v1.2.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
@ -19,20 +19,26 @@ require (
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/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
github.com/vbauerster/mpb/v8 v8.8.3
golang.org/x/crypto v0.26.0
golang.org/x/net v0.28.0
golang.org/x/sys v0.24.0
golang.org/x/sys v0.26.0
golang.org/x/term v0.23.0
software.sslmate.com/src/go-pkcs12 v0.4.0
)
@ -50,32 +56,44 @@ require (
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.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect

80
go.sum
View File

@ -1,19 +1,20 @@
b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI=
b612.me/notify v1.2.6 h1:fY+0ccP6cJCDvnfRilmPlDK+J8xTYBpYNwf7jaC2IIE=
b612.me/notify v1.2.6/go.mod h1:awcFq3bvbkf3hdviUtOW16Io0IEJXkNPgno7IRe7B9g=
b612.me/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.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
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 v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc=
b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
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=
@ -36,11 +37,9 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aov
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
@ -60,7 +59,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
github.com/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=
@ -70,11 +70,18 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTe
github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/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=
@ -87,22 +94,22 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
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.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/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
@ -116,6 +123,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
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=
@ -126,12 +135,19 @@ 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-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.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=
@ -148,11 +164,18 @@ 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=
@ -161,18 +184,24 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/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=
@ -184,12 +213,15 @@ 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.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=
@ -211,7 +243,10 @@ 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=
@ -220,13 +255,15 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/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=
@ -254,12 +291,14 @@ 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.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=
@ -268,7 +307,6 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

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

View File

@ -1,6 +1,7 @@
package httpserver
import (
"b612.me/apps/b612/version"
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
@ -24,7 +25,7 @@ import (
"time"
)
var version = "2.1.0.b11"
var ver = version.Version
type HttpServerCfgs func(cfg *HttpServerCfg)
@ -320,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
}
@ -617,7 +618,7 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
}
err = tmpt.Execute(w, map[string]interface{}{
"IdxTitle": r.URL.Path,
"Version": version,
"Version": ver,
"Idx": "Index of " + r.URL.Path,
"Upload": template.HTML(upload),
"Data": template.JS(jData),

View File

@ -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("任务完成!")
},
}

View File

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

View File

@ -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"
@ -23,15 +24,19 @@ import (
"b612.me/apps/b612/merge"
"b612.me/apps/b612/mget"
"b612.me/apps/b612/net"
"b612.me/apps/b612/nmon"
"b612.me/apps/b612/rmt"
"b612.me/apps/b612/search"
"b612.me/apps/b612/smtpclient"
"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"
@ -42,7 +47,7 @@ import (
var cmdRoot = &cobra.Command{
Use: "b612",
Version: "2.1.0.beta.13",
Version: version.Version,
}
func init() {
@ -51,7 +56,7 @@ func init() {
base64.Cmd, base85.Cmd, base91.Cmd, attach.Cmd, detach.Cmd, df.Cmd, dfinder.Cmd,
ftp.Cmd, generate.Cmd, hash.Cmd, image.Cmd, merge.Cmd, search.Cmd, split.Cmd, vic.Cmd,
calc.Cmd, net.Cmd, rmt.Cmds, rmt.Cmdc, keygen.Cmd, dns.Cmd, whois.Cmd, socks5.Cmd, httproxy.Cmd, smtpserver.Cmd, smtpclient.Cmd,
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd)
cert.Cmd, aes.Cmd, tls.Cmd, mget.Cmd, tcpkill.Cmd, tcm.Cmd, astro.CmdCal, astro.Cmd, nmon.Cmd)
}
func main() {

View File

@ -34,7 +34,9 @@ func init() {
Cmd.Flags().StringVarP(&mg.Tareget, "output", "o", "", "输出文件名")
Cmd.Flags().IntVarP(&mg.BufferSize, "buffer", "b", 8192, "缓冲区大小")
Cmd.Flags().IntVarP(&mg.Thread, "thread", "t", 8, "线程数")
Cmd.Flags().IntVarP(&mg.RedoRPO, "safe", "s", 1048576, "安全校验点")
Cmd.Flags().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")
@ -140,7 +142,9 @@ func Run(cmd *cobra.Command, args []string) {
starlog.Infoln("User Interrupted")
mg.fn()
time.Sleep(time.Second)
mg.Redo.Save()
if !mg.NoWriteRedo {
mg.Redo.Save()
}
os.Exit(3)
}
}

View File

@ -26,6 +26,7 @@ type Redo struct {
avgSpeed float64
total uint64
isRedo bool
lastCallSave time.Time
sync.RWMutex
}
@ -128,18 +129,20 @@ func (r *Redo) AverageSpeed() float64 {
}
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
}
r.Lock()
defer r.Unlock()
return os.WriteFile(r.Filename+".bgrd", data, 0644)
}
return nil

View File

@ -23,7 +23,10 @@ type Mget struct {
//本地文件大小
TargetSize int64
//redo文件最大丢数据量
RedoRPO int
RedoRPO int
NoWriteRedo bool
RedoAutoRpo bool
RedoMinSaveSec int
//单个buffer大小
BufferSize int
//并发下载线程数
@ -223,20 +226,22 @@ func (w *Mget) Run() error {
go w.Process()
w.wg.Wait()
time.Sleep(2 * time.Microsecond)
exitFn := sync.OnceFunc(w.fn)
var once sync.Once
for {
if w.writeEnable {
exitFn()
once.Do(w.fn)
time.Sleep(time.Millisecond * 50)
continue
}
if w.writeError != nil {
err = w.Redo.Save()
if !w.NoWriteRedo {
err = w.Redo.Save()
}
return fmt.Errorf("write error: %w %v", w.writeError, err)
}
break
}
exitFn()
once.Do(w.fn)
stario.WaitUntilTimeout(time.Second*2,
func(c chan struct{}) error {
for {
@ -252,10 +257,16 @@ func (w *Mget) Run() error {
if err != nil {
return err
}
if len(r) == 0 {
return os.Remove(w.Tareget + ".bgrd")
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 w.Redo.Save()
return nil
}
func (w *Mget) dispatch(idx int) error {
@ -393,6 +404,10 @@ func (w *Mget) WriteServer() error {
}
}
}
if w.RedoRPO == 0 {
w.RedoAutoRpo = true
w.RedoRPO = 1024 * 1024 * 1024
}
for {
select {
case <-w.ctx.Done():
@ -413,9 +428,12 @@ func (w *Mget) WriteServer() error {
if err != nil {
return err
}
if currentRange-lastUpdateRange >= w.RedoRPO {
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
}
}

View File

@ -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)
@ -103,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{
@ -217,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
View 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
View 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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,8 @@ import (
"fmt"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
"math/rand"
"net"
"os"
"strings"
@ -23,6 +25,9 @@ var (
curlUrl string
serverVersion string
curlArg []string
passwds []string
allowAny bool
logPath string
)
func init() {
@ -31,6 +36,10 @@ func init() {
cmdSSHJar.Flags().StringVarP(&KeyPasswd, "passwd", "p", "", "私钥密码")
cmdSSHJar.Flags().StringVarP(&outpath, "output", "o", "", "输出文件")
cmdSSHJar.Flags().StringVarP(&serverVersion, "version", "v", "SSH-2.0-OpenSSH_8.0", "SSH版本")
cmdSSHJar.Flags().StringVarP(&curlUrl, "curl", "c", "", "Curl URL")
cmdSSHJar.Flags().StringSliceVarP(&passwds, "allow-passwds", "P", nil, "密码列表,格式:[用户名]:[密码]")
cmdSSHJar.Flags().BoolVarP(&allowAny, "allow-any", "A", false, "允许任意密码登录")
cmdSSHJar.Flags().StringVarP(&logPath, "log", "L", "", "日志文件")
}
var cmdSSHJar = &cobra.Command{
@ -38,11 +47,41 @@ var cmdSSHJar = &cobra.Command{
Short: "SSH蜜罐",
Long: "SSH蜜罐",
Run: func(cmd *cobra.Command, args []string) {
runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, serverVersion)
var mypwds [][]string
for _, v := range passwds {
args := strings.SplitN(v, ":", 2)
if len(args) == 2 {
mypwds = append(mypwds, args)
}
}
runSSHHoneyJar(SSHJar{
listenAddr: listenAddr,
keyFile: keyFile,
keyPasswd: KeyPasswd,
outpath: outpath,
logpath: logPath,
version: serverVersion,
passwds: mypwds,
allowAny: allowAny,
})
},
}
func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
type SSHJar struct {
listenAddr string
keyFile string
keyPasswd string
outpath string
logpath string
version string
passwds [][]string
allowAny bool
}
func runSSHHoneyJar(jar SSHJar) {
if jar.logpath != "" {
starlog.SetLogFile(jar.logpath, starlog.Std, true)
}
var f *os.File
var err error
if outpath != "" {
@ -56,10 +95,10 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
defer f.Close()
defer conn.Flush()
config := &ssh.ServerConfig{
ServerVersion: version,
ServerVersion: jar.version,
// 密码验证回调函数
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
starlog.Infof("Login attempt from %s with %s %s\n", c.RemoteAddr(), c.User(), string(pass))
starlog.Infof("Login attempt from %s with %s by %s\n", c.RemoteAddr(), c.User(), string(pass))
data := []string{time.Now().Format("2006-01-02 15:04:05"), c.RemoteAddr().String(), c.User(), string(pass)}
if f != nil {
conn.Write(data)
@ -83,9 +122,30 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
}
}()
}
perm := &ssh.Permissions{
Extensions: map[string]string{
"user": c.User(),
"passwd": string(pass),
},
}
if jar.allowAny {
return perm, nil
}
for _, v := range jar.passwds {
if c.User() == v[0] && string(pass) == v[1] {
return perm, nil
}
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
if jar.allowAny {
return &ssh.Permissions{
Extensions: map[string]string{
"user": conn.User(),
},
}, nil
}
return nil, fmt.Errorf("public key rejected for %q", conn.User())
},
}
@ -132,9 +192,179 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
}
starlog.Infof("New connection from %s\n", conn.RemoteAddr())
go func(conn net.Conn) {
ssh.NewServerConn(conn, config)
conn.Close()
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
starlog.Errorf("SSH handshake failed: %v\n", err)
return
}
defer sConn.Close()
defer starlog.Noticef("Connection from %s closed\n", sConn.RemoteAddr())
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
starlog.Errorf("Failed to accept channel: %v", err)
continue
}
go handleSession(channel, requests, sConn)
}
}(conn)
}
}
func handleSession(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) {
defer channel.Close()
term := terminal.NewTerminal(channel, "$ ") // 设置 shell 提示符
term.AutoCompleteCallback = nil // 禁用自动补全
for req := range requests {
switch req.Type {
case "pty-req":
// 接受伪终端请求(攻击者希望获得交互式体验)
req.Reply(true, nil)
term.SetSize( // 简单设置终端尺寸
24, // 行
80, // 列
)
case "shell":
req.Reply(true, nil)
go func() {
for {
line, err := term.ReadLine()
if err != nil {
break
}
starlog.Infof("[%s %s] Command: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], line)
time.Sleep(time.Millisecond * 200)
term.Write([]byte(FakeCommand(line)))
}
}()
case "exec":
// 处理非交互式命令(如 ssh user@host 'ls -l'
var payload struct{ Command string }
ssh.Unmarshal(req.Payload, &payload)
req.Reply(true, nil)
// 记录并返回假输出
starlog.Infof("[%s %s] Exec: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], payload.Command)
term.Write([]byte(FakeCommand(payload.Command)))
channel.Close()
default:
req.Reply(false, nil)
}
}
}
func FakeCommand(cmd string) string {
// 按命令类型分级模拟
switch {
//---------------- 系统信息探测类 ----------------
case strings.Contains(cmd, "uname -a"):
return "Linux core 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64 GNU/Linux\n\n"
case strings.Contains(cmd, "cat /etc/os-release"):
return `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
`
case strings.Contains(cmd, "free -h"):
return ` total used free shared buff/cache available
Mem: 1022Gi 12Gi 3.0Gi 1.0Gi 47Gi 480Gi
Swap: 0B 0B 0B
`
//---------------- 敏感文件诱导类 ----------------
case strings.Contains(cmd, "ls /home"):
return "admin backup devops secret\n"
case strings.Contains(cmd, "ls /var/log"):
return `auth.log apache2 payment_system.log database_backup.log
`
case strings.Contains(cmd, "ls"):
return "password.txt\n"
case strings.Contains(cmd, "cat /etc/passwd"):
return `root:x:0:0:root:/root:/bin/bash
admin:x:1000:1000:,,,:/home/admin:/bin/bash
mysql:x:106:113:MySQL Server,,,:/nonexistent:/bin/false
core:x:0:0:,,,:/root:/bin/bash
`
//---------------- 网络配置诱导类 ----------------
case strings.Contains(cmd, "ifconfig") || strings.Contains(cmd, "ip addr"):
return `eth0: flags=4163<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"
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -52,6 +52,7 @@ func Traceroute(address string, bindaddr string, dns string, maxHops int, timeou
}
traceroute(address, bindaddr, maxHops, timeout, ipinfoAddr, hideIncorrect)
}
func traceroute(address string, bindaddr string, maxHops int, timeout time.Duration, ipinfoAddr string, hideIncorrect bool) {
ipinfo := net.ParseIP(address)
if ipinfo == nil {
@ -59,26 +60,46 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat
return
}
var echoType icmp.Type = ipv4.ICMPTypeEcho
var exceededType icmp.Type = ipv4.ICMPTypeTimeExceeded
var replyType icmp.Type = ipv4.ICMPTypeEchoReply
var proto = 1
var network = "ip4:icmp"
var resolveIP = "ip4"
if ipinfo.To4() == nil {
network = "ip6:ipv6-icmp"
resolveIP = "ip6"
var (
echoType icmp.Type
exceededType icmp.Type
replyType icmp.Type
unreachType icmp.Type
proto int
network string
resolveIP string
isIPv4 bool
)
if ipinfo.To4() != nil {
echoType = ipv4.ICMPTypeEcho
exceededType = ipv4.ICMPTypeTimeExceeded
replyType = ipv4.ICMPTypeEchoReply
unreachType = ipv4.ICMPTypeDestinationUnreachable
proto = 1
network = "ip4:icmp"
resolveIP = "ip4"
isIPv4 = true
} else {
echoType = ipv6.ICMPTypeEchoRequest
exceededType = ipv6.ICMPTypeTimeExceeded
replyType = ipv6.ICMPTypeEchoReply
unreachType = ipv6.ICMPTypeDestinationUnreachable
proto = 58
network = "ip6:ipv6-icmp"
resolveIP = "ip6"
isIPv4 = false
}
if bindaddr == "" {
bindaddr = "0.0.0.0"
if !isIPv4 {
bindaddr = "::"
}
}
c, err := icmp.ListenPacket(network, bindaddr)
if err != nil {
fmt.Println(err)
starlog.Errorln("监听失败:", err)
return
}
defer c.Close()
@ -90,121 +111,179 @@ func traceroute(address string, bindaddr string, maxHops int, timeout time.Durat
if timeout == 0 {
timeout = time.Second * 3
}
exitfor:
for i := 1; i <= maxHops; i++ {
retry := 0
doRetry:
for ttl := 1; ttl <= maxHops; ttl++ {
if atomic.LoadInt32(&firstTargetHop) <= int32(ttl) {
return
}
dst, err := net.ResolveIPAddr(resolveIP, address)
if err != nil {
starlog.Errorln("IP地址解析失败", address, err)
starlog.Errorln("解析失败:", address, err)
return
}
if atomic.LoadInt32(&firstTargetHop) <= int32(i) {
return
}
m := icmp.Message{
// 构造ICMP报文
msg := icmp.Message{
Type: echoType, Code: 0,
Body: &icmp.Echo{
ID: i, Seq: i,
ID: ttl, // 使用TTL作为ID
Seq: ttl, // 使用TTL作为序列号
Data: []byte("B612.ME-ROUTER-TRACE"),
},
}
b, err := m.Marshal(nil)
msgBytes, err := msg.Marshal(nil)
if err != nil {
fmt.Printf("%d\tMarshal error: %v\n", i, err)
starlog.Warningf("%d\t封包失败: %v\n", ttl, err)
continue
}
// 设置TTL/HopLimit
if network == "ip4:icmp" {
if err := c.IPv4PacketConn().SetTTL(i); err != nil {
fmt.Printf("%d\tSetTTL error: %v\n", i, err)
if err := c.IPv4PacketConn().SetTTL(ttl); err != nil {
starlog.Warningf("%d\t设置TTL失败: %v\n", ttl, err)
continue
}
} else {
if err := c.IPv6PacketConn().SetHopLimit(i); err != nil {
fmt.Printf("%d\tSetHopLimit error: %v\n", i, err)
if err := c.IPv6PacketConn().SetHopLimit(ttl); err != nil {
starlog.Warningf("%d\t设置HopLimit失败: %v\n", ttl, err)
continue
}
}
start := time.Now()
n, err := c.WriteTo(b, dst)
if err != nil {
fmt.Printf("%d\tWriteTo error: %v\n", i, err)
continue
} else if n != len(b) {
fmt.Printf("%d\tWrite Short: %v Expected: %v\n", i, n, len(b))
startTime := time.Now()
if _, err := c.WriteTo(msgBytes, dst); err != nil {
starlog.Warningf("%d\t发送失败: %v\n", ttl, err)
continue
}
now := time.Now()
exitrecheck:
for {
reply := make([]byte, 1500)
err = c.SetReadDeadline(time.Now().Add(timeout))
if err != nil {
fmt.Printf("%d\tSetReadDeadline error: %v\n", i, err)
break
}
n, peer, err := c.ReadFrom(reply)
if err != nil {
fmt.Printf("%d\tReadFrom error: %v\n", i, err)
break
}
duration := time.Since(start)
// 接收响应处理
timeoutCh := time.After(timeout)
responsesReceived := 0
rm, err := icmp.ParseMessage(proto, reply[:n])
if err != nil {
fmt.Printf("%d\tParseMessage error: %v\n", i, err)
break
}
switch rm.Type {
case exceededType:
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
break exitrecheck
case replyType:
fmt.Printf("%d\thops away:\t%s\t(%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
if peer.String() == dst.String() {
break exitfor
}
case ipv4.ICMPTypeEcho, ipv6.ICMPTypeEchoRequest:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
}
if !hideIncorrect {
fmt.Printf("%d\tInvalid Echo Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
}
break exitrecheck
}
case ipv4.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeDestinationUnreachable:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
}
if !hideIncorrect {
fmt.Printf("%d\tInvalid DstInv Request:%s (%s) %s\n", i, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
}
break exitrecheck
recvLoop:
for responsesReceived < 3 {
select {
case <-timeoutCh:
if responsesReceived == 0 {
fmt.Printf("%d\t*\n", ttl)
}
break recvLoop
default:
if time.Now().Sub(now).Seconds() > timeout.Seconds() {
if retry < 1 {
retry++
goto doRetry
reply := make([]byte, 1500)
if err := c.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil {
break recvLoop
}
n, peer, err := c.ReadFrom(reply)
if err != nil {
if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
continue
}
if !hideIncorrect {
fmt.Printf("%d\tgot %+v from %v (%s) %s\n", i, rm.Type, peer, duration, GetIPInfo(peer.String(), ipinfoAddr))
starlog.Debugf("%d\t接收错误: %v", ttl, err)
continue
}
// 解析响应
rm, err := icmp.ParseMessage(proto, reply[:n])
if err != nil {
starlog.Debugf("%d\t解析错误: %v", ttl, err)
continue
}
// 验证响应匹配
if match := checkResponseMatch(rm, ttl, peer.String() == dst.String(), isIPv4, exceededType, replyType, unreachType); match {
duration := time.Since(startTime)
fmt.Printf("%d\t%s\t%s\t%s\n",
ttl,
peer,
duration.Round(time.Millisecond),
GetIPInfo(peer.String(), ipinfoAddr),
)
responsesReceived++
if peer.String() == dst.String() {
atomic.StoreInt32(&firstTargetHop, int32(ttl))
break exitfor
}
break exitrecheck
}
}
}
}
}
func checkResponseMatch(rm *icmp.Message, ttl int, isFinal bool, isIPv4 bool,
exceededType, replyType, unreachType icmp.Type) bool {
switch {
case rm.Type == exceededType:
if body, ok := rm.Body.(*icmp.TimeExceeded); ok {
return validateOriginalPacket(body.Data, ttl, isIPv4)
}
case rm.Type == replyType:
if isFinal {
if body, ok := rm.Body.(*icmp.Echo); ok {
return body.ID == ttl && body.Seq == ttl
}
}
return false
case rm.Type == unreachType:
if body, ok := rm.Body.(*icmp.DstUnreach); ok {
return validateOriginalPacket(body.Data, ttl, isIPv4)
}
}
return false
}
func validateOriginalPacket(data []byte, ttl int, isIPv4 bool) bool {
var (
proto byte
header []byte
)
if isIPv4 {
if len(data) < 20+8 {
return false
}
ihl := data[0] & 0x0F
if ihl < 5 {
return false
}
proto = data[9]
header = data[:ihl*4]
} else {
if len(data) < 40+8 {
return false
}
proto = data[6]
header = data[:40]
}
if proto != 1 && proto != 58 {
return false
}
payload := data[len(header):]
if len(payload) < 8 {
return false
}
if isIPv4 {
return payload[0] == 8 && // ICMP Echo Request
payload[4] == byte(ttl>>8) &&
payload[5] == byte(ttl) &&
payload[6] == byte(ttl>>8) &&
payload[7] == byte(ttl)
} else {
return payload[0] == 128 && // ICMPv6 Echo Request
payload[4] == byte(ttl>>8) &&
payload[5] == byte(ttl) &&
payload[6] == byte(ttl>>8) &&
payload[7] == byte(ttl)
}
}
@ -213,16 +292,23 @@ func GetIPInfo(ip string, addr string) string {
return ""
}
uri := strings.ReplaceAll(addr, "{ip}", ip)
res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET", starnet.WithTimeout(time.Second*2), starnet.WithDialTimeout(time.Second*3)))
res, err := starnet.Curl(starnet.NewSimpleRequest(uri, "GET",
starnet.WithTimeout(time.Second*2),
starnet.WithDialTimeout(time.Second*3)))
if err != nil {
return "获取IP信息失败" + err.Error()
return "IP信息获取失败"
}
var ipinfo IPInfo
err = res.Body().Unmarshal(&ipinfo)
if err != nil {
return "解析IP信息失败" + err.Error()
if err := res.Body().Unmarshal(&ipinfo); err != nil {
return "IP信息解析失败"
}
return fmt.Sprintf("%s %s %s %s %s", ipinfo.CountryName, ipinfo.RegionName, ipinfo.CityName, ipinfo.OwnerDomain, ipinfo.ISP)
return fmt.Sprintf("%s %s %s",
ipinfo.CountryName,
ipinfo.RegionName,
ipinfo.ISP)
}
type IPInfo struct {

View File

@ -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
View 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
View 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
View 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
View 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
View 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=目标端单向RSTreverse=对端反向RSTboth=双向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
View 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=目标端单向RSTreverse=对端反向RSTboth=双向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, &eth, &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, &eth, &iPv6, &tcp); err != nil {
return err
}
}
return p.WritePacketData(buffer.Bytes())
}

41
tcpkill/cmd.go Normal file
View 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", "", "匹配的连接状态如ESTABLISHEDCLOSE_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
View 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
View 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
View 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
View 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, &eth, &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, &eth, &iPv6, &tcp); err != nil {
return err
}
}
return p.WritePacketData(buffer.Bytes())
}

View File

@ -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
View File

@ -0,0 +1,3 @@
package version
var Version = "2.1.0.beta.17"