From 5cb3c6d651b792f18841afef4061aac2b21e3667 Mon Sep 17 00:00:00 2001 From: starainrt Date: Sun, 24 Nov 2024 11:30:27 +0800 Subject: [PATCH] first commit --- .gitignore | 1 + bcap.go | 280 +++++++++++++++++++++++++++++++++++++++++++++ go.mod | 18 +++ go.sum | 30 +++++ libpcap/libpcap.go | 77 +++++++++++++ nfq/nfqueue.go | 85 ++++++++++++++ 6 files changed, 491 insertions(+) create mode 100644 .gitignore create mode 100644 bcap.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 libpcap/libpcap.go create mode 100644 nfq/nfqueue.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..723ef36 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/bcap.go b/bcap.go new file mode 100644 index 0000000..264908b --- /dev/null +++ b/bcap.go @@ -0,0 +1,280 @@ +package bcap + +import ( + "fmt" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "net" + "sync" +) + +type Packets struct { + sync.RWMutex + cu map[string]PacketInfo +} + +type PacketInfo struct { + Key string + ReverseKey string + Type string + SrcMac net.HardwareAddr + DstMac net.HardwareAddr + SrcIP string + SrcPort string + DstIP string + DstPort string + comment string + packet gopacket.Packet + tcpSeq uint32 + tcpAck uint32 + tcpWindow uint16 + tcpPayloads int + finState bool + synState bool + isFirst bool + //stateDescript 0=unknown 1=tcp_connect_1 2=tcp_connect_2 3=tcp_connect_3 + // 4=tcp_disconnect_1 5=tcp_disconnect_2 6=tcp_disconnect_2+3 7=tcp_disconnect_3 + // 8=tcp_disconnect_4 9=tcp_ack_ok 10=tcp_retransmit 11=tcp_ece 12=tcp_cwr 13=tcp_rst + // 14=tcp_keepalived 20=udp + stateDescript uint8 +} + +func (p *Packets) Key(key string) PacketInfo { + return p.cu[key] +} + +func (p *Packets) SetComment(key, comment string) { + p.Lock() + c := p.cu[key] + c.comment = comment + p.cu[key] = c + p.Unlock() +} + +func (p *PacketInfo) SetComment(comment string) { + p.comment = comment +} + +func (p PacketInfo) StateDescript() uint8 { + return p.stateDescript +} + +func (p PacketInfo) TcpPayloads() int { + return p.tcpPayloads +} + +func (p PacketInfo) FinState() bool { + return p.finState +} + +func (p PacketInfo) SynState() bool { + return p.synState +} + +func (p PacketInfo) TcpWindow() uint16 { + return p.tcpWindow +} + +func (p PacketInfo) TcpAck() uint32 { + return p.tcpAck +} + +func (p PacketInfo) TcpSeq() uint32 { + return p.tcpSeq +} + +func (p PacketInfo) Packet() gopacket.Packet { + return p.packet +} + +func (p PacketInfo) Comment() string { + return p.comment +} + +func NewPackets() *Packets { + return &Packets{ + cu: make(map[string]PacketInfo), + } +} + +func (p *Packets) ParsePacket(packet gopacket.Packet, opts ...interface{}) (PacketInfo, error) { + var info PacketInfo + //firstOpts is macaddr(nfqueue) + if ew := packet.Layer(layers.LayerTypeEthernet); ew != nil { + eth := ew.(*layers.Ethernet) + info.SrcMac = eth.SrcMAC + info.DstMac = eth.DstMAC + } + for k, v := range opts { + switch tp := v.(type) { + case *[]byte: + if tp != nil && k == 0 { + //nfqueue src mac addr + info.SrcMac = net.HardwareAddr(*tp) + } + } + } + if nw := packet.NetworkLayer(); nw != nil { + srcp, dstp := nw.NetworkFlow().Endpoints() + info.SrcIP = srcp.String() + info.DstIP = dstp.String() + } else { + return info, fmt.Errorf("Failed to parse packet,not a valid network info") + } + { + //tcp valid + layer := packet.Layer(layers.LayerTypeTCP) + if layer != nil { + if tcp, ok := layer.(*layers.TCP); ok { + return p.parseTcp(info, packet, layer, tcp) + } + } + } + { + layer := packet.Layer(layers.LayerTypeUDP) + if layer != nil { + if udp, ok := layer.(*layers.UDP); ok { + return p.parseUdp(info, packet, layer, udp) + } + } + } + //icmp + return info, fmt.Errorf("not support packet") +} + +func (p *Packets) parseTcp(info PacketInfo, packet gopacket.Packet, layer gopacket.Layer, tcp *layers.TCP) (PacketInfo, error) { + info.Key = fmt.Sprintf("tcp://%s:%d=%s:%d", info.SrcIP, tcp.SrcPort, info.DstIP, tcp.DstPort) + info.ReverseKey = fmt.Sprintf("tcp://%s:%d=%s:%d", info.DstIP, tcp.DstPort, info.SrcIP, tcp.SrcPort) + info.Type = "tcp" + info.SrcPort = fmt.Sprintf("%d", tcp.SrcPort) + info.DstPort = fmt.Sprintf("%d", tcp.DstPort) + info.packet = packet + info.tcpSeq = tcp.Seq + info.tcpAck = tcp.Ack + info.tcpPayloads = len(layer.LayerPayload()) + info.finState = tcp.FIN + info.synState = tcp.SYN + info.tcpWindow = tcp.Window + lastPacket := p.cu[info.Key] + if lastPacket.Key != info.Key { + lastPacket = PacketInfo{ + Key: info.Key, + ReverseKey: info.ReverseKey, + Type: "tcp", + SrcIP: info.SrcIP, + SrcPort: info.SrcPort, + DstIP: info.DstIP, + DstPort: info.DstPort, + comment: "", + packet: packet, + tcpSeq: tcp.Seq, + tcpAck: tcp.Ack, + tcpWindow: tcp.Window, + tcpPayloads: len(layer.LayerPayload()), + finState: tcp.FIN, + synState: tcp.SYN, + isFirst: true, + stateDescript: 0, + } + p.Lock() + p.cu[info.Key] = lastPacket + p.Unlock() + } + lastReverse := p.cu[info.ReverseKey] + if !lastPacket.isFirst { + info.comment = lastPacket.comment + if lastPacket.SrcMac != nil && len(info.SrcMac) == 0 { + info.SrcMac = lastPacket.SrcMac + } + if lastPacket.SrcMac != nil && len(info.SrcMac) == 0 { + info.SrcMac = lastPacket.SrcMac + } + } + if lastReverse.SrcMac != nil && len(info.DstMac) == 0 { + info.DstMac = lastReverse.SrcMac + } + { + //state judge + if tcp.RST { + info.stateDescript = 13 + p.Lock() + delete(p.cu, info.Key) + delete(p.cu, info.ReverseKey) + p.Unlock() + return info, nil + } + if tcp.SYN && !tcp.ACK { + info.stateDescript = 1 + } else if tcp.SYN && tcp.ACK { + info.stateDescript = 2 + } else if tcp.ACK { + + if !tcp.FIN { + if lastReverse.tcpSeq+1 == tcp.Ack && lastReverse.stateDescript == 2 { + info.stateDescript = 3 + } else if tcp.CWR { + info.stateDescript = 12 + } else if tcp.ECE { + info.stateDescript = 11 + } + if info.stateDescript != 0 { + goto savereturn + } + if info.tcpSeq == lastReverse.tcpAck-1 && info.tcpSeq == lastPacket.tcpSeq+uint32(lastPacket.tcpPayloads)-1 { + //keepalive + info.stateDescript = 14 + goto savereturn + } + if !lastPacket.isFirst { + if info.tcpSeq < lastPacket.tcpSeq+uint32(lastPacket.tcpPayloads) { + //retransmit + info.stateDescript = 10 + goto savereturn + } + } + if lastReverse.finState && lastPacket.finState { + info.stateDescript = 8 + p.Lock() + delete(p.cu, info.Key) + delete(p.cu, info.ReverseKey) + p.Unlock() + return info, nil + } + if lastReverse.finState && lastReverse.tcpSeq+1 == info.tcpAck { + info.stateDescript = 5 + goto savereturn + } + info.stateDescript = 9 + } else { + if !lastReverse.finState { + info.stateDescript = 4 + } else { + if lastReverse.finState && lastReverse.tcpSeq+1 == info.tcpAck && lastPacket.tcpAck == info.tcpAck && lastPacket.tcpSeq == info.tcpSeq { + info.stateDescript = 7 + } else { + info.stateDescript = 6 + } + } + } + } + } +savereturn: + p.Lock() + p.cu[info.Key] = info + p.Unlock() + return info, nil +} + +func (p *Packets) parseUdp(info PacketInfo, packet gopacket.Packet, layer gopacket.Layer, udp *layers.UDP) (PacketInfo, error) { + info.Key = fmt.Sprintf("udp://%s:%d=%s:%d", info.SrcIP, udp.SrcPort, info.DstIP, udp.DstPort) + info.ReverseKey = fmt.Sprintf("udp://%s:%d=%s:%d", info.DstIP, udp.DstPort, info.SrcIP, udp.SrcPort) + info.Type = "udp" + info.SrcPort = fmt.Sprintf("%d", udp.SrcPort) + info.DstPort = fmt.Sprintf("%d", udp.DstPort) + info.packet = packet + info.tcpPayloads = len(layer.LayerPayload()) + p.Lock() + p.cu[info.Key] = info + p.Unlock() + return info, nil +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dd6a85e --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module b612.me/bcap + +go 1.22.4 + +require ( + github.com/florianl/go-nfqueue/v2 v2.0.0 + github.com/google/gopacket v1.1.19 +) + +require ( + github.com/google/go-cmp v0.6.0 // indirect + github.com/josharian/native v1.1.0 // indirect + github.com/mdlayher/netlink v1.7.2 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.20.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..08539f5 --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +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/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/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= +github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= +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/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= +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/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/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.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.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.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/libpcap/libpcap.go b/libpcap/libpcap.go new file mode 100644 index 0000000..316f0c9 --- /dev/null +++ b/libpcap/libpcap.go @@ -0,0 +1,77 @@ +package libpcap + +import ( + "fmt" + "github.com/google/gopacket" + "github.com/google/gopacket/pcap" +) + +type NetCatch struct { + eth string + host string + sentence string + fn func(gopacket.Packet) + *pcap.Handle +} + +func (n *NetCatch) SetRecall(fn func(p gopacket.Packet)) { + n.fn = fn +} + +func FindAllDevs() ([]pcap.Interface, error) { + return pcap.FindAllDevs() +} + +func NewCatch(host string, sentence string) (*NetCatch, error) { + ifs, err := pcap.FindAllDevs() + if err != nil { + return nil, err + } + eth := "" + for _, v := range ifs { + if host == "127.0.0.1" && v.Name == "\\Device\\NPF_Loopback" { + eth = v.Name + } + for _, addr := range v.Addresses { + if addr.IP.String() == host { + eth = v.Name + } + } + } + if len(eth) == 0 { + return nil, fmt.Errorf("cannot found eth") + } + nc := new(NetCatch) + nc.host = host + nc.eth = eth + nc.sentence = sentence + return nc, nil +} + +func NewCatchEth(eth string, sentence string) (*NetCatch, error) { + nc := new(NetCatch) + nc.eth = eth + nc.sentence = sentence + return nc, nil +} + +func (n *NetCatch) Run() error { + if n.eth == "" { + return fmt.Errorf("no pcap device") + } + handle, err := pcap.OpenLive(n.eth, 65535, true, pcap.BlockForever) + if err != nil { + return err + } + n.Handle = handle + if err = handle.SetBPFFilter(n.sentence); err != nil { + return err + } + pks := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range pks.Packets() { + if n.fn != nil { + n.fn(packet) + } + } + return nil +} diff --git a/nfq/nfqueue.go b/nfq/nfqueue.go new file mode 100644 index 0000000..1d71f3a --- /dev/null +++ b/nfq/nfqueue.go @@ -0,0 +1,85 @@ +package nfq + +import ( + "context" + "fmt" + "github.com/florianl/go-nfqueue/v2" + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "time" +) + +type NfQueue struct { + queid uint16 + maxqueue uint32 + ctx context.Context + stopFn context.CancelFunc + recallFn func(id uint32, q *nfqueue.Nfqueue, p Packet) +} + +type Packet struct { + Packet gopacket.Packet + Attr nfqueue.Attribute +} + +func NewNfQueue(ctx context.Context, queid uint16, maxqueue uint32) *NfQueue { + var q = new(NfQueue) + if ctx == nil { + q.ctx, q.stopFn = context.WithCancel(context.Background()) + } else { + q.ctx, q.stopFn = context.WithCancel(ctx) + } + q.queid = queid + q.maxqueue = maxqueue + return q +} + +func (n *NfQueue) SetRecall(fn func(id uint32, q *nfqueue.Nfqueue, p Packet)) { + n.recallFn = fn +} + +func (n *NfQueue) Run() error { + cfg := nfqueue.Config{ + NfQueue: n.queid, + MaxQueueLen: n.maxqueue, + Copymode: nfqueue.NfQnlCopyPacket, + WriteTimeout: time.Second * 10, + } + nfq, err := nfqueue.Open(&cfg) + if err != nil { + return fmt.Errorf("failed to open nfqueue, err:", err) + } + + if err := nfq.RegisterWithErrorFunc(n.ctx, func(a nfqueue.Attribute) int { + return n.handlePacket(nfq, a) + }, func(e error) int { + return 0 + }); err != nil { + return fmt.Errorf("failed to register handlers, err:", err) + } + <-n.ctx.Done() + return nil +} + +func (n *NfQueue) handlePacket(q *nfqueue.Nfqueue, a nfqueue.Attribute) int { + if a.Payload != nil && len(*a.Payload) != 0 { + var packet gopacket.Packet + data := *a.Payload + if data[0]&0xf0 == 0x40 { + packet = gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.DecodeOptions{Lazy: true, NoCopy: true}) + } else { + packet = gopacket.NewPacket(data, layers.LayerTypeIPv6, gopacket.DecodeOptions{Lazy: true, NoCopy: true}) + } + if n.recallFn != nil { + n.recallFn(*a.PacketID, q, Packet{ + Packet: packet, + Attr: a, + }) + } else { + q.SetVerdict(*a.PacketID, nfqueue.NfAccept) + } + return 0 + } + q.SetVerdict(*a.PacketID, nfqueue.NfAccept) + return 0 +}