You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
star/tcm/tcpmonitor_unix.go

680 lines
18 KiB
Go

//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=目标端单向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
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)
}