//go:build !windows package tcm import ( "b612.me/bcap" "b612.me/bcap/nfq" "b612.me/stario" "b612.me/starlog" "context" "encoding/hex" "fmt" "github.com/florianl/go-nfqueue/v2" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcapgo" "math/rand" "net" "os" "os/exec" "os/signal" "strconv" "strings" "sync" "syscall" "time" ) type NfCap struct { count uint64 // 按连接数最后一个历史报文 cap *bcap.Packets // 监控目标ip地址列表 target []string // 将军,下达命令吧! // 监控命令列表 targetCmd []string targetAsHex bool //保存为pcap格式的数据文件 saveFile string // 写缓存 packetCache chan gopacket.Packet //保存封禁的map blockMap sync.Map //交互模式 interactive bool //展示所有报文信息,包括未在追踪列表的 showAll bool //展示payload报文(utf-8直接输出) showPayload bool //payload展示为hex showAsHex bool //payload允许展示的最大字符 maxShowPayloadSize int //不展示任何信息 noShowMode bool // 全链路丢包率,百分比 loss float64 // 报文延迟抵达时间,毫秒 delay int // 触发封禁词后,再过N个包再封禁 packetDelay int // 触发封禁词后,使用RST重置链路 useRST bool // RST模式,target=目标端单向RST,reverse=对端反向RST,both=双向RST rstMode string fastMode bool //自探测 printColor []*starlog.Color logCache chan loged monitorPort int cache []string NFQNums int ctx context.Context fn context.CancelFunc requests chan *handler allowRandomAck bool onlyDropblackwordPacket bool } 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, 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()) nf.requests = make(chan *handler, 2048) 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 info.p.SetVerdict(info.id, <-info.fin) continue } 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) 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 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 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)) && n.strInRange(string(layer.LayerPayload())) { 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.onlyDropblackwordPacket { 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 } 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 } 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) pcapWriter(stopCtx context.Context, fp *os.File) error { w := pcapgo.NewWriter(fp) err := w.WriteFileHeader(65535, layers.LinkTypeRaw) if err != nil { return err } for { select { case <-stopCtx.Done(): return nil case p := <-n.packetCache: w.WritePacket(gopacket.CaptureInfo{ Timestamp: time.Now(), CaptureLength: len(p.Data()), Length: len(p.Data()), }, p.Data()) } } } 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: dPort, Addr: [16]byte(dstNetIP.To16()), } return syscall.Sendto(fd, buffer.Bytes(), 0, &addr) }