Compare commits

...

8 Commits

Author SHA1 Message Date
1879810c9e update 2025-03-25 10:00:26 +08:00
98f50488ca update go.mod 2025-01-23 21:47:55 +08:00
98ed1980ff update 2025-01-17 20:11:08 +08:00
32c6e7b534 update image and tcp 2024-12-31 10:21:15 +08:00
5b0b3834bb 1.add tcp kill/mon functions 2.support win 7 again 3.fix tcp keepalive period too fast on windows 4.other bug fix 2024-12-18 17:17:54 +08:00
8845d35339 mget bug fix 2024-11-08 22:22:59 +08:00
ff8a3470c7 bug fix 2024-11-02 19:59:36 +08:00
4fd9c37944 update 2024-09-15 15:27:50 +08:00
65 changed files with 10821 additions and 567 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

@ -50,3 +50,15 @@ func LoadCA(caKeyPath, caCertPath, KeyPwd string) (crypto.PrivateKey, *x509.Cert
}
return caKey, cert, nil
}
func LoadPriv(caKeyPath, KeyPwd string) (crypto.PrivateKey, error) {
caKeyBytes, err := os.ReadFile(caKeyPath)
if err != nil {
return nil, err
}
caKey, err := starcrypto.DecodePrivateKey(caKeyBytes, KeyPwd)
if err != nil {
return nil, err
}
return caKey, nil
}

View File

@ -15,6 +15,7 @@ import (
"fmt"
"golang.org/x/crypto/ssh"
"os"
"reflect"
"software.sslmate.com/src/go-pkcs12"
"strings"
)
@ -104,9 +105,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -115,8 +116,8 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("私钥系数qInv%d\n", n.Precomputed.Qinv)
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("钥位数:%d\n", n.Curve.Params().BitSize)
starlog.Green("钥曲线:%s\n", n.Curve.Params().Name)
starlog.Green("私钥长度:%d\n", n.Params().BitSize)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥公钥X%d\n", n.PublicKey.X)
@ -125,7 +126,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y)
case *ed25519.PrivateKey:
case *ed25519.PrivateKey, ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
@ -208,7 +209,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥公钥Y%d\n", n.Y)
case *ecdh.PublicKey:
starlog.Green("公钥算法为ECDH\n")
case *ed25519.PublicKey:
case *ed25519.PublicKey, ed25519.PublicKey:
starlog.Green("公钥算法为ED25519\n")
default:
starlog.Green("未知公钥类型\n")
@ -236,9 +237,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -257,12 +258,18 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y)
case ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
starlog.Green("公钥:%s\n", string(sshPub))
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
starlog.Green("公钥:%s\n", string(sshPub))
case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
}
continue
@ -371,9 +378,9 @@ func ParseCert(data []byte, pwd string) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥指数:%d\n", n.E)
starlog.Green("私钥系数:%d\n", n.D)
starlog.Green("私钥质数p%d\n", n.Primes[0])
starlog.Green("私钥质数q%d\n", n.Primes[1])
@ -392,12 +399,18 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("这是一个DSA私钥\n")
starlog.Green("私钥系数:%d\n", n.X)
starlog.Green("私钥公钥Y%d\n", n.Y)
case ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
starlog.Green("公钥:%s\n", string(sshPub))
case *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
sshPub, _ := starcrypto.EncodeSSHPublicKey(n.Public())
starlog.Green("公钥:%s\n", string(sshPub))
case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
starlog.Infof("不支持的私钥类型:%v\n", reflect.TypeOf(n))
}
continue
case "OPENSSH PUBLIC KEY":
@ -410,7 +423,7 @@ func ParseCert(data []byte, pwd string) {
starlog.Green("公钥算法:%s\n", pub.Type())
continue
default:
starlog.Infof("未知证书文件类型\n")
starlog.Infof("不支持的证书文件类型:%v\n", reflect.TypeOf(block))
}
}
}
@ -546,9 +559,9 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey:
case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
case ecdh.PrivateKey, *ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
@ -627,15 +640,15 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
switch n := priv.(type) {
case *rsa.PrivateKey:
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥位数:%d\n", n.Size())
case *ecdsa.PrivateKey:
starlog.Green("这是一个ECDSA私钥\n")
starlog.Green("私钥位数:%d\n", n.Curve.Params().BitSize)
case *dsa.PrivateKey:
starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey:
case ed25519.PrivateKey, *ed25519.PrivateKey:
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
case ecdh.PrivateKey:
starlog.Green("这是一个ECDH私钥\n")
default:
starlog.Green("未知私钥类型\n")
@ -747,8 +760,8 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
case *rsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个RSA私钥\n")
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
starlog.Green("钥位数:%d\n", n.Size())
starlog.Green("钥长度:%d\n", n.N.BitLen())
case *ecdsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDSA私钥\n")
@ -756,10 +769,10 @@ func GetCert(data []byte, pwd string) ([]any, []x509.Certificate, error) {
case *dsa.PrivateKey:
common = append(common, n)
starlog.Green("这是一个DSA私钥\n")
case *ed25519.PrivateKey:
case ed25519.PrivateKey, *ed25519.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ED25519私钥\n")
case *ecdh.PrivateKey:
case ecdh.PrivateKey:
common = append(common, n)
starlog.Green("这是一个ECDH私钥\n")
default:
@ -777,28 +790,42 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
if err != nil {
return err
}
fmt.Println(len(keys))
for _, v := range keys {
if v == nil {
continue
}
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n)
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, *ed25519.PrivateKey, ecdh.PrivateKey:
var key interface{} = n
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
fmt.Println("1")
key = *(n.(*ed25519.PrivateKey))
}
fmt.Println("2")
data, err = x509.MarshalPKCS8PrivateKey(key)
if err != nil {
return err
}
fmt.Println("3")
var block *pem.Block
if newPwd != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, "PRIVATE KEY", data, []byte(newPwd), x509.PEMCipherAES256)
if err != nil {
return err
}
} else {
block = &pem.Block{Type: "PRIVATE KEY", Bytes: data}
}
fmt.Println("4")
err = os.WriteFile(outpath+"/"+originName+".pkcs8", pem.EncodeToMemory(block), 0644)
if err != nil {
fmt.Println("5")
return err
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".pkcs8")
}
fmt.Println("6")
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
@ -810,6 +837,8 @@ func Pkcs8(data []byte, pwd, newPwd string, originName string, outpath string) e
} else {
starlog.Green("已将公钥保存到%s\n", outpath+"/"+originName+".pub.pkcs8")
}
default:
return fmt.Errorf("未知的密钥类型:%v", reflect.TypeOf(n))
}
}
return nil
@ -865,8 +894,11 @@ func Pkcs12(keys []any, certs []x509.Certificate, enPwd string, originName strin
continue
}
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
priv = n
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
priv = *(n.(*ed25519.PrivateKey))
}
break
}
}
@ -927,7 +959,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else {
starlog.Green("已将公钥保存到%s\n", fmt.Sprintf("%s/%s_%v.tran.pub", outpath, originName, idx))
}
case *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
case *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey:
data, err = x509.MarshalPKCS8PrivateKey(n)
if err != nil {
return err
@ -938,7 +970,7 @@ func Tran(data []byte, pwd string, originName string, outpath string) error {
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".tran.key")
}
case *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
case *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
data, err = x509.MarshalPKIXPublicKey(n)
if err != nil {
return err
@ -966,11 +998,15 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
}
var block *pem.Block
switch n := v.(type) {
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
if newPwd != "" {
block, err = ssh.MarshalPrivateKey(n, "")
case *ecdsa.PrivateKey, *rsa.PrivateKey, *dsa.PrivateKey, ed25519.PrivateKey, ecdh.PrivateKey, *ed25519.PrivateKey, *ecdh.PrivateKey:
var key interface{} = n
if reflect.TypeOf(n) == reflect.TypeOf(&ed25519.PrivateKey{}) {
key = *(n.(*ed25519.PrivateKey))
}
if newPwd == "" {
block, err = ssh.MarshalPrivateKey(key, "")
} else {
block, err = ssh.MarshalPrivateKeyWithPassphrase(n, "", []byte(newPwd))
block, err = ssh.MarshalPrivateKeyWithPassphrase(key, "", []byte(newPwd))
}
if err != nil {
return err
@ -981,7 +1017,7 @@ func Openssh(data []byte, pwd, newPwd string, originName string, outpath string)
} else {
starlog.Green("已将私钥保存到%s\n", outpath+"/"+originName+".openssh")
}
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, *ed25519.PublicKey, *ecdh.PublicKey:
case *ecdsa.PublicKey, *rsa.PublicKey, *dsa.PublicKey, ed25519.PublicKey, ecdh.PublicKey:
sk, err := ssh.NewPublicKey(n)
if err != nil {
return err

View File

@ -4,6 +4,7 @@ import (
"b612.me/starcrypto"
"b612.me/stario"
"b612.me/starlog"
"crypto"
"crypto/x509"
"fmt"
"github.com/spf13/cobra"
@ -64,12 +65,6 @@ var CmdCsr = &cobra.Command{
if dnsName == nil {
dnsName = stario.MessageBox("请输入dns名称用逗号分割", "").MustSliceString(",")
}
if startStr == "" {
startStr = stario.MessageBox("请输入开始时间:", "").MustString()
}
if endStr == "" {
endStr = stario.MessageBox("请输入结束时间:", "").MustString()
}
}
start, err = time.Parse(time.RFC3339, startStr)
if err != nil {
@ -81,7 +76,12 @@ var CmdCsr = &cobra.Command{
starlog.Errorln("结束时间格式错误,格式:2006-01-02T15:04:05Z07:00", err)
os.Exit(1)
}
csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName, start, end, isCa, maxPathLenZero, maxPathLen))
key, err := LoadPriv(caKey, caKeyPwd)
if err != nil {
starlog.Errorln("加载Key错误", err)
os.Exit(1)
}
csr := outputCsr(GenerateCsr(country, province, city, org, orgUnit, name, dnsName), key)
err = os.WriteFile(savefolder+"/"+name+".csr", csr, 0644)
if err != nil {
starlog.Errorln("保存csr文件错误", err)
@ -112,10 +112,21 @@ var CmdGen = &cobra.Command{
starlog.Errorln("证书公钥不能为空")
os.Exit(1)
}
caKeyRaw, caCertRaw, err := LoadCA(caKey, caCert, caKeyPwd)
if err != nil {
starlog.Errorln("加载CA错误", err)
os.Exit(1)
var caKeyRaw crypto.PrivateKey
var caCertRaw *x509.Certificate
var err error
if !isCa {
caKeyRaw, caCertRaw, err = LoadCA(caKey, caCert, caKeyPwd)
if err != nil {
starlog.Errorln("加载CA错误", err)
os.Exit(1)
}
} else {
caKeyRaw, err = LoadPriv(caKey, caKeyPwd)
if err != nil {
starlog.Errorln("加载CA错误", err)
os.Exit(1)
}
}
csrRaw, err := LoadCsr(csr)
if err != nil {
@ -132,7 +143,18 @@ var CmdGen = &cobra.Command{
starlog.Errorln("解析公钥错误", err)
os.Exit(1)
}
cert, err := MakeCert(caKeyRaw, caCertRaw, csrRaw, pubKeyRaw)
certReq := &x509.Certificate{
Subject: csrRaw.Subject,
IsCA: isCa,
NotBefore: start,
NotAfter: end,
MaxPathLen: maxPathLen,
MaxPathLenZero: maxPathLenZero,
}
if isCa {
caCertRaw = certReq
}
cert, err := MakeCert(caKeyRaw, caCertRaw, certReq, pubKeyRaw)
if err != nil {
starlog.Errorln("生成证书错误", err)
os.Exit(1)
@ -171,19 +193,21 @@ var CmdParse = &cobra.Command{
func init() {
Cmd.AddCommand(CmdCsr)
CmdCsr.Flags().BoolVarP(&promptMode, "prompt", "P", false, "是否交互模式")
CmdCsr.Flags().StringVarP(&country, "country", "c", "", "国家")
CmdCsr.Flags().StringVarP(&province, "province", "p", "", "省份")
CmdCsr.Flags().StringVarP(&city, "city", "t", "", "城市")
CmdCsr.Flags().StringVarP(&country, "country", "c", "CN", "国家")
CmdCsr.Flags().StringVarP(&province, "province", "p", "B612", "省份")
CmdCsr.Flags().StringVarP(&city, "city", "t", "B612", "城市")
CmdCsr.Flags().StringVarP(&org, "org", "o", "", "组织")
CmdCsr.Flags().StringVarP(&orgUnit, "orgUnit", "u", "", "组织单位")
CmdCsr.Flags().StringVarP(&name, "name", "n", "", "通用名称")
CmdCsr.Flags().StringVarP(&name, "name", "n", "Starainrt", "通用名称")
CmdCsr.Flags().StringSliceVarP(&dnsName, "dnsName", "d", nil, "dns名称")
CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
CmdCsr.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
CmdCsr.Flags().StringVarP(&caKey, "secret-key", "k", "", "加密私钥")
CmdCsr.Flags().StringVarP(&caKeyPwd, "secret-key-passwd", "K", "", "加密私钥的密码")
//CmdCsr.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
//CmdCsr.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
//CmdCsr.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
//CmdCsr.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
//CmdCsr.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
CmdGen.Flags().StringVarP(&caKey, "caKey", "k", "", "CA私钥")
CmdGen.Flags().StringVarP(&caCert, "caCert", "C", "", "CA证书")
@ -191,6 +215,11 @@ func init() {
CmdGen.Flags().StringVarP(&pubKey, "pubKey", "P", "", "证书公钥")
CmdGen.Flags().StringVarP(&savefolder, "savefolder", "s", "./", "保存文件夹")
CmdGen.Flags().StringVarP(&caKeyPwd, "caKeyPwd", "p", "", "CA私钥密码")
CmdGen.Flags().BoolVarP(&isCa, "isCa", "A", false, "是否是CA")
CmdGen.Flags().StringVarP(&startStr, "start", "S", time.Now().Format(time.RFC3339), "开始时间,格式:2006-01-02T15:04:05Z07:00")
CmdGen.Flags().StringVarP(&endStr, "end", "E", time.Now().AddDate(1, 0, 0).Format(time.RFC3339), "结束时间,格式:2006-01-02T15:04:05Z07:00")
CmdGen.Flags().BoolVarP(&maxPathLenZero, "maxPathLenZero", "z", false, "允许最大路径长度为0")
CmdGen.Flags().IntVarP(&maxPathLen, "maxPathLen", "m", 0, "最大路径长度")
Cmd.AddCommand(CmdGen)
CmdParse.Flags().StringVarP(&passwd, "passwd", "p", "", "pfx解密密码")

View File

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

View File

@ -151,6 +151,7 @@ func (c *DoHClient) Exchange(req *dns.Msg, address string) (r *dns.Msg, rtt time
// No need to use hreq.URL.Query()
hreq, _ := http.NewRequest("GET", address+"?dns="+string(b64), nil)
hreq.Header.Set("User-Agent", "B612 DoH Client")
hreq.Header.Add("Accept", DoHMediaType)
resp, err := c.cli.Do(hreq)
if err != nil {

87
go.mod
View File

@ -1,52 +1,101 @@
module b612.me/apps/b612
go 1.21
toolchain go1.21.2
go 1.20
require (
b612.me/notify v1.2.5
b612.me/astro v0.0.4
b612.me/bcap v0.0.4
b612.me/notify v0.0.0-20240818092352-85803f75dfa0
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991
b612.me/starcrypto v0.0.5
b612.me/stario v0.0.9
b612.me/starlog v1.3.3
b612.me/starnet v0.1.8
b612.me/staros v1.1.7
b612.me/stario v0.0.10
b612.me/starlog v1.3.4
b612.me/starmap v0.0.0-20240818092703-ae61140c5062
b612.me/starnet v0.2.1
b612.me/staros v1.1.8
b612.me/starssh v0.0.2
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd
b612.me/wincmd v0.0.3
b612.me/wincmd v0.0.4
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2
github.com/emersion/go-smtp v0.20.2
github.com/florianl/go-nfqueue/v2 v2.0.0
github.com/gdamore/tcell/v2 v2.7.1
github.com/go-acme/lego/v4 v4.16.1
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/gopacket v1.1.19
github.com/huin/goupnp v1.3.0
github.com/inconshreveable/mousetrap v1.1.0
github.com/likexian/whois v1.15.1
github.com/miekg/dns v1.1.58
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57
github.com/shirou/gopsutil/v4 v4.24.10
github.com/spf13/cobra v1.8.0
github.com/things-go/go-socks5 v0.0.5
golang.org/x/crypto v0.21.0
golang.org/x/net v0.21.0
github.com/vbauerster/mpb/v8 v8.8.3
golang.org/x/crypto v0.26.0
golang.org/x/net v0.28.0
golang.org/x/sys v0.26.0
golang.org/x/term v0.23.0
software.sslmate.com/src/go-pkcs12 v0.4.0
)
require (
b612.me/starmap v1.2.4 // indirect
b612.me/win32api v0.0.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 // indirect
github.com/VividCortex/ewma v1.2.0 // indirect
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/cloudflare/cloudflare-go v0.86.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/ebitengine/purego v0.8.1 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/gdamore/encoding v1.0.0 // indirect
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
github.com/jlaffaye/ftp v0.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kr/fs v0.1.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/sftp v1.13.4 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/image v0.6.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/tools v0.17.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

231
go.sum
View File

@ -1,31 +1,66 @@
b612.me/notify v1.2.5 h1:fASpzi8YAo78g6jKnefzfbsQz0nGNYFbClB2Bylj+MA=
b612.me/notify v1.2.5/go.mod h1:GTnAdC6v9krGxtC8Gkn8TcyUsYnHSiHjRAXsONPiLpI=
b612.me/starcrypto v0.0.3/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/astro v0.0.4 h1:i60vJ3Dq+9U16ryMqmnjE0uXB28poRBx8boIMbP8VH8=
b612.me/astro v0.0.4/go.mod h1:yC1arIa79Q5ztQgWphj5mX5UvM/gddwTP+jGneojPt4=
b612.me/bcap v0.0.4 h1:iY2Oz+uyG/mue6a/dJiU82ci5Xwkj4xHhre/q0O8G60=
b612.me/bcap v0.0.4/go.mod h1:tpus+4iMpsnxb98Pck70s87Zt4sIWuRZjK23MmmzmoY=
b612.me/notify v0.0.0-20240818092352-85803f75dfa0 h1:PaLuNLMM0HBM7qPdk4igtNvqT2CDgdm52sL+KA9I3so=
b612.me/notify v0.0.0-20240818092352-85803f75dfa0/go.mod h1:YyaYF4jj5ygIC/QbCMyhBWnsmmTL8kVs6UlHMGVKiY8=
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991 h1:+eXeVqkoi4s9sNoY9eGt4ieSbr1+Deos8fC8wMfOCNI=
b612.me/sdk/whois v0.0.0-20240816133027-129514a15991/go.mod h1:PB9QpUoQEip0MB3st8H5hmnDTcDsR0RGV0BfpUr5XDg=
b612.me/starcrypto v0.0.5 h1:Aa4pRDO2lBH2Aw+vz8NuUtRb73J8z5aOa9SImBY5sq4=
b612.me/starcrypto v0.0.5/go.mod h1:pF5A16p8r/h1G0x7ZNmmAF6K1sdIMpbCUxn2WGC8gZ0=
b612.me/stario v0.0.9 h1:bFDlejUJMwZ12a09snZJspQsOlkqpDAl9qKPEYOGWCk=
b612.me/stario v0.0.9/go.mod h1:x4D/x8zA5SC0pj/uJAi4FyG5p4j5UZoMEZfvuRR6VNw=
b612.me/starlog v1.3.3 h1:xYCHouOTpo6dsFg2A92TqTznxvRPPS/ovMWs7CJZ9WI=
b612.me/starlog v1.3.3/go.mod h1:h928hRahvWqcXXxy0uKWZ+oFe3K7kFQDHKiBemedLyE=
b612.me/starmap v1.2.4 h1:gfAyBtzW3KKCIyI14I2pEqGsR/u2E+3tkH0xRqtWb4E=
b612.me/starmap v1.2.4/go.mod h1:EhOUzkItc5IcyBmr1C7/vmZBbW3GgCWs63hGn7WhuMc=
b612.me/starnet v0.1.8 h1:sTNytUFP38i2BFR9nha3lTSLYb7El3tvKpZplYCrhZk=
b612.me/starnet v0.1.8/go.mod h1:k862Kf8DiVWTqdX6PHTFb6NoT+3G3Y74n8NCyNhuP0Y=
b612.me/staros v1.1.7 h1:GkQp5sBPRqo3pOh6nKyKffJydyYrjlfzpsPxNeVJ26g=
b612.me/staros v1.1.7/go.mod h1:Yi/WfvIqRAPQEf/eiaaIwrL5LNcUbqzMIuFIyJJOU40=
b612.me/stario v0.0.0-20240818091810-d528a583f4b2/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk=
b612.me/stario v0.0.10 h1:+cIyiDCBCjUfodMJDp4FLs+2E1jo7YENkN+sMEe6550=
b612.me/stario v0.0.10/go.mod h1:1Owmu9jzKWgs4VsmeI8YWlGwLrCwPNM/bYpxkyn+MMk=
b612.me/starlog v1.3.4 h1:XuVYo6NCij8F4TGSgtEuMhs1WkZ7HZNnYUgQ3nLTt84=
b612.me/starlog v1.3.4/go.mod h1:37GMgkWQMOAjzKs49Hf2i8bLwdXbd9QF4zKhUxFDoSk=
b612.me/starmap v0.0.0-20240818092703-ae61140c5062 h1:ImKEWAxzBYsS/YbqdVOPdUdv6b+i/lSGpipUGueXk7w=
b612.me/starmap v0.0.0-20240818092703-ae61140c5062/go.mod h1:PhtO9wFrwPIHpry2CEdnVNZkrNOgfv77xrE0ZKQDkLM=
b612.me/starnet v0.2.1 h1:17n3wa2QgBYbO1rqDLAhyc2DfvbBc23GSp1v42Pvmiw=
b612.me/starnet v0.2.1/go.mod h1:6q+AXhYeXsIiKV+hZZmqAMn8S48QcdonURJyH66rbzI=
b612.me/staros v1.1.8 h1:5Bpuf9q2nH75S2ekmieJuH3Y8LTqg/voxXCOiMAC3kk=
b612.me/staros v1.1.8/go.mod h1:4KmokjKXFW5h1hbA4aIv5O+2FptVzBubCo7IPirfqm8=
b612.me/starssh v0.0.2 h1:cYlrXjd7ZTesdZG+7XcoLsEEMROaeWMTYonScBLnvyY=
b612.me/starssh v0.0.2/go.mod h1:1gvG/GT5Y5EvOx9ZKnLFUa+wOX20HaqS1IuTnU7BOlk=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd h1:EsmnczYZhOV8JTxD/m0N0qBjfZN8JuLNrTJ6z3S8YqA=
b612.me/startext v0.0.0-20220314043758-22c6d5e5b1cd/go.mod h1:yKdeLQHZ3scqyjw1ZODCoL+hLmkOp2eu5riP4agraz8=
b612.me/win32api v0.0.2 h1:5PwvPR5fYs3a/v+LjYdtRif+5Q04zRGLTVxmCYNjCpA=
b612.me/win32api v0.0.2/go.mod h1:sj66sFJDKElEjOR+0YhdSW6b4kq4jsXu4T5/Hnpyot0=
b612.me/wincmd v0.0.3 h1:GYrkYnNun39yfNcA2+u0h4VW/BYbTrJK39QW4W1LCYA=
b612.me/wincmd v0.0.3/go.mod h1:nWdNREHO6F+2PngEUcyYN3Eo7DzYEVa/fO6czd9d/fo=
b612.me/wincmd v0.0.4 h1:fv9p1V8mw2HdUjaoZBWZy0T41JftueyLxAuch1MgtdI=
b612.me/wincmd v0.0.4/go.mod h1:o3yPoE+DpVPHGKl/q1WT1C8OaIVwHEnpeNgMFqzlwD8=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0 h1:8kDqDngH+DmVBiCtIjCFTGa7MBnsIOkF9IccInFEbjk=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.6.0/go.mod h1:bjGvMhVMb+EEm3VRNQawDMUyMMjo+S5ewNjflkep/0Q=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0/go.mod h1:OQeznEEkTZ9OrhHJoDD8ZDq51FHgXjqtP9z6bEwBq9U=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0/go.mod h1:okt5dMMTOFjX/aovMlrjvvXoPMBVSPzk9185BT0+eZM=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0 h1:8iR6OLffWWorFdzL2JFCab5xpD8VKEE2DUBBl+HNTDY=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.1.0/go.mod h1:copqlcjMWc/wgQ1N2fzsJFQxDdqKGg1EQt8T5wJMOGE=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0 h1:rR8ZW79lE/ppfXTfiYSnMFv5EzmVuY4pfZWIkscIJ64=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.1.0/go.mod h1:y2zXtLSMM/X5Mfawq0lOftpWn3f4V6OCsRdINsvWBPI=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0 h1:OBhqkivkhkMqLPymWEppkm7vgPQY2XsHoEkaMQ0AdZY=
github.com/AzureAD/microsoft-authentication-library-for-go v1.0.0/go.mod h1:kgDmCTgBzIEPFElEF+FK0SdjAor06dRq2Go927dnQ6o=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755 h1:J45/QHgrzUdqe/Vco/Vxk0wRvdS2nKUxmf/zLgvfass=
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1755/go.mod h1:RcDobYh8k5VP6TNybz9m++gL3ijVI5wueVr0EM10VsU=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/cloudflare-go v0.86.0 h1:jEKN5VHNYNYtfDL2lUFLTRo+nOVNPFxpXTstVx0rqHI=
github.com/cloudflare/cloudflare-go v0.86.0/go.mod h1:wYW/5UP02TUfBToa/yKbQHV+r6h1NnJ1Je7XjuGM4Jw=
github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4=
github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
github.com/ebitengine/purego v0.8.1 h1:sdRKd6plj7KYW33EH5As6YKfe8m9zbN9JMrOjNVF/BE=
github.com/ebitengine/purego v0.8.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M=
github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
@ -34,92 +69,212 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1X
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.20.2 h1:peX42Qnh5Q0q3vrAnRy43R/JwTnnv75AebxbkTL7Ia4=
github.com/emersion/go-smtp v0.20.2/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/florianl/go-nfqueue/v2 v2.0.0 h1:NTCxS9b0GSbHkWv1a7oOvZn679fsyDkaSkRvOYpQ9Oo=
github.com/florianl/go-nfqueue/v2 v2.0.0/go.mod h1:M2tBLIj62QpwqjwV0qfcjqGOqP3qiTuXr2uSRBXH9Qk=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc=
github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
github.com/go-acme/lego/v4 v4.16.1 h1:JxZ93s4KG0jL27rZ30UsIgxap6VGzKuREsSkkyzeoCQ=
github.com/go-acme/lego/v4 v4.16.1/go.mod h1:AVvwdPned/IWpD/ihHhMsKnveF7HHYAz/CmtXi7OZoE=
github.com/go-jose/go-jose/v4 v4.0.1 h1:QVEPDE3OluqXBQZDcnNvQrInro2h0e4eqNbnZSWqS6U=
github.com/go-jose/go-jose/v4 v4.0.1/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9 h1:cC0Hbb+18DJ4i6ybqDybvj4wdIDS4vnD0QEci98PgM8=
github.com/goftp/file-driver v0.0.0-20180502053751-5d604a0fc0c9/go.mod h1:GpOj6zuVBG3Inr9qjEnuVTgBlk2lZ1S9DcoFiXWyKss=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42 h1:JdOp2qR5PF4O75tzHeqrwnDDv8oHDptWyTbyYS4fD8E=
github.com/goftp/server v0.0.0-20200708154336-f64f7c2d8a42/go.mod h1:k/SS6VWkxY7dHPhoMQ8IdRu8L4lQtmGbhyXGg+vCnXE=
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc=
github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jlaffaye/ftp v0.1.0 h1:DLGExl5nBoSFoNshAUHwXAezXwXBvFdx7/qwhucWNSE=
github.com/jlaffaye/ftp v0.1.0/go.mod h1:hhq4G4crv+nW2qXtNYcuzLeOudG92Ps37HEKeg2e3lE=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
github.com/likexian/whois v1.15.1 h1:6vTMI8n9s1eJdmcO4R9h1x99aQWIZZX1CD3am68gApU=
github.com/likexian/whois v1.15.1/go.mod h1:/nxmQ6YXvLz+qTxC/QFtEJNAt0zLuRxJrKiWpBJX8X0=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U=
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg=
github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE=
github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil/v4 v4.24.10 h1:7VOzPtfw/5YDU+jLEoBwXwxJbQetULywoSV4RYY7HkM=
github.com/shirou/gopsutil/v4 v4.24.10/go.mod h1:s4D/wg+ag4rG0WO7AiTj2BeYCRhym0vM7DHbZRxnIT8=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 h1:mmz27tVi2r70JYnm5y0Zk8w0Qzsx+vfUw3oqSyrEfP8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 h1:g9SWTaTy/rEuhMErC2jWq9Qt5ci+jBYSvXnJsLq4adg=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490/go.mod h1:l9q4vc1QiawUB1m3RU+87yLvrrxe54jc0w/kEl4DbSQ=
github.com/things-go/go-socks5 v0.0.5 h1:qvKaGcBkfDrUL33SchHN93srAmYGzb4CxSM2DPYufe8=
github.com/things-go/go-socks5 v0.0.5/go.mod h1:mtzInf8v5xmsBpHZVbIw2YQYhc4K0jRwzfsH64Uh0IQ=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/vbauerster/mpb/v8 v8.8.3 h1:dTOByGoqwaTJYPubhVz3lO5O6MK553XVgUo33LdnNsQ=
github.com/vbauerster/mpb/v8 v8.8.3/go.mod h1:JfCCrtcMsJwP6ZwMn9e5LMnNyp3TVNpUWWkN+nd4EWk=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220313003712-b769efc7c000/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4=
golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -127,17 +282,33 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=

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

@ -5,9 +5,12 @@ import (
"b612.me/staros"
"context"
"encoding/json"
"fmt"
"github.com/spf13/cobra"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
)
@ -15,6 +18,7 @@ var s HttpServer
var daemon bool
var hooks string
var speedlimit string
func init() {
Cmd.Flags().StringVarP(&hooks, "hook", "H", "", "fileget hook for modify")
@ -34,8 +38,54 @@ func init() {
Cmd.Flags().StringVar(&s.page403, "403", "", "自定义403页面地址")
Cmd.Flags().StringVar(&s.page404, "404", "", "自定义404页面地址")
Cmd.Flags().BoolVarP(&s.httpDebug, "debug", "D", false, "开启调试模式")
Cmd.Flags().StringSliceVarP(&s.noListPath, "nolist", "N", []string{}, "禁止列出文件的路径,如/")
Cmd.Flags().StringToStringVarP(&s.listPwd, "listpwd", "L", map[string]string{}, "列出文件的路径的密码,如/=/password")
Cmd.Flags().BoolVarP(&s.listSameForFile, "list-same", "S", false, "如开启,文件的获取权限将与文件夹保持一致")
Cmd.Flags().StringVarP(&speedlimit, "speedlimit", "s", "", "限速如1M意思是1MB/s")
Cmd.Flags().Bool("daeapplied", false, "")
Cmd.Flags().StringVar(&s.background, "background", "", "背景图片地址")
Cmd.Flags().StringVar(&s.mobildBackground, "mbackground", "", "移动端背景图片地址")
Cmd.Flags().MarkHidden("daeapplied")
}
func parseSpeedString(speedString string) (uint64, error) {
// 定义单位及其对应的字节值
unitMultipliers := map[string]int64{
"b": 1, "": 1,
"k": 1024, "kb": 1024, "kib": 1024,
"m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024,
"g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024,
"t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024,
}
// 正则表达式匹配速度的格式
re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`)
matches := re.FindStringSubmatch(strings.ToLower(speedString))
if matches == nil {
return 0, fmt.Errorf("invalid speed string format")
}
// 解析数值部分
value, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return 0, fmt.Errorf("invalid numeric value")
}
// 获取单位部分
unit := matches[2]
if unit == "" {
unit = "b"
}
// 根据单位计算最终的字节每秒值
multiplier, ok := unitMultipliers[unit]
if !ok {
return 0, fmt.Errorf("invalid unit in speed string")
}
return uint64(value * float64(multiplier)), nil
}
var Cmd = &cobra.Command{
@ -43,6 +93,19 @@ var Cmd = &cobra.Command{
Short: "HTTP文件服务器(HTTP File Browser Server)",
Long: `HTTP文件服务器(HTTP File Browser Server)`,
Run: func(cmd *cobra.Command, args []string) {
if s.logpath != "" && starlog.GetWriter() == nil {
starlog.SetLogFile(s.logpath, starlog.Std, true)
}
if speedlimit != "" {
speed, err := parseSpeedString(speedlimit)
if err != nil {
starlog.Criticalln("Speed Limit Error:", err)
os.Exit(1)
}
s.speedlimit = speed
starlog.Infoln("Speed Limit:(user in):\t", speedlimit)
starlog.Infoln("Speed Limit (bytes/s):\t", speed)
}
if hooks != "" {
if !staros.Exists(hooks) {
starlog.Criticalln("hook file not exists")

View File

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

View File

@ -1,6 +1,7 @@
package httpserver
import (
"b612.me/apps/b612/version"
"b612.me/starcrypto"
"b612.me/starlog"
"b612.me/starnet"
@ -8,8 +9,10 @@ import (
"context"
_ "embed"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"html/template"
"io"
"io/ioutil"
"math"
@ -22,7 +25,7 @@ import (
"time"
)
var version = "2.1.0"
var ver = version.Version
type HttpServerCfgs func(cfg *HttpServerCfg)
@ -45,6 +48,14 @@ type HttpServerCfg struct {
ctx context.Context
hooks []ServerHook
httpDebug bool
noListPath []string
listPwd map[string]string
listSameForFile bool
// speed limit means xx bytes/s
speedlimit uint64
background string
mobildBackground string
}
type ServerHook struct {
@ -67,219 +78,8 @@ var jquery []byte
//go:embed upload.html
var uploadPage []byte
var htmlTitle string = `<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>B612 Http Server %s</title>
<style>
* {
box-sizing: border-box;
}
body {
background-color: #f5f5f5;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.container {
max-width: 960px;
margin: 0 auto;
padding: 24px;
}
h1 {
text-align: center;
margin-bottom: 24px;
}
table {
width: 100%%;
border-collapse: collapse;
margin-top: 24px;
}
th,
td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #ddd;
position: relative;
}
th[data-sort]:before {
content: "▼";
display: inline-block;
height: 20px;
width: 20px;
margin-right: 10px;
vertical-align: middle;
position: absolute;
right: 0;
top: 50%%;
transform: translateY(-50%%);
opacity: 0.3;
transition: all 0.2s ease-in-out;
}
th[data-sort].asc:before {
content: "▲";
opacity: 1;
}
th:hover:before {
opacity: 1;
}
.filename {
color: #007bff;
text-decoration: underline;
}
.filetype {
text-transform: uppercase;
}
@media screen and (max-width: 600px) {
table {
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="container">
<h1>B612 Http Server - %s</h1>
<hr /><pre><h2> %s </h2></pre>%s
<table>
<thead>
<tr>
<th data-sort="name" class="asc">Name</th>
<th data-sort="modified">Modified</th>
<th data-sort="size">Size</th>
<th data-sort="type">Type</th>
</tr>
</thead>
<tbody>`
var htmlTail = ` </tbody>
</table>
<hr />
<pre>
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
</pre>
</div>
<script>
function sortTable(th, n) {
const table = document.querySelector('table');
const rows = table.rows;
let switching = true;
let shouldSwitch = false;
let direction = 'asc';
let switchcount = 0;
while (switching) {
switching = false;
let i;
for (i = 1; i < rows.length - 1; i++) {
shouldSwitch = false;
const x = rows[i].getElementsByTagName("td")[n];
const y = rows[i + 1].getElementsByTagName("td")[n];
let xValue, yValue;
if (n === 2) { // size sorting
if (x.innerText==="-") {
xValue=-1;
}else{
xValue = parseInt(x.innerText.split(' ')[0]);
}
if (y.innerText==="-") {
yValue=-1;
}else{
yValue = parseInt(y.innerText.split(' ')[0]);
}
} else {
xValue = x.innerText.toLowerCase();
yValue = y.innerText.toLowerCase();
}
if (direction === 'asc') {
if (xValue > yValue) {
shouldSwitch = true;
break;
}
} else if (direction === 'desc') {
if (xValue < yValue) {
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
switchcount++;
} else {
if (switchcount === 0 && direction === 'asc') {
direction = 'desc';
switching = true;
}
}
}
// update sort class
const ths = table.getElementsByTagName('th');
for (let i = 0; i < ths.length; i++) {
const currentTh = ths[i];
if (currentTh !== th) {
currentTh.classList.remove('asc');
} else {
currentTh.classList.toggle('asc');
}
}
// hide arrow on non-sorting columns
const sortableThs = table.querySelectorAll('thead th[data-sort]');
for (let i = 0; i < sortableThs.length; i++) {
const sortableTh = sortableThs[i];
if (sortableTh !== th) {
sortableTh.classList.remove('asc');
}
}
}
// add sorting event listener to thead
const ths = document.querySelectorAll('table th[data-sort]');
for (let i = 0; i < ths.length; i++) {
const th = ths[i];
th.addEventListener('click', () => {
const sortType = th.getAttribute('data-sort');
let columnIndex;
switch (sortType) {
case 'name':
columnIndex = 0;
break;
case 'modified':
columnIndex = 1;
break;
case 'size':
columnIndex = 2;
break;
case 'type':
columnIndex = 3;
break;
}
sortTable(th, columnIndex);
});
}
</script>
</body>
</html>
`
//go:embed template.html
var templateHtml []byte
func WithHooks(hooks []ServerHook) HttpServerCfgs {
return func(cfg *HttpServerCfg) {
@ -333,7 +133,7 @@ func (h *HttpServer) Run(ctx context.Context) error {
server.Shutdown(ctx)
}
}()
if h.logpath != "" {
if h.logpath != "" && starlog.GetWriter() == nil {
starlog.SetLogFile(h.logpath, starlog.Std, true)
}
netcards, err := net.Interfaces()
@ -401,7 +201,14 @@ func (h *HttpServer) Page403(w http.ResponseWriter) {
return
}
}
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">403 Forbidden</h1><hr ></body></html>`))
w.Write([]byte(`
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>B612 HTTP SERVER</center>
</body>
</html>`))
}
func (h *HttpServer) Page401(w http.ResponseWriter) {
@ -514,7 +321,7 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
log.SetShowFuncName(false)
log.SetShowOriginFile(false)
w.Header().Set("X-Powered-By", "B612.ME")
w.Header().Set("Server", "B612/"+version)
w.Header().Set("Server", "B612/"+ver)
if !h.BasicAuth(log, w, r) {
return
}
@ -575,26 +382,25 @@ func (h *HttpServer) Listen(w http.ResponseWriter, r *http.Request) {
}
func (h *HttpServer) CalcRange(r *http.Request) (int64, int64) {
var rangeStart, rangeEnd int64
rangeStart, rangeEnd = -1, -1
for k, v := range r.Header {
if strings.ToLower(k) == "range" {
if strings.Contains(v[0], "bytes=") {
v[0] = strings.Replace(v[0], "bytes=", "", -1)
} else {
continue
}
data := strings.Split(v[0], "-")
if len(data) == 0 {
break
}
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
if len(data) > 1 {
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
}
//w.WriteHeader(206) //206 支持断点续传
break
}
var rangeStart, rangeEnd int64 = -1, -1
ranges := r.Header.Get("Range")
if ranges == "" {
return rangeStart, rangeEnd
}
if !strings.Contains(ranges, "bytes=") {
return rangeStart, rangeEnd
}
ranges = strings.TrimPrefix(ranges, "bytes=")
data := strings.Split(ranges, "-")
if len(data) == 0 {
return rangeStart, rangeEnd
}
rangeStart, _ = strconv.ParseInt(data[0], 10, 64)
if len(data) > 1 {
rangeEnd, _ = strconv.ParseInt(data[1], 10, 64)
}
if rangeEnd == 0 {
rangeEnd = -1
}
return rangeStart, rangeEnd
}
@ -630,16 +436,19 @@ func (h *HttpServer) BuildHeader(w http.ResponseWriter, r *http.Request, fullpat
if _, ok := h.willHook(fullpath); ok {
return nil
}
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
start, end := h.CalcRange(r)
if start != -1 {
if end == -1 {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size()-start, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(finfo.Size()-1, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(fpinfo.Size()-rangeStart, 10))
} else {
w.Header().Set("Content-Length", strconv.FormatInt(end-start+1, 10))
w.Header().Set("Content-Range", `bytes `+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)+"/"+strconv.FormatInt(finfo.Size(), 10))
//w.Header().Set("Content-Length", strconv.FormatInt(1+rangeEnd-rangeStart, 10))
}
} else {
w.Header().Set("Content-Length", strconv.FormatInt(finfo.Size(), 10))
}
}
}
@ -666,11 +475,57 @@ func (h *HttpServer) willHook(fullpath string) (ServerHook, bool) {
func (h *HttpServer) ResponseGet(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if staros.IsFolder(fullpath) {
if len(h.listPwd) != 0 {
for k, v := range h.listPwd {
if strings.HasPrefix(r.URL.Path, k) {
if r.URL.Query().Get("list") == v {
return h.getFolder(log, w, r, fullpath)
}
}
}
}
if len(h.noListPath) != 0 {
for _, v := range h.noListPath {
if strings.HasPrefix(r.URL.Path, v) {
h.Page403(w)
return nil
}
}
}
return h.getFolder(log, w, r, fullpath)
}
if !h.listSameForFile {
return h.getFile(log, w, r, fullpath)
}
if len(h.listPwd) != 0 {
for k, v := range h.listPwd {
if strings.HasPrefix(r.URL.Path, k) {
if r.URL.Query().Get("list") == v {
return h.getFile(log, w, r, fullpath)
}
}
}
}
if len(h.noListPath) != 0 {
for _, v := range h.noListPath {
if strings.HasPrefix(r.URL.Path, v) {
h.Page403(w)
return nil
}
}
}
return h.getFile(log, w, r, fullpath)
}
type FileData struct {
Attr string `json:"attr"`
Name string `json:"name"`
Modified string `json:"modified"`
Size int64 `json:"size"`
Type string `json:"type"`
}
func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
dir, err := ioutil.ReadDir(fullpath)
if err != nil {
@ -688,14 +543,24 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
if h.uploadFolder != "" {
upload = `<a href=/b612?upload=true>Upload Web Page Is Openned!</a>`
}
w.Write([]byte(fmt.Sprintf(htmlTitle, r.URL.Path, version, "Index of "+r.URL.Path, upload)))
attr := r.URL.Query().Get("list")
if attr != "" {
attr = "/?list=" + attr
}
var fdatas = make([]FileData, 0, len(dir)+1)
if r.URL.Path != "/" {
p := r.URL.Path
if p[len(p)-1:] != "/" {
p += "/"
}
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
p+"..", "../", "-", "-", "上层文件夹")))
fdatas = append(fdatas, FileData{
Attr: p + ".." + attr,
Name: "..",
Modified: "-",
Size: -1,
Type: "上层文件夹",
})
}
if r.URL.Path == "/" {
r.URL.Path = ""
@ -706,23 +571,104 @@ func (h *HttpServer) getFolder(log *starlog.StarLogger, w http.ResponseWriter, r
for _, v := range dir {
if v.Name() != "." || v.Name() != ".." {
if !v.IsDir() {
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
r.URL.Path+"/"+v.Name(), v.Name(), v.ModTime().Format("2006-01-02 15:04:05"), fmt.Sprintf("%d (%s)", v.Size(), h.trimSize(v.Size())), h.FileType(v.Name()))))
fdatas = append(fdatas, FileData{
Name: v.Name(),
Attr: r.URL.Path + "/" + v.Name(),
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
Size: v.Size(),
Type: h.FileType(v.Name()),
})
} else {
w.Write([]byte(fmt.Sprintf(`<tr><td><a class="filename" href="%s">%s</a></td><td>%s</td><td>%s</td><td class="filetype">%s</td></tr>`,
r.URL.Path+"/"+v.Name(), v.Name()+"/", v.ModTime().Format("2006-01-02 15:04:05"), "-", "文件夹")))
fdatas = append(fdatas, FileData{
Name: v.Name() + "/",
Attr: r.URL.Path + "/" + v.Name() + attr,
Modified: v.ModTime().Format("2006-01-02 15:04:05"),
Size: -1,
Type: "文件夹",
})
}
}
}
w.Write([]byte(htmlTail))
tmpt, err := template.New("index").Parse(string(templateHtml))
if err != nil {
log.Errorf("Parse Template Error:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
jData, err := json.Marshal(fdatas)
if err != nil {
log.Errorf("Json Marshal Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
if r.URL.Path == "" {
r.URL.Path = "/"
}
var bk, mbk string
if h.background != "" {
bk = `background: url('` + h.background + `') no-repeat center center fixed;`
}
if h.mobildBackground != "" {
mbk = `background: url('` + h.mobildBackground + `') no-repeat center center fixed;`
}
if h.mobildBackground == "" && h.background != "" {
mbk = bk
}
err = tmpt.Execute(w, map[string]interface{}{
"IdxTitle": r.URL.Path,
"Version": ver,
"Idx": "Index of " + r.URL.Path,
"Upload": template.HTML(upload),
"Data": template.JS(jData),
"Photo": template.CSS(bk),
"MobilePhoto": template.CSS(mbk),
})
if err != nil {
log.Errorf("Template Execute Failed:%v\n", err)
w.WriteHeader(502)
w.Write([]byte(`<html><title>B612 Http Server</title><body><h1 "style="text-align: center;">502 SERVER ERROR</h1><hr ></body></html>`))
return err
}
return nil
}
func (h *HttpServer) getSleepTime() time.Duration {
if h.speedlimit == 0 {
return 0
}
return time.Nanosecond * time.Duration(16384*1000*1000*1000/h.speedlimit) / 2
}
func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *http.Request, fullpath string) error {
if !staros.Exists(fullpath) {
h.Page404(w)
return errors.New("File Not Found! 404 ERROR")
}
var lastCount int64
var lastDate time.Time = time.Now()
var currentCount int64
speedControl := func(count int) {
if h.speedlimit == 0 {
return
}
currentCount += int64(count)
for {
if time.Since(lastDate) < time.Second {
if uint64(currentCount-lastCount) > h.speedlimit {
time.Sleep(h.getSleepTime())
} else {
break
}
} else {
lastDate = time.Now()
lastCount = currentCount
break
}
}
}
//starlog.Debugln(r.Header)
startRange, endRange := h.CalcRange(r)
fp, err := os.Open(fullpath)
@ -756,10 +702,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if !needCurl {
w.WriteHeader(200)
for {
buf := make([]byte, 1048576)
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if n != 0 {
ns, err := w.Write(buf[0:n])
speedControl(n)
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@ -770,7 +717,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
if err == io.EOF {
break
}
log.Errorln("Read File %s Failed:%v\n", fullpath, err)
log.Errorf("Read File %s Failed:%v\n", fullpath, err)
return err
}
}
@ -784,13 +731,14 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
return err
}
b64 := base64.StdEncoding.EncodeToString(data)
req, err := starnet.Curl(starnet.NewRequests(hook.Url, starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
}),
req, err := starnet.Curl(starnet.NewSimpleRequest(hook.Url,
"POST",
starnet.WithBytes(starnet.BuildPostForm(map[string]string{
"data": b64,
"ip": r.RemoteAddr,
})),
starnet.WithTimeout(time.Duration(hook.Timeout)*time.Millisecond)))
if err != nil || len(req.RecvData) == 0 {
if err != nil {
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.WriteHeader(200)
ns, err := w.Write(data)
@ -801,9 +749,10 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
}
return nil
}
recvData := req.Body().Bytes()
w.WriteHeader(200)
w.Header().Set("Content-Length", strconv.Itoa(len(req.RecvData)))
ns, err := w.Write(req.RecvData)
w.Header().Set("Content-Length", strconv.Itoa(len(recvData)))
ns, err := w.Write(recvData)
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", fullpath, err)
@ -816,7 +765,7 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
fp.Seek(int64(startRange), 0)
count := startRange
for {
buf := make([]byte, 1048576)
buf := make([]byte, 16384)
n, err := fp.Read(buf)
if err != nil {
if err == io.EOF {
@ -825,8 +774,9 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
log.Errorf("Read File %s Failed:%v\n", r.URL.Path, err)
return err
}
speedControl(n)
if endRange == -1 {
ns, err := w.Write(buf[0:n])
ns, err := w.Write(buf[:n])
transferData += ns
if err != nil {
log.Errorf("Transfer File %s to Remote Failed:%v\n", r.URL.Path, err)
@ -841,11 +791,11 @@ func (h *HttpServer) getFile(log *starlog.StarLogger, w http.ResponseWriter, r *
writeNum = int(endRange - count + 1)
}
ns, err := w.Write(buf[0:writeNum])
transferData += ns
if err != nil {
log.Errorln("Transfer Error:", err)
return err
}
transferData += ns
count += int64(n)
}
}

478
httpserver/template.html Normal file
View File

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

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

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

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")
}
@ -106,7 +134,7 @@ func (k *KeyGen) Gen() error {
return err
}
var block *pem.Block
if k.Encrypt != "" {
if k.Encrypt == "" {
block, err = ssh.MarshalPrivateKey(priv, "")
} else {
block, err = ssh.MarshalPrivateKeyWithPassphrase(priv, "", []byte(k.Encrypt))

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

150
mget/cmd.go Normal file
View File

@ -0,0 +1,150 @@
package mget
import (
"b612.me/stario"
"b612.me/starlog"
"b612.me/starnet"
"fmt"
"github.com/spf13/cobra"
"os"
"os/signal"
"regexp"
"strconv"
"strings"
"time"
)
var mg Mget
var Cmd = &cobra.Command{
Use: "mget",
Short: "多线程下载工具",
Long: `多线程下载工具`,
Run: Run,
}
var headers []string
var ua string
var proxy string
var skipVerify bool
var speedcontrol, user, pwd string
var dialTimeout, timeout int
func init() {
Cmd.Flags().StringVarP(&mg.Tareget, "output", "o", "", "输出文件名")
Cmd.Flags().IntVarP(&mg.BufferSize, "buffer", "b", 8192, "缓冲区大小")
Cmd.Flags().IntVarP(&mg.Thread, "thread", "t", 8, "线程数")
Cmd.Flags().BoolVarP(&mg.NoWriteRedo, "no-redo", "N", false, "不写入redo文件")
Cmd.Flags().IntVarP(&mg.RedoRPO, "safe", "s", 0, "安全校验点,0意味自动调整")
Cmd.Flags().IntVarP(&mg.RedoMinSaveSec, "redo-min-sec", "m", 2, "redo文件最短写入间隔")
Cmd.Flags().StringSliceVarP(&headers, "header", "H", []string{}, "自定义请求头,格式: key=value")
Cmd.Flags().StringVarP(&proxy, "proxy", "P", "", "代理地址")
Cmd.Flags().StringVarP(&ua, "user-agent", "U", "", "自定义User-Agent")
Cmd.Flags().BoolVarP(&skipVerify, "skip-verify", "k", false, "跳过SSL验证")
Cmd.Flags().StringVarP(&speedcontrol, "speed", "S", "", "限速如1M意思是1MB/s")
Cmd.Flags().IntVarP(&dialTimeout, "dial-timeout", "d", 5, "连接网络超时时间,单位:秒")
Cmd.Flags().IntVarP(&timeout, "timeout", "T", 0, "下载超时时间,单位:秒")
Cmd.Flags().StringVarP(&user, "user", "u", "", "http basic认证用户")
Cmd.Flags().StringVarP(&pwd, "passwd", "p", "", "http basic认证密码")
}
func parseSpeedString(speedString string) (uint64, error) {
// 定义单位及其对应的字节值
unitMultipliers := map[string]int64{
"b": 1, "": 1,
"k": 1024, "kb": 1024, "kib": 1024,
"m": 1024 * 1024, "mb": 1024 * 1024, "mib": 1024 * 1024,
"g": 1024 * 1024 * 1024, "gb": 1024 * 1024 * 1024, "gib": 1024 * 1024 * 1024,
"t": 1024 * 1024 * 1024 * 1024, "tb": 1024 * 1024 * 1024 * 1024, "tib": 1024 * 1024 * 1024 * 1024,
}
// 正则表达式匹配速度的格式
re := regexp.MustCompile(`(?i)^\s*([\d.]+)\s*(b|k|m|g|t|kb|mb|gb|tb|kib|mib|gib|tib)?\s*/?\s*s?\s*$`)
matches := re.FindStringSubmatch(strings.ToLower(speedString))
if matches == nil {
return 0, fmt.Errorf("invalid speed string format")
}
// 解析数值部分
value, err := strconv.ParseFloat(matches[1], 64)
if err != nil {
return 0, fmt.Errorf("invalid numeric value")
}
// 获取单位部分
unit := matches[2]
if unit == "" {
unit = "b"
}
// 根据单位计算最终的字节每秒值
multiplier, ok := unitMultipliers[unit]
if !ok {
return 0, fmt.Errorf("invalid unit in speed string")
}
return uint64(value * float64(multiplier)), nil
}
func Run(cmd *cobra.Command, args []string) {
if args == nil || len(args) == 0 {
starlog.Errorln("缺少URL参数")
os.Exit(1)
}
mg.Setting = *starnet.NewSimpleRequest(args[0], "GET")
mg.OriginUri = args[0]
if dialTimeout > 0 {
mg.Setting.SetDialTimeout(time.Duration(dialTimeout) * time.Second)
}
if timeout > 0 {
mg.Setting.SetTimeout(time.Duration(timeout) * time.Second)
}
if user != "" || pwd != "" {
mg.Setting.RequestOpts.SetBasicAuth(user, pwd)
}
if speedcontrol != "" {
speed, err := parseSpeedString(speedcontrol)
if err != nil {
starlog.Criticalln("Speed Limit Error:", err)
os.Exit(1)
}
mg.speedlimit = int64(speed)
fmt.Printf("Max Speed Limit:(user in):\t%v\n", speedcontrol)
fmt.Printf("Max Speed Limit (bytes/s):\t%v bytes/sec\n", speed)
}
for _, v := range headers {
kv := strings.SplitN(v, "=", 2)
if len(kv) != 2 {
continue
}
mg.Setting.AddHeader(strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]))
}
if ua != "" {
mg.Setting.SetUserAgent(ua)
}
if proxy != "" {
mg.Setting.SetProxy(proxy)
}
if skipVerify {
mg.Setting.SetSkipTLSVerify(true)
}
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt)
select {
case err := <-stario.WaitUntilFinished(mg.Run):
if err != nil {
starlog.Errorln(err)
os.Exit(2)
}
time.Sleep(time.Second)
return
case <-sig:
starlog.Infoln("User Interrupted")
mg.fn()
time.Sleep(time.Second)
if !mg.NoWriteRedo {
mg.Redo.Save()
}
os.Exit(3)
}
}

104
mget/process.go Normal file
View File

@ -0,0 +1,104 @@
package mget
import (
"fmt"
"github.com/vbauerster/mpb/v8"
"github.com/vbauerster/mpb/v8/decor"
"io"
"strings"
"time"
)
func (m *Mget) processMiddleware(base mpb.BarFiller) mpb.BarFiller {
fn := func(w io.Writer, st decor.Statistics) error {
var res string
count := 0
fmt.Fprintf(w, "\nSpeed:%v AvgSpeed:%v\n", m.Redo.FormatSpeed("MB"), m.Redo.FormatAvgSpeed("MB"))
_, err := fmt.Fprintf(w, "Finished:%s Total Write:%d\n\n", m.Redo.FormatPercent(), m.Redo.Total())
for k := range m.threads {
v := m.threads[len(m.threads)-1-k]
if v != nil {
count++
percent := v.FormatPercent()
if m.Redo.Total() == m.Redo.ContentLength {
percent = "100.00%"
}
res = fmt.Sprintf("Thread %v: %s %s\t", len(m.threads)-k, v.FormatSpeed("MB"), percent) + res
if count%3 == 0 {
res = strings.TrimRight(res, "\t")
fmt.Fprintf(w, "%s\n", res)
res = ""
}
}
}
if res != "" {
res = strings.TrimRight(res, "\t")
fmt.Fprintf(w, "%s\n", res)
}
return err
}
if base == nil {
return mpb.BarFillerFunc(fn)
}
return mpb.BarFillerFunc(func(w io.Writer, st decor.Statistics) error {
err := fn(w, st)
if err != nil {
return err
}
return base.Fill(w, st)
})
}
func (w *Mget) Process() {
w.processEnable = true
defer func() {
w.processEnable = false
}()
fmt.Println()
p := mpb.New()
var filler mpb.BarFiller
filler = w.processMiddleware(filler)
bar := p.New(int64(w.ContentLength),
mpb.BarStyle().Rbound("|"),
mpb.BarExtender(filler, true), // all bars share same extender filler
mpb.PrependDecorators(
decor.Counters(decor.SizeB1024(0), "% .2f / % .2f"),
),
mpb.AppendDecorators(
decor.AverageETA(decor.ET_STYLE_GO),
decor.Name(" ] "),
decor.AverageSpeed(decor.SizeB1024(0), "% .2f "),
),
)
defer p.Wait()
lastTime := time.Now()
bar.SetRefill(int64(w.Redo.Total()))
bar.DecoratorAverageAdjust(time.Now().Add(time.Millisecond * time.Duration(-w.TimeCost)))
for {
select {
case <-w.ctx.Done():
bar.SetCurrent(int64(w.Redo.Total()))
if w.dynLength {
bar.SetTotal(int64(w.Redo.ContentLength), true)
}
bar.Abort(false)
return
case <-time.After(time.Second):
if !w.writeEnable {
bar.SetCurrent(int64(w.Redo.Total()))
if w.dynLength {
bar.SetTotal(int64(w.Redo.ContentLength), true)
}
bar.Abort(true)
return
}
now := w.Redo.Total()
date := time.Now()
bar.EwmaSetCurrent(int64(now), date.Sub(lastTime))
lastTime = date
if w.dynLength {
bar.SetTotal(int64(w.Redo.ContentLength), false)
}
}
}
}

84
mget/range.go Normal file
View File

@ -0,0 +1,84 @@
package mget
import "sort"
type Range struct {
Min uint64 `json:"min"`
Max uint64 `json:"max"`
}
type SortRange []Range
func (s SortRange) Len() int { return len(s) }
func (s SortRange) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SortRange) Less(i, j int) bool { return s[i].Min < s[j].Min }
func uniformRange(rg []Range) ([]Range, error) {
newRg := make([]Range, 0, len(rg))
sort.Sort(SortRange(rg))
var last *Range = nil
for _, v := range rg {
if last != nil && v.Min <= last.Max+1 {
if last.Max <= v.Max {
last.Max = v.Max
}
continue
}
newRg = append(newRg, v)
last = &newRg[len(newRg)-1]
}
return newRg, nil
}
func singleSubRange(origin []Range, v Range) []Range {
newRg := make([]Range, 0)
sort.Sort(SortRange(origin))
for i := 0; i < len(origin); i++ {
ori := origin[i]
res := make([]Range, 0)
shouldAdd := true
for j := 0; j < 1; j++ {
if v.Min <= ori.Min && v.Max >= ori.Max {
shouldAdd = false
break
}
if v.Max < ori.Min {
continue
}
if v.Min > ori.Max {
break
}
ur1 := Range{
Min: ori.Min,
Max: v.Min - 1,
}
if v.Min == 0 {
ur1.Min = 1
ur1.Max = 0
}
ur2 := Range{
Min: v.Max + 1,
Max: ori.Max,
}
if ur1.Max >= ur1.Min {
res = append(res, ur1)
}
if ur2.Max >= ur2.Min {
res = append(res, ur2)
}
}
if len(res) == 0 && shouldAdd {
res = append(res, ori)
}
newRg = append(newRg, res...)
}
return newRg
}
func subRange(origin, rg []Range) []Range {
sort.Sort(SortRange(rg))
sort.Sort(SortRange(origin))
for _, v := range rg {
origin = singleSubRange(origin, v)
}
return origin
}

30
mget/range_test.go Normal file
View File

@ -0,0 +1,30 @@
package mget
import (
"fmt"
"reflect"
"testing"
)
func TestRangePlus(t *testing.T) {
var r = Redo{
ContentLength: 100,
rangeUpdated: true,
Range: []Range{
{10, 12},
{13, 20},
{17, 19},
{30, 80},
{90, 97},
},
}
err := r.reform()
if err != nil {
t.Error(err)
}
if !reflect.DeepEqual(r.Range, []Range{{10, 20}, {30, 80}, {90, 97}}) {
t.Error("reform error")
}
fmt.Println(r.Range)
fmt.Println(r.ReverseRange())
}

174
mget/redo.go Normal file
View File

@ -0,0 +1,174 @@
package mget
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"time"
)
type Redo struct {
Is206 bool `json:"is_206"`
OriginUri string `json:"origin_uri"`
Date time.Time `json:"date"`
Filename string `json:"filename"`
ContentLength uint64 `json:"content_length"`
Range []Range `json:"range"`
TimeCost uint64 `json:"time_cost"`
rangeUpdated bool
startDate time.Time
startCount uint64
lastUpdate time.Time
lastTotal uint64
speed float64
avgSpeed float64
total uint64
isRedo bool
lastCallSave time.Time
sync.RWMutex
}
func (r *Redo) CacheTotal() uint64 {
return r.total
}
func (r *Redo) Total() uint64 {
var total uint64
for {
r.RLock()
for _, v := range r.Range {
total += v.Max - v.Min + 1
}
r.total = total
r.RUnlock()
if r.total > r.ContentLength && r.ContentLength > 0 {
r.reform()
total = 0
continue
}
break
}
return total
}
func (r *Redo) Update(start, end int) error {
if start < 0 || end < 0 || start > end {
return fmt.Errorf("invalid range: %d-%d", start, end)
}
r.Lock()
defer r.Unlock()
r.rangeUpdated = true
if r.lastUpdate.IsZero() {
r.startDate = time.Now()
for _, v := range r.Range {
r.startCount += v.Max - v.Min + 1
}
time.Sleep(time.Millisecond)
}
r.Range = append(r.Range, Range{uint64(start), uint64(end)})
now := time.Now()
if now.Sub(r.lastUpdate) >= time.Millisecond*500 {
var total uint64
for _, v := range r.Range {
total += v.Max - v.Min + 1
}
r.total = total
r.speed = float64(total-r.lastTotal) / (float64(now.Sub(r.lastUpdate).Milliseconds()) / 1000.00)
if !r.lastUpdate.IsZero() {
r.TimeCost += uint64(now.Sub(r.lastUpdate).Milliseconds())
}
r.avgSpeed = float64(total-r.startCount) / (float64(now.Sub(r.startDate).Milliseconds()) / 1000.00)
r.lastTotal = total
r.lastUpdate = now
}
return nil
}
func (r *Redo) Percent() float64 {
return float64(r.Total()) / float64(r.ContentLength)
}
func (r *Redo) FormatPercent() string {
return fmt.Sprintf("%.2f%%", r.Percent()*100)
}
func (r *Redo) FormatSpeed(unit string) string {
switch strings.ToLower(unit) {
case "kb":
return fmt.Sprintf("%.2f KB/s", r.speed/1024)
case "mb":
return fmt.Sprintf("%.2f MB/s", r.speed/1024/1024)
case "gb":
return fmt.Sprintf("%.2f GB/s", r.speed/1024/1024/1024)
default:
return fmt.Sprintf("%.2f B/s", r.speed)
}
}
func (r *Redo) FormatAvgSpeed(unit string) string {
switch strings.ToLower(unit) {
case "kb":
return fmt.Sprintf("%.2f KB/s", r.avgSpeed/1024)
case "mb":
return fmt.Sprintf("%.2f MB/s", r.avgSpeed/1024/1024)
case "gb":
return fmt.Sprintf("%.2f GB/s", r.avgSpeed/1024/1024/1024)
default:
return fmt.Sprintf("%.2f B/s", r.avgSpeed)
}
}
func (r *Redo) Speed() float64 {
return r.speed
}
func (r *Redo) AverageSpeed() float64 {
return r.avgSpeed
}
func (r *Redo) Save() error {
//defer recover()
var err error
err = r.reform()
if err != nil {
return err
}
r.Lock()
defer r.Unlock()
r.lastCallSave = time.Now()
if r.Filename != "" {
data, err := json.Marshal(r)
if err != nil {
return err
}
return os.WriteFile(r.Filename+".bgrd", data, 0644)
}
return nil
}
func (r *Redo) reform() error {
r.Lock()
defer r.Unlock()
if !r.rangeUpdated {
return nil
}
tmp, err := r.uniformRange(r.Range)
if err != nil {
return err
}
r.Range = tmp
return nil
}
func (r *Redo) uniformRange(rg []Range) ([]Range, error) {
return uniformRange(rg)
}
func (r *Redo) ReverseRange() ([]Range, error) {
r.reform()
r.RLock()
defer r.RUnlock()
return r.uniformRange(subRange([]Range{{0, r.ContentLength - 1}}, r.Range))
}

141
mget/util.go Normal file
View File

@ -0,0 +1,141 @@
package mget
import (
"b612.me/staros"
"context"
"fmt"
"io"
"net/http"
"os"
"path"
"regexp"
"runtime"
"sync/atomic"
"time"
)
func parseContentRange(contentRange string) (start, end, total int64, err error) {
_, err = fmt.Sscanf(contentRange, "bytes %d-%d/%d", &start, &end, &total)
return
}
func GetFileName(resp *http.Response) string {
fname := getFileName(resp)
var idx = 0
for {
idx++
if staros.Exists(fname) {
if staros.Exists(fname + ".bgrd") {
return fname
}
fname = fmt.Sprintf("%s.%d", fname, idx)
} else {
break
}
}
return fname
}
func getFileName(resp *http.Response) string {
// 尝试从Content-Disposition头中提取文件名
contentDisposition := resp.Header.Get("Content-Disposition")
if contentDisposition != "" {
// 使用正则表达式提取文件名
re := regexp.MustCompile(`(?i)^attachment; filename="?(?P<filename>[^;"]+)`)
matches := re.FindStringSubmatch(contentDisposition)
if len(matches) > 1 {
// 提取命名的捕获组
for i, name := range re.SubexpNames() {
if name == "filename" {
return matches[i]
}
}
}
}
// 提取路径中的最后一个元素作为文件名
return path.Base(resp.Request.URL.Path)
}
func IOWriter(stopCtx context.Context, ch chan Buffer, state *uint32, di *downloadinfo, reader io.ReadCloser, bufSize int, start *int64, end *int64) error {
defer reader.Close()
for {
buf := make([]byte, bufSize)
select {
case <-stopCtx.Done():
return nil
default:
if atomic.LoadUint32(state) == 1 {
runtime.Gosched()
time.Sleep(time.Millisecond)
continue
}
n, err := reader.Read(buf)
if n > 0 {
ch <- Buffer{Data: buf[:n], Start: uint64(*start)}
*start += int64(n)
di.AddCurrent(int64(n))
}
if *end != 0 && *start >= *end {
return nil
}
if err != nil {
if err == io.EOF {
return nil
}
return err
}
}
}
}
func createFileWithSize(filename string, size int64) (*os.File, error) {
file, err := os.Create(filename)
if err != nil {
return nil, err
}
if size == 0 {
return file, nil
}
// 调整文件指针到指定大小位置
if _, err = file.Seek(size-1, 0); err != nil {
return nil, err
}
// 写入一个空字节,以确保文件达到所需大小
if _, err = file.Write([]byte{0}); err != nil {
return nil, err
}
return file, nil
}
func CloneHeader(original http.Header) http.Header {
newHeader := make(http.Header)
for key, values := range original {
copiedValues := make([]string, len(values))
copy(copiedValues, values)
newHeader[key] = copiedValues
}
return newHeader
}
func CloneCookies(original []*http.Cookie) []*http.Cookie {
cloned := make([]*http.Cookie, len(original))
for i, cookie := range original {
cloned[i] = &http.Cookie{
Name: cookie.Name,
Value: cookie.Value,
Path: cookie.Path,
Domain: cookie.Domain,
Expires: cookie.Expires,
RawExpires: cookie.RawExpires,
MaxAge: cookie.MaxAge,
Secure: cookie.Secure,
HttpOnly: cookie.HttpOnly,
SameSite: cookie.SameSite,
Raw: cookie.Raw,
Unparsed: append([]string(nil), cookie.Unparsed...),
}
}
return cloned
}

581
mget/wget.go Normal file
View File

@ -0,0 +1,581 @@
package mget
import (
"b612.me/stario"
"b612.me/starnet"
"b612.me/staros"
"context"
"encoding/json"
"fmt"
"os"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"
)
type Mget struct {
Setting starnet.Request
Redo
//本地文件地址
Tareget string
//本地文件大小
TargetSize int64
//redo文件最大丢数据量
RedoRPO int
NoWriteRedo bool
RedoAutoRpo bool
RedoMinSaveSec int
//单个buffer大小
BufferSize int
//并发下载线程数
dynLength bool
Thread int `json:"thread"`
tf *os.File
ch chan Buffer
ctx context.Context
fn context.CancelFunc
wg sync.WaitGroup
threads []*downloader
lastUndoInfo []Range
writeError error
writeEnable bool
processEnable bool
speedlimit int64
}
type Buffer struct {
Data []byte
Start uint64
}
func (w *Mget) Clone() *starnet.Request {
req := starnet.NewSimpleRequest(w.Setting.Uri(), w.Setting.Method())
req.SetHeaders(CloneHeader(w.Setting.Headers()))
req.SetCookies(CloneCookies(w.Setting.Cookies()))
req.SetSkipTLSVerify(w.Setting.SkipTLSVerify())
req.SetProxy(w.Setting.Proxy())
if w.Setting.DialTimeout() > 0 {
req.SetDialTimeout(w.Setting.DialTimeout())
}
if w.Setting.Timeout() > 0 {
req.SetTimeout(w.Setting.Timeout())
}
if u, p := w.Setting.BasicAuth(); u != "" || p != "" {
req.SetBasicAuth(u, p)
}
return req
}
func (w *Mget) IsUrl206() (*starnet.Response, bool, error) {
req := w.Clone()
req.SetHeader("Range", "bytes=0-")
res, err := req.Do()
if err != nil {
return nil, false, err
}
if res.StatusCode == 206 {
return res, true, nil
}
return res, false, nil
}
func (w *Mget) prepareRun(res *starnet.Response, is206 bool) error {
var err error
length := res.Header.Get("Content-Length")
if length == "" {
length = "0"
w.dynLength = true
is206 = false
}
w.TargetSize, err = strconv.ParseInt(length, 10, 64)
if err != nil {
return fmt.Errorf("parse content length error: %w", err)
}
if w.Tareget == "" {
w.Tareget = GetFileName(res.Response)
}
fmt.Println("Will write to:", w.Tareget)
fmt.Println("Size:", w.TargetSize)
fmt.Println("Is206:", is206)
fmt.Println("IsDynLen:", w.dynLength)
if !is206 {
w.Thread = 1
}
w.Redo = Redo{
Filename: w.Tareget,
ContentLength: uint64(w.TargetSize),
OriginUri: w.Setting.Uri(),
Date: time.Now(),
Is206: is206,
}
fmt.Println("Threads:", w.Thread)
if staros.Exists(w.Tareget + ".bgrd") {
fmt.Println("Found redo file, try to recover...")
var redo Redo
data, err := os.ReadFile(w.Tareget + ".bgrd")
if err != nil {
return fmt.Errorf("read redo file error: %w", err)
}
err = json.Unmarshal(data, &redo)
if err != nil {
return fmt.Errorf("unmarshal redo file error: %w", err)
}
redo.reform()
if redo.ContentLength != w.Redo.ContentLength {
fmt.Println("Content length not match, redo file may be invalid, ignore it")
return nil
}
if redo.OriginUri != w.Redo.OriginUri {
fmt.Println("Origin uri not match, redo file may be invalid, ignore it")
return nil
}
w.Redo = redo
w.Redo.isRedo = true
w.lastUndoInfo, err = w.Redo.ReverseRange()
if err != nil {
return fmt.Errorf("reverse redo range error: %w", err)
}
fmt.Println("Recover redo file success,process:", w.Redo.FormatPercent())
}
return nil
}
func (w *Mget) Run() error {
var err error
var res *starnet.Response
var is206 bool
w.ctx, w.fn = context.WithCancel(context.Background())
w.ch = make(chan Buffer)
defer w.fn()
w.threads = make([]*downloader, w.Thread)
if w.Setting.Uri() == "" {
w.Setting.SetUri(w.OriginUri)
w.Setting.SetMethod("GET")
}
for {
res, is206, err = w.IsUrl206()
if err != nil {
return fmt.Errorf("check 206 error: %w", err)
}
err = w.prepareRun(res, is206)
if err != nil {
return fmt.Errorf("prepare run error: %w", err)
}
if res.StatusCode != 206 && res.StatusCode != 200 {
return fmt.Errorf("Server return %d", res.StatusCode)
}
if !is206 {
go func() {
w.writeEnable = true
w.writeError = w.WriteServer()
w.writeEnable = false
}()
var di = &downloader{
alive: true,
downloadinfo: &downloadinfo{
Start: 0,
End: w.TargetSize - 1,
Size: w.TargetSize,
},
}
if w.dynLength {
di.End = 0
}
w.writeEnable = true
w.threads[0] = di
w.Thread = 1
go w.Process()
state := uint32(0)
err = IOWriter(w.ctx, w.ch, &state, di.downloadinfo, res.Body().Reader(), w.BufferSize, &di.Start, &di.End)
di.alive = false
if err == nil {
w.writeEnable = false
stario.WaitUntilTimeout(time.Second*2,
func(c chan struct{}) error {
for {
if w.processEnable {
time.Sleep(time.Millisecond * 50)
continue
}
return nil
}
})
return nil
}
return err
} else {
res.Body().Close()
}
break
}
go func() {
w.writeEnable = true
w.writeError = w.WriteServer()
w.writeEnable = false
}()
if w.TargetSize == 0 {
return nil
}
for i := 0; i < w.Thread; i++ {
w.wg.Add(1)
go w.dispatch(i)
}
go w.Process()
w.wg.Wait()
time.Sleep(2 * time.Microsecond)
var once sync.Once
for {
if w.writeEnable {
once.Do(w.fn)
time.Sleep(time.Millisecond * 50)
continue
}
if w.writeError != nil {
if !w.NoWriteRedo {
err = w.Redo.Save()
}
return fmt.Errorf("write error: %w %v", w.writeError, err)
}
break
}
once.Do(w.fn)
stario.WaitUntilTimeout(time.Second*2,
func(c chan struct{}) error {
for {
if w.processEnable {
time.Sleep(time.Millisecond * 50)
continue
}
return nil
}
})
r, err := w.ReverseRange()
if err != nil {
return err
}
if !w.NoWriteRedo {
if len(r) == 0 {
if staros.Exists(w.Tareget + ".bgrd") {
return os.Remove(w.Tareget + ".bgrd")
}
return nil
}
return w.Redo.Save()
}
return nil
}
func (w *Mget) dispatch(idx int) error {
defer w.wg.Done()
var start, end int64
if len(w.lastUndoInfo) == 0 {
count := w.TargetSize / int64(w.Thread)
start = count * int64(idx)
end = count*int64(idx+1) - 1
if idx == w.Thread-1 {
end = w.TargetSize - 1
}
} else {
w.Lock()
if len(w.lastUndoInfo) == 0 {
d := &downloader{}
w.threads[idx] = d
w.Unlock()
goto morejob
}
start = int64(w.lastUndoInfo[0].Min)
end = int64(w.lastUndoInfo[0].Max)
w.lastUndoInfo = w.lastUndoInfo[1:]
w.Unlock()
}
for {
req := w.Clone()
req.SetCookies(CloneCookies(w.Setting.Cookies()))
d := &downloader{
Request: req,
ch: w.ch,
ctx: w.ctx,
bufferSize: w.BufferSize,
downloadinfo: &downloadinfo{
Start: start,
End: end,
},
}
w.threads[idx] = d
if err := d.Run(); err != nil {
fmt.Printf("thread %d error: %v\n", idx, err)
if d.Start >= d.End {
break
}
start = d.Start
end = d.End
continue
}
break
}
morejob:
for {
w.Lock()
if len(w.lastUndoInfo) > 0 {
w.threads[idx].Start = int64(w.lastUndoInfo[idx].Min)
w.threads[idx].End = int64(w.lastUndoInfo[idx].Max)
w.lastUndoInfo = w.lastUndoInfo[1:]
w.Unlock()
} else {
w.Unlock()
if !w.RequestNewTask(w.threads[idx]) {
break
}
}
for {
req := w.Clone()
req.SetCookies(CloneCookies(w.Setting.Cookies()))
d := &downloader{
Request: req,
ch: w.ch,
ctx: w.ctx,
bufferSize: w.BufferSize,
downloadinfo: &downloadinfo{
Start: w.threads[idx].Start,
End: w.threads[idx].End,
},
}
w.threads[idx] = d
if err := d.Run(); err != nil {
fmt.Printf("thread %d error: %v\n", idx, err)
if d.Start >= d.End {
break
}
start = d.Start
end = d.End
continue
}
break
}
}
return nil
}
func (w *Mget) getSleepTime() time.Duration {
if w.speedlimit == 0 {
return 0
}
return time.Nanosecond * time.Duration(16384*1000*1000*1000/w.speedlimit) / 2
}
func (w *Mget) WriteServer() error {
var err error
defer w.fn()
if !w.isRedo {
w.tf, err = createFileWithSize(w.Tareget, w.TargetSize)
} else {
w.tf, err = os.OpenFile(w.Tareget, os.O_RDWR, 0666)
}
if err != nil {
return err
}
lastUpdateRange := 0
currentRange := 0
currentCount := int64(0)
lastDate := time.Now()
lastCount := int64(0)
speedControl := func(count int) {
if w.speedlimit == 0 {
return
}
currentCount += int64(count)
for {
if time.Since(lastDate) < time.Second {
if currentCount-lastCount > w.speedlimit {
time.Sleep(w.getSleepTime())
} else {
break
}
} else {
lastDate = time.Now()
lastCount = currentCount
break
}
}
}
if w.RedoRPO == 0 {
w.RedoAutoRpo = true
w.RedoRPO = 1024 * 1024 * 1024
}
for {
select {
case <-w.ctx.Done():
return nil
case b := <-w.ch:
n, err := w.tf.WriteAt(b.Data, int64(b.Start))
if err != nil {
fmt.Println("write error:", err)
return err
}
speedControl(n)
if w.dynLength {
w.ContentLength += uint64(n)
}
currentRange += n
end := b.Start + uint64(n) - 1
err = w.Update(int(b.Start), int(end))
if err != nil {
return err
}
if !w.NoWriteRedo && currentRange-lastUpdateRange >= w.RedoRPO && time.Now().After(w.Redo.lastCallSave.Add(time.Second*time.Duration(w.RedoMinSaveSec))) {
w.tf.Sync()
go w.Redo.Save()
if w.RedoAutoRpo {
w.RedoRPO = int(w.Redo.Speed()) * 2
}
lastUpdateRange = currentRange
}
}
}
}
type downloader struct {
*starnet.Request
alive bool
ch chan Buffer
ctx context.Context
state uint32
bufferSize int
*downloadinfo
}
func (d *downloader) Run() error {
d.alive = true
defer func() {
d.alive = false
}()
d.SetHeader("Range", fmt.Sprintf("bytes=%d-%d", d.Start, d.End))
res, err := d.Do()
if err != nil {
return err
}
if res.Header.Get("Content-Range") == "" {
return fmt.Errorf("server not support range")
}
start, end, _, err := parseContentRange(res.Header.Get("Content-Range"))
if d.Start != start {
return fmt.Errorf("server not support range")
}
d.End = end
d.downloadinfo = &downloadinfo{
Start: d.Start,
End: d.End,
Size: d.End - d.Start + 1,
}
reader := res.Body().Reader()
return IOWriter(d.ctx, d.ch, &d.state, d.downloadinfo, reader, d.bufferSize, &d.Start, &d.End)
}
func (w *Mget) RequestNewTask(task *downloader) bool {
//stop thhe world first
w.Lock()
defer w.Unlock()
defer func() {
for _, v := range w.threads {
if v != nil {
atomic.StoreUint32(&v.state, 0)
}
}
}()
var maxThread *downloader
for _, v := range w.threads {
if v != nil {
atomic.StoreUint32(&v.state, 1)
}
}
time.Sleep(time.Microsecond * 2)
for _, v := range w.threads {
if v == nil {
continue
}
if maxThread == nil {
maxThread = v
continue
}
if v.End-v.Start > maxThread.End-maxThread.Start {
maxThread = v
}
}
if maxThread == nil || maxThread.End <= maxThread.Start {
return false
}
if (maxThread.End-maxThread.Start)/2 < int64(w.BufferSize*2) || (maxThread.End-maxThread.Start)/2 < 100*1024 {
return false
}
task.End = maxThread.End
maxThread.End = maxThread.Start + (maxThread.End-maxThread.Start)/2
task.Start = maxThread.End + 1
//fmt.Printf("thread got new task %d-%d\n", task.Start, task.End)
return true
}
type downloadinfo struct {
Start int64
End int64
Size int64
current int64
lastCurrent int64
lastTime time.Time
speed float64
}
func (d *downloadinfo) Current() int64 {
return d.current
}
func (d *downloadinfo) Percent() float64 {
return float64(d.current) / float64(d.Size)
}
func (d *downloadinfo) FormatPercent() string {
return fmt.Sprintf("%.2f%%", d.Percent()*100)
}
func (d *downloadinfo) SetCurrent(info int64) {
d.current = info
now := time.Now()
if now.Sub(d.lastTime) >= time.Millisecond*500 {
d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00)
d.lastCurrent = d.current
d.lastTime = time.Now()
}
}
func (d *downloadinfo) AddCurrent(info int64) {
d.current += info
now := time.Now()
if now.Sub(d.lastTime) >= time.Millisecond*500 {
d.speed = float64(d.current-d.lastCurrent) / (float64(now.Sub(d.lastTime).Milliseconds()) / 1000.00)
d.lastCurrent = d.current
d.lastTime = time.Now()
}
}
func (d *downloadinfo) FormatSpeed(unit string) string {
switch strings.ToLower(unit) {
case "kb":
return fmt.Sprintf("%.2f KB/s", d.speed/1024)
case "mb":
return fmt.Sprintf("%.2f MB/s", d.speed/1024/1024)
case "gb":
return fmt.Sprintf("%.2f GB/s", d.speed/1024/1024/1024)
default:
return fmt.Sprintf("%.2f B/s", d.speed)
}
}
func (d *downloadinfo) Speed() float64 {
return d.speed
}

35
mget/wget_test.go Normal file
View File

@ -0,0 +1,35 @@
package mget
import (
"b612.me/starnet"
"fmt"
"testing"
)
func TestWget(t *testing.T) {
r := starnet.NewSimpleRequest("http://192.168.2.33:88/DJI_0746.MP4", "GET")
w := Mget{
Setting: *r,
RedoRPO: 1048576,
BufferSize: 8192,
Thread: 8,
}
if err := w.Run(); err != nil {
t.Fatal(err)
}
}
func TestSM(t *testing.T) {
a := map[string]string{
"1": "1",
"2": "2",
}
modify(a)
fmt.Println(a)
}
func modify(a map[string]string) {
b := make(map[string]string)
b = a
b["1"] = "3"
}

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)
@ -81,6 +87,7 @@ func init() {
CmdNatThrough.Flags().BoolVarP(&natt.AutoUPnP, "auto-upnp", "u", true, "自动UPnP")
CmdNatThrough.Flags().IntVarP(&natt.WebPort, "web-port", "w", 8080, "web端口")
CmdNatThrough.Flags().IntVarP(&natt.HealthCheckInterval, "health-check-interval", "H", 30, "健康检查间隔")
CmdNatThrough.Flags().StringVarP(&natt.Type, "type", "t", "tcp", "穿透协议tcp或udp")
Cmd.AddCommand(CmdNatThrough)
CmdScanIP.Flags().StringVarP(&scanip.Host, "ip", "i", "", "扫描IP地址")
@ -102,6 +109,18 @@ func init() {
CmdScanPort.Flags().IntVarP(&scanport.Retry, "retry", "r", 2, "重试次数")
Cmd.AddCommand(CmdScanPort)
CmdMonitorIP.Flags().StringSliceVarP(&monitorip.IPs, "ip", "i", []string{}, "扫描IP地址列表")
CmdMonitorIP.Flags().IntVarP(&monitorip.Port, "port", "p", 80, "TCP模式扫描端口")
CmdMonitorIP.Flags().IntVarP(&monitorip.Timeout, "timeout", "t", 1200, "超时时间,毫秒")
CmdMonitorIP.Flags().IntVarP(&monitorip.Interval, "interval", "I", 1, "扫描间隔,秒")
CmdMonitorIP.Flags().IntVarP(&monitorip.Threads, "threads", "m", 100, "最大线程数")
CmdMonitorIP.Flags().StringVarP(&monitorip.Log, "log", "l", "", "日志文件地址")
CmdMonitorIP.Flags().StringVarP(&monitorip.ScanType, "type", "T", "icmp", "扫描类型")
CmdMonitorIP.Flags().IntVarP(&monitorip.Retry, "retry", "r", 1, "重试次数")
CmdMonitorIP.Flags().BoolVarP(&monitorip.WithHostname, "with-hostname", "H", false, "显示主机名")
Cmd.AddCommand(CmdMonitorIP)
Cmd.AddCommand(tcpkill.Cmd, tcping.Cmd, tcm.Cmd)
}
var CmdNatPClient = &cobra.Command{
@ -216,6 +235,30 @@ var CmdScanIP = &cobra.Command{
},
}
var CmdMonitorIP = &cobra.Command{
Use: "monitorip",
Short: "监控IP",
Run: func(cmd *cobra.Command, args []string) {
if len(monitorip.IPs) == 0 {
cmd.Help()
return
}
monitorip.status = make(map[string]bool)
monitorip.stopChan = make(chan struct{})
sig := make(chan os.Signal)
signal.Notify(sig, os.Interrupt, os.Kill)
go func() {
err := monitorip.Start()
if err != nil {
starlog.Errorln(err)
os.Exit(1)
}
}()
<-sig
monitorip.Stop()
},
}
var CmdScanPort = &cobra.Command{
Use: "scanport",
Short: "扫描端口",

183
net/icmp.go Normal file
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

@ -3,6 +3,7 @@ package net
import (
"b612.me/apps/b612/netforward"
"b612.me/starlog"
"b612.me/starmap"
"context"
"encoding/binary"
"encoding/json"
@ -27,6 +28,7 @@ type NatThroughs struct {
STUN string
Remote string
HealthCheckInterval int
Type string
}
func (n *NatThroughs) Close() {
@ -48,19 +50,23 @@ func (n *NatThroughs) Parse(reqs []string) error {
if n.STUN == "" {
n.STUN = "turn.b612.me:3478"
}
if n.Type == "" {
n.Type = "tcp"
}
for _, v := range reqs {
var req = NatThrough{
Forward: netforward.NetForward{
LocalAddr: "0.0.0.0",
DialTimeout: time.Second * 5,
UDPTimeout: time.Second * 20,
UDPTimeout: time.Second * 30,
KeepAlivePeriod: n.KeepAlivePeriod,
KeepAliveIdel: n.KeepAliveIdel,
KeepAliveCount: n.KeepAliveCount,
UsingKeepAlive: true,
EnableTCP: true,
EnableUDP: true,
},
Type: "tcp",
Type: n.Type,
STUN: n.STUN,
Remote: n.Remote,
KeepAlivePeriod: n.KeepAlivePeriod,
@ -72,7 +78,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
strs := strings.Split(v, ",")
switch len(strs) {
case 1:
req.Type = "tcp"
req.Forward.RemoteURI = strs[0]
case 2:
ipport := strings.Split(strs[0], ":")
@ -90,7 +95,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
}
req.Forward.LocalPort = port
}
req.Type = "tcp"
req.Forward.RemoteURI = strs[1]
case 3:
ipport := strings.Split(strs[1], ":")
@ -108,7 +112,6 @@ func (n *NatThroughs) Parse(reqs []string) error {
}
req.Forward.LocalPort = port
}
req.Type = "tcp"
req.Forward.RemoteURI = strs[2]
req.Name = strs[0]
case 4:
@ -144,7 +147,7 @@ func (n *NatThroughs) Run() error {
go func(v *NatThrough) {
defer wg.Done()
if err := v.Run(); err != nil {
starlog.Errorf("Failed to run forward: %v\n", err)
starlog.Errorf("Failed to run natThrough: %v\n", err)
}
v.HealthCheck()
}(v)
@ -262,6 +265,7 @@ func (c *NatThrough) Run() error {
c.Forward.EnableTCP = false
c.Forward.EnableUDP = true
}
starlog.Infof("NatThrough Type: %s\n", c.Type)
starlog.Infof("Local Port: %d\n", c.Forward.LocalPort)
starlog.Infof("Keepalive To: %s\n", c.Remote)
starlog.Infof("Forward To: %s\n", c.Forward.RemoteURI)
@ -294,7 +298,7 @@ func (c *NatThrough) Run() error {
}
go func() {
if err := c.KeepAlive(c.Forward.LocalAddr, c.Forward.LocalPort); err != nil {
starlog.Errorf("Failed to run forward: %v\n", err)
starlog.Errorf("Failed to run keepalive: %v\n", err)
c.Forward.Close()
c.stopFn()
}
@ -319,6 +323,26 @@ func (c *NatThrough) Run() error {
}
func (c *NatThrough) HealthCheck() {
getIP := func(ip net.Addr) string {
switch ip.(type) {
case *net.TCPAddr:
return ip.(*net.TCPAddr).IP.String()
case *net.UDPAddr:
return ip.(*net.UDPAddr).IP.String()
default:
return ""
}
}
getPort := func(ip net.Addr) int {
switch ip.(type) {
case *net.TCPAddr:
return ip.(*net.TCPAddr).Port
case *net.UDPAddr:
return ip.(*net.UDPAddr).Port
default:
return 0
}
}
count := 0
if c.HealthCheckInterval == 0 {
c.HealthCheckInterval = 30
@ -330,16 +354,30 @@ func (c *NatThrough) HealthCheck() {
case <-time.After(time.Second * time.Duration(c.HealthCheckInterval)):
}
if c.Type == "udp" {
continue
}
conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
if err != nil {
starlog.Warningf("Health Check Fail: %v\n", err)
count++
_, extIp, err := c.UdpKeppaliveSTUN(c.Forward.UdpListener(), c.STUN)
if err != nil {
count++
starlog.Errorf("Health Check Error: %v\n", err)
continue
}
extUrl := fmt.Sprintf("%s:%d", getIP(extIp), getPort(extIp))
if c.ExtUrl != extUrl {
count++
} else {
count = 0
}
starlog.Noticef("Health Check:Origin %s,Current %s\n", c.ExtUrl, extUrl)
} else {
starlog.Infof("Health Check Ok\n")
conn.(*net.TCPConn).SetLinger(0)
conn.Close()
conn, err := net.DialTimeout("tcp", c.ExtUrl, time.Second*2)
if err != nil {
starlog.Warningf("Health Check Fail: %v\n", err)
count++
} else {
count = 0
starlog.Infof("Health Check Ok\n")
conn.(*net.TCPConn).SetLinger(0)
conn.Close()
}
}
if count >= 3 {
count = 0
@ -412,20 +450,20 @@ func (c *NatThrough) KeepAlive(localAddr string, localPort int) error {
if err != nil {
return err
}
conn, err := net.DialUDP("udp", &net.UDPAddr{IP: net.ParseIP(localAddr), Port: localPort}, rmtUdpAddr)
if err != nil {
starlog.Errorf("Failed to dial remote: %v\n", err)
if c.Forward.UdpListener() == nil {
time.Sleep(time.Second * 5)
continue
}
c.keepaliveConn = conn
c.keepaliveConn = c.Forward.UdpListener()
for {
_, err = conn.Write([]byte("b612 tcp nat through"))
_, err = c.Forward.UdpListener().WriteTo([]byte("b612 udp nat through"), rmtUdpAddr)
if err != nil {
conn.Close()
c.keepaliveConn.Close()
starlog.Warningf("Failed to keepalive remote: %v\n", err)
time.Sleep(time.Second * 30)
break
}
starlog.Infof("UDP Keepalive Ok! %v\n", rmtUdpAddr.String())
time.Sleep(time.Second * 30)
}
}
@ -482,6 +520,7 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
conn.(*net.TCPConn).SetLinger(0)
}
if netType == "udp" {
conn, err = net.DialUDP(netType, &net.UDPAddr{IP: net.ParseIP(localip), Port: localPort}, stunAddr)
if err != nil {
return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
@ -551,6 +590,90 @@ func (c *NatThrough) GetIPPortFromSTUN(netType string, localip string, localPort
return innerAddr, outerAddr, nil
}
func (c *NatThrough) UdpKeppaliveSTUN(conn *net.UDPConn, stunServer string) (net.Addr, net.Addr, error) {
// 替换为你的 TURN 服务器地址
var target *starmap.StarStack
{
tmpConn, err := net.Dial("udp", stunServer)
if err != nil {
return nil, nil, fmt.Errorf("failed to connect to STUN server: %v", err)
}
if c.Forward.UdpHooks == nil {
c.Forward.UdpHooks = make(map[string]*starmap.StarStack)
}
if c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] == nil {
c.Forward.UdpHooks[tmpConn.RemoteAddr().String()] = starmap.NewStarStack(16)
}
target = c.Forward.UdpHooks[tmpConn.RemoteAddr().String()]
tmpConn.Close()
}
stunAddr, err := net.ResolveUDPAddr("udp", stunServer)
if err != nil {
return nil, nil, fmt.Errorf("failed to resolve STUN server address: %v", err)
}
innerAddr := conn.LocalAddr()
// Create STUN request
transactionID := make([]byte, 12)
rand.Read(transactionID)
stunRequest := make([]byte, 20)
binary.BigEndian.PutUint16(stunRequest[0:], 0x0001) // Message Type: Binding Request
binary.BigEndian.PutUint16(stunRequest[2:], 0x0000) // Message Length
copy(stunRequest[4:], []byte{0x21, 0x12, 0xa4, 0x42}) // Magic Cookie
copy(stunRequest[8:], transactionID) // Transaction ID
_, err = conn.WriteToUDP(stunRequest, stunAddr)
if err != nil {
return nil, nil, fmt.Errorf("failed to send STUN request: %v", err)
}
time.Sleep(time.Millisecond * 2500)
tmp, err := target.Pop()
if err != nil {
return nil, nil, fmt.Errorf("failed to receive STUN response: %v", err)
}
buf := tmp.([]byte)
n := len(buf)
// Parse STUN response
if n < 20 {
return nil, nil, fmt.Errorf("invalid STUN response")
}
payload := buf[20:n]
var ip uint32
var port uint16
for len(payload) > 0 {
attrType := binary.BigEndian.Uint16(payload[0:])
attrLen := binary.BigEndian.Uint16(payload[2:])
if len(payload) < int(4+attrLen) {
return nil, nil, fmt.Errorf("invalid STUN attribute length")
}
if attrType == 0x0001 || attrType == 0x0020 {
port = binary.BigEndian.Uint16(payload[6:])
ip = binary.BigEndian.Uint32(payload[8:])
if attrType == 0x0020 {
port ^= 0x2112
ip ^= 0x2112a442
}
break
}
payload = payload[4+attrLen:]
}
if ip == 0 || port == 0 {
return nil, nil, fmt.Errorf("invalid STUN response")
}
outerAddr := &net.UDPAddr{
IP: net.IPv4(byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)),
Port: int(port),
}
return innerAddr, outerAddr, nil
}
func (c *NatThrough) GetMyOutIP() string {
tmp, err := net.Dial("udp", "8.8.8.8:53")
if err != nil {

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)
@ -79,13 +118,34 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
data[args[0]] = args[1]
}
}
starnet.Curl(starnet.NewRequests(curlUrl, []byte(starnet.BuildQuery(data)), "POST"))
starnet.NewSimpleRequest(curlUrl, "POST").SetBodyDataBytes([]byte(starnet.BuildQuery(data))).Do()
}
}()
}
perm := &ssh.Permissions{
Extensions: map[string]string{
"user": c.User(),
"passwd": string(pass),
},
}
if jar.allowAny {
return perm, nil
}
for _, v := range jar.passwds {
if c.User() == v[0] && string(pass) == v[1] {
return perm, nil
}
}
return nil, fmt.Errorf("password rejected for %q", c.User())
},
PublicKeyCallback: func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
if jar.allowAny {
return &ssh.Permissions{
Extensions: map[string]string{
"user": conn.User(),
},
}, nil
}
return nil, fmt.Errorf("public key rejected for %q", conn.User())
},
}
@ -132,9 +192,179 @@ func runSSHHoneyJar(listenAddr, keyFile, KeyPasswd, outpath, version string) {
}
starlog.Infof("New connection from %s\n", conn.RemoteAddr())
go func(conn net.Conn) {
ssh.NewServerConn(conn, config)
conn.Close()
sConn, chans, reqs, err := ssh.NewServerConn(conn, config)
if err != nil {
starlog.Errorf("SSH handshake failed: %v\n", err)
return
}
defer sConn.Close()
defer starlog.Noticef("Connection from %s closed\n", sConn.RemoteAddr())
go ssh.DiscardRequests(reqs)
for newChannel := range chans {
if newChannel.ChannelType() != "session" {
newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
continue
}
channel, requests, err := newChannel.Accept()
if err != nil {
starlog.Errorf("Failed to accept channel: %v", err)
continue
}
go handleSession(channel, requests, sConn)
}
}(conn)
}
}
func handleSession(channel ssh.Channel, requests <-chan *ssh.Request, conn *ssh.ServerConn) {
defer channel.Close()
term := terminal.NewTerminal(channel, "$ ") // 设置 shell 提示符
term.AutoCompleteCallback = nil // 禁用自动补全
for req := range requests {
switch req.Type {
case "pty-req":
// 接受伪终端请求(攻击者希望获得交互式体验)
req.Reply(true, nil)
term.SetSize( // 简单设置终端尺寸
24, // 行
80, // 列
)
case "shell":
req.Reply(true, nil)
go func() {
for {
line, err := term.ReadLine()
if err != nil {
break
}
starlog.Infof("[%s %s] Command: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], line)
time.Sleep(time.Millisecond * 200)
term.Write([]byte(FakeCommand(line)))
}
}()
case "exec":
// 处理非交互式命令(如 ssh user@host 'ls -l'
var payload struct{ Command string }
ssh.Unmarshal(req.Payload, &payload)
req.Reply(true, nil)
// 记录并返回假输出
starlog.Infof("[%s %s] Exec: %s\n", conn.RemoteAddr(), conn.Permissions.Extensions["user"], payload.Command)
term.Write([]byte(FakeCommand(payload.Command)))
channel.Close()
default:
req.Reply(false, nil)
}
}
}
func FakeCommand(cmd string) string {
// 按命令类型分级模拟
switch {
//---------------- 系统信息探测类 ----------------
case strings.Contains(cmd, "uname -a"):
return "Linux core 6.1.0-21-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.90-1 (2024-05-03) x86_64 GNU/Linux\n\n"
case strings.Contains(cmd, "cat /etc/os-release"):
return `PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
`
case strings.Contains(cmd, "free -h"):
return ` total used free shared buff/cache available
Mem: 1022Gi 12Gi 3.0Gi 1.0Gi 47Gi 480Gi
Swap: 0B 0B 0B
`
//---------------- 敏感文件诱导类 ----------------
case strings.Contains(cmd, "ls /home"):
return "admin backup devops secret\n"
case strings.Contains(cmd, "ls /var/log"):
return `auth.log apache2 payment_system.log database_backup.log
`
case strings.Contains(cmd, "ls"):
return "password.txt\n"
case strings.Contains(cmd, "cat /etc/passwd"):
return `root:x:0:0:root:/root:/bin/bash
admin:x:1000:1000:,,,:/home/admin:/bin/bash
mysql:x:106:113:MySQL Server,,,:/nonexistent:/bin/false
core:x:0:0:,,,:/root:/bin/bash
`
//---------------- 网络配置诱导类 ----------------
case strings.Contains(cmd, "ifconfig") || strings.Contains(cmd, "ip addr"):
return `eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.1.105 netmask 255.255.255.0 broadcast 192.168.1.255
inet6 fe80::250:56ff:fec0:8888 prefixlen 64 scopeid 0x20<link>
ether 00:50:56:c0:88:88 txqueuelen 1000 (Ethernet)
RX packets 123456 bytes 123456789 (117.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 98765 bytes 9876543 (9.4 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1000 (Local Loopback)
`
case strings.Contains(cmd, "netstat -antp"):
return `Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1234/sshd
tcp 0 0 192.168.1.105:5432 203.0.113.5:43892 ESTABLISHED 5678/postgres
tcp6 0 0 :::8080 :::* LISTEN 91011/java
`
//---------------- 凭证钓鱼类 ----------------
case strings.Contains(cmd, "mysql -u root -p"):
return "ERROR 1045 (28000): Access denied\n"
case strings.Contains(cmd, "sudo -l"):
return `Matching Defaults entries for admin on this host:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User admin may run the following commands on honeypot:
(ALL) NOPASSWD: /usr/bin/vim /etc/shadow
`
//---------------- 进程服务类 ----------------
case strings.Contains(cmd, "ps aux"):
return `USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 169020 13184 ? Ss May01 0:12 /sbin/init
admin 1234 0.3 2.1 1123456 178912 ? Sl May01 12:34 /opt/payment_system/payment_processor --debug
`
//---------------- 定制化陷阱 ----------------
case strings.Contains(cmd, "find / -name *.db"):
return `/var/lib/mysql/transactions.db
/home/backup/internal_users.db
`
case strings.Contains(cmd, "curl"):
return `<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>404 Not Found</title>
</head><body>
<h1>Not Found</h1>
<p>The requested URL was not found on this server.</p>
</body></html>
`
default:
// 模糊响应增加真实感
if rand.Intn(100) > 70 { // 30%概率返回"command not found"
return "sh: command not found: " + strings.Fields(cmd)[0] + "\n"
}
return "\n"
}
}

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

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

View File

@ -29,6 +29,7 @@ func init() {
CmdNetforward.Flags().IntVarP(&f.KeepAliveCount, "keepalive-count", "C", 3, "keepalive count")
CmdNetforward.Flags().IntVarP(&f.UserTimeout, "user-timeout", "U", 0, "user timeout (milliseconds)")
CmdNetforward.Flags().BoolVarP(&f.IgnoreEof, "ignore-eof", "E", false, "ignore eof")
CmdNetforward.Flags().BoolVarP(&f.Verbose, "verbose", "v", false, "verbose mode")
}
var CmdNetforward = &cobra.Command{

View File

@ -3,6 +3,7 @@ package netforward
import (
"b612.me/stario"
"b612.me/starlog"
"b612.me/starmap"
"context"
"errors"
"fmt"
@ -31,12 +32,19 @@ type NetForward struct {
stopCtx context.Context
stopFn context.CancelFunc
running int32
UdpHooks map[string]*starmap.StarStack
KeepAlivePeriod int
KeepAliveIdel int
KeepAliveCount int
UserTimeout int
UsingKeepAlive bool
Verbose bool
udpListener *net.UDPConn
}
func (n *NetForward) UdpListener() *net.UDPConn {
return n.udpListener
}
func (n *NetForward) Close() {
@ -194,17 +202,17 @@ type UDPConn struct {
lastbeat int64
}
func (u UDPConn) Write(p []byte) (n int, err error) {
func (u *UDPConn) Write(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Write(p)
}
func (u UDPConn) Read(p []byte) (n int, err error) {
func (u *UDPConn) Read(p []byte) (n int, err error) {
u.lastbeat = time.Now().Unix()
return u.Conn.Read(p)
}
func (u UDPConn) Work(delay int) {
func (u *UDPConn) Work(delay int, verbose bool) {
buf := make([]byte, 8192)
for {
if delay > 0 {
@ -216,7 +224,10 @@ func (u UDPConn) Work(delay int) {
u.lastbeat = 0
return
}
_, err = u.listen.Write(buf[0:count])
if verbose {
fmt.Printf("U %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), u.Conn.RemoteAddr().String(), u.remoteAddr.String(), buf[0:count])
}
_, err = u.listen.WriteTo(buf[0:count], u.remoteAddr)
if err != nil {
u.lastbeat = 0
return
@ -236,12 +247,13 @@ func (n *NetForward) runUDP() error {
if err != nil {
return err
}
n.udpListener = listen
starlog.Infof("Listening UDP on %v\n", fmt.Sprintf("%s:%d", n.LocalAddr, n.LocalPort))
go func() {
<-n.stopCtx.Done()
listen.Close()
}()
udpMap := make(map[string]UDPConn)
udpMap := make(map[string]*UDPConn)
go func() {
for {
select {
@ -270,6 +282,22 @@ func (n *NetForward) runUDP() error {
if err != nil || rmt.String() == n.RemoteURI {
continue
}
{
//hooks
if n.UdpHooks != nil {
if m, ok := n.UdpHooks[rmt.String()]; ok {
if m.Free() > 0 {
if n.Verbose {
starlog.Noticef("Hooked UDP Data %s ==> %s %X\n", rmt.String(), n.RemoteURI, buf[0:count])
} else {
starlog.Noticef("Hooked UDP Data %s ==> %s\n", rmt.String(), n.RemoteURI)
}
m.Push(buf[0:count])
continue
}
}
}
}
go func(data []byte, rmt *net.UDPAddr) {
log := starlog.Std.NewFlag()
mu.Lock()
@ -282,20 +310,23 @@ func (n *NetForward) runUDP() error {
mu.Unlock()
return
}
addr = UDPConn{
addr = &UDPConn{
Conn: conn,
remoteAddr: rmt,
listen: listen,
lastbeat: time.Now().Unix(),
}
udpMap[rmt.String()] = addr
go addr.Work(n.DelayMilSec)
go addr.Work(n.DelayMilSec, n.Verbose)
log.Infof("UDP Connect %s <==> %s\n", rmt.String(), n.RemoteURI)
}
mu.Unlock()
if n.DelayMilSec > 0 || (n.DelayToward == 0 || n.DelayToward == 1) {
time.Sleep(time.Millisecond * time.Duration(n.DelayMilSec))
}
if n.Verbose {
fmt.Printf("T %v Recv Data %s ==> %s %X\n", time.Now().Format("2006-01-02 15:04:05"), rmt.String(), n.RemoteURI, data)
}
_, err := addr.Write(data)
if err != nil {
mu.Lock()
@ -308,6 +339,12 @@ func (n *NetForward) runUDP() error {
}
}
func (n *NetForward) showVerbose(toward, src, dst string, data []byte) {
if n.Verbose {
fmt.Printf("%s %v Recv Data %s ==> %s %X\n", toward, time.Now().Format("2006-01-02 15:04:05"), src, dst, data)
}
}
func (n *NetForward) copy(dst, src net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
@ -324,6 +361,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
src.Close()
return
}
n.showVerbose("T", src.RemoteAddr().String(), dst.RemoteAddr().String(), bufsize[:count])
_, err = dst.Write(bufsize[:count])
if err != nil {
src.Close()
@ -348,6 +386,7 @@ func (n *NetForward) copy(dst, src net.Conn) {
dst.Close()
return
}
n.showVerbose("U", dst.RemoteAddr().String(), src.RemoteAddr().String(), bufsize[:count])
_, err = src.Write(bufsize[:count])
if err != nil {
src.Close()

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"

View File

@ -1,13 +1,18 @@
package whois
import (
"b612.me/sdk/whois"
"b612.me/stario"
"b612.me/starlog"
"b612.me/staros"
"github.com/likexian/whois"
"bufio"
"fmt"
"github.com/spf13/cobra"
"golang.org/x/net/proxy"
"io"
"os"
"strings"
"sync"
"time"
)
@ -16,13 +21,37 @@ var output string
var whoisServer []string
var socks5 string
var socks5Auth string
var showFull bool
var hideFormat bool
var useDict bool
var hideExists bool
var hideNoExists bool
var thread int
var suffix string
var retries int
func init() {
Cmd.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
Cmd.Flags().StringVarP(&output, "output", "o", "", "输出文件夹")
Cmd.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
Cmd.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080")
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "A", "", "socks5代理认证示例username:password")
Cmd.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证示例username:password")
Cmd.Flags().BoolVarP(&showFull, "full", "f", false, "显示完整信息")
Cmd.Flags().BoolVarP(&hideFormat, "hide-format", "g", false, "隐藏格式化信息")
CmdExists.Flags().IntVarP(&timeout, "timeout", "t", 20, "超时时间")
CmdExists.Flags().StringVarP(&output, "output", "o", "", "输出文件")
CmdExists.Flags().BoolVarP(&useDict, "use-dict", "d", false, "使用字典查询")
CmdExists.Flags().StringSliceVarP(&whoisServer, "server", "s", nil, "whois服务器")
CmdExists.Flags().StringVarP(&socks5, "socks5", "p", "", "socks5代理示例127.0.0.1:1080")
CmdExists.Flags().StringVarP(&socks5Auth, "socks5-auth", "a", "", "socks5代理认证示例username:password")
CmdExists.Flags().BoolVarP(&hideExists, "hide-exists", "e", false, "隐藏存在的域名")
CmdExists.Flags().BoolVarP(&hideNoExists, "hide-no-exists", "n", false, "隐藏不存在的域名")
CmdExists.Flags().IntVarP(&thread, "thread", "m", 10, "并发查询数")
CmdExists.Flags().StringVarP(&suffix, "suffix", "x", "", "域名后缀")
CmdExists.Flags().IntVarP(&retries, "retries", "r", 3, "重试次数")
Cmd.AddCommand(CmdExists)
}
var Cmd = &cobra.Command{
@ -35,7 +64,6 @@ var Cmd = &cobra.Command{
return
}
if !staros.Exists(output) {
cmd.Println("输出文件夹不存在,将使用标准输出")
output = ""
}
c := whois.NewClient()
@ -70,9 +98,238 @@ var Cmd = &cobra.Command{
cmd.Println("-----------------------------------------------------")
continue
}
cmd.Println(data)
if !hideFormat && !data.Exists() {
fmt.Printf("域名:\t%s 不存在\n", data.Domain())
}
if !hideFormat && data.Exists() {
fmt.Printf("域名名称:\t%s\n", data.Domain())
fmt.Printf("注册商:\t%s\n", data.Registar())
if data.HasRegisterDate() {
fmt.Printf("注册时间:\t%s\n", data.RegisterDate())
}
if data.HasExpireDate() {
fmt.Printf("到期时间:\t%s\n", data.ExpireDate())
}
if data.HasUpdateDate() {
fmt.Printf("更新时间:\t%s\n", data.UpdateDate())
}
for _, v := range data.Status() {
fmt.Printf("域名状态:\t%s\n", v)
}
if data.IanaID() != "" {
fmt.Printf("IANA ID\t%s\n", data.IanaID())
}
if data.Dnssec() != "" {
fmt.Printf("DNSSEC\t%s\n", data.Dnssec())
}
isShowContact := false
if data.RegisterInfo().Name != "" {
fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
isShowContact = true
}
if data.RegisterInfo().State != "" {
fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
isShowContact = true
}
if data.RegisterInfo().Country != "" {
fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
isShowContact = true
}
if data.RegisterInfo().Email != "" {
fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
isShowContact = true
}
if !isShowContact {
if data.AdminInfo().Name != "" {
fmt.Printf("注册者:\t%s\n", data.RegisterInfo().Name)
isShowContact = true
}
if data.AdminInfo().State != "" {
fmt.Printf("注册省份:\t%s\n", data.RegisterInfo().State)
isShowContact = true
}
if data.AdminInfo().Country != "" {
fmt.Printf("注册国家:\t%s\n", data.RegisterInfo().Country)
isShowContact = true
}
if data.AdminInfo().Email != "" {
fmt.Printf("注册邮箱:\t%s\n", data.RegisterInfo().Email)
isShowContact = true
}
}
for _, v := range data.NsServers() {
fmt.Printf("NS服务器\t%s\n", v)
}
for _, v := range data.NsIps() {
fmt.Printf("NS IP\t%s\n", v)
}
}
if showFull {
fmt.Println("")
fmt.Println(data.RawData())
}
cmd.Println("-----------------------------------------------------")
os.WriteFile(output+"/"+v+".txt", []byte(data), 0644)
os.WriteFile(output+"/"+v+".txt", []byte(data.RawData()), 0644)
}
},
}
var CmdExists = &cobra.Command{
Use: "exists",
Short: "域名是否存在查询",
Long: "域名是否存在查询,可使用字典",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
cmd.Help()
return
}
var of *os.File
var err error
if output != "" {
of, err = os.Create(output)
if err != nil {
starlog.Errorln("创建输出文件失败", err)
return
}
defer of.Close()
}
c := whois.NewClient()
if socks5 != "" {
var auth *proxy.Auth
if socks5Auth != "" {
up := strings.SplitN(socks5Auth, ":", 2)
if len(up) == 2 {
auth = &proxy.Auth{
User: up[0],
Password: up[1],
}
} else {
starlog.Errorln("socks5认证格式错误")
return
}
}
s5Dial, err := proxy.SOCKS5("tcp", socks5, auth, proxy.Direct)
if err == nil {
c.SetDialer(s5Dial)
} else {
starlog.Errorln("socks5代理错误:", err)
return
}
}
c.SetTimeout(time.Second * time.Duration(timeout))
var d domainList
if !useDict {
err = d.New("stdin", args)
} else {
err = d.New("file", args)
}
if err != nil {
starlog.Errorln("初始化域名列表失败", err)
return
}
if thread == 0 {
thread = 1
}
var wg = stario.NewWaitGroup(thread)
fin := false
for !fin {
wg.Add(1)
go func() {
defer wg.Done()
domain, err := d.Next()
if err != nil {
if err == io.EOF {
fin = true
}
return
}
if suffix != "" {
domain = domain + "." + suffix
}
var data whois.Result
for i := 0; i < retries; i++ {
data, err = c.Whois(domain, whoisServer...)
if err != nil {
continue
}
break
}
if err != nil {
starlog.Errorln("查询失败:", domain, err)
return
}
if !hideExists && data.Exists() {
fmt.Println(domain, "已注册")
if of != nil {
of.WriteString(domain + " 已注册\n")
}
}
if !hideNoExists && !data.Exists() {
fmt.Println(domain, "不存在")
if of != nil {
of.WriteString(domain + " 不存在\n")
}
}
}()
}
wg.Wait()
},
}
type domainList struct {
typed string
args []string
idx int
mu sync.Mutex
f *os.File
scanner *bufio.Reader
}
func (d *domainList) New(types string, args []string) error {
var err error
d.typed = types
d.args = args
switch d.typed {
case "file":
d.f, err = os.OpenFile(d.args[0], os.O_RDONLY, 0644)
if err != nil {
return err
}
d.scanner = bufio.NewReader(d.f)
case "stdin":
}
return err
}
func (d *domainList) Next() (string, error) {
d.mu.Lock()
defer d.mu.Unlock()
for {
switch d.typed {
case "file":
l, _, err := d.scanner.ReadLine()
if err == io.EOF {
if d.idx+1 >= len(d.args) {
d.f.Close()
return "", err
}
d.idx++
d.f.Close()
d.f, err = os.OpenFile(d.args[d.idx], os.O_RDONLY, 0644)
if err != nil {
return "", err
}
d.scanner = bufio.NewReader(d.f)
continue
}
return strings.TrimSpace(string(l)), nil
case "stdin":
if d.idx >= len(d.args) {
return "", io.EOF
}
d.idx++
return d.args[d.idx-1], nil
}
}
}