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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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