diff --git a/Makefile b/Makefile index 2117b84..3e17f6e 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# cmd/packemon/ 配下で以下 +# go generate +# sudo go run . + # いかな感じで単発で送信 debug: sudo go run cmd/packemon/main.go --send --proto icmp --debug diff --git a/cmd/packemon/egress_packet.bpf.c b/cmd/packemon/egress_packet.bpf.c new file mode 100644 index 0000000..6896f82 --- /dev/null +++ b/cmd/packemon/egress_packet.bpf.c @@ -0,0 +1,177 @@ +//go:build ignore + +// 部分的にコピーしてる +// https://eunomia.dev/tutorials/20-tc/#writing-ebpf-programs + +// 以下あたりの定義も拝借. ethhdr/iphdr など +// https://github.com/cilium/ebpf/blob/b8dc0ee25417ce7cd4a6feb48be42c0615ee9043/examples/headers/common.h#L4 + +// #include +// #include "common.h" +#include +#include +#include +#include + +#define TC_ACT_OK 0 +#define TC_ACT_SHOT 2 + +#define ETH_P_IPv4 0x0800 +#define ETH_P_ARP 0x0806 + +#define IP_P_ICMP 0x01 +#define IP_P_TCP 0x06 +#define IP_P_UDP 0x17 + +// Wireshark観察する限りはそうだが要fix +#define TCP_FLG_RST_ACK 0x29 +// bpfのlog観察する限りはそうだが要fix +#define TCP_FLG_RST 0x8 + +#define MAX_ENTRIES 64 +#define AF_INET 2 + +struct ethhdr { + unsigned char h_dest[6]; + unsigned char h_source[6]; + __be16 h_proto; +}; + +struct iphdr { + __u8 ihl: 4; + __u8 version: 4; + __u8 tos; + __be16 tot_len; + __be16 id; + __be16 frag_off; + __u8 ttl; + __u8 protocol; + __sum16 check; + __be32 saddr; + __be32 daddr; +}; + +// https://www.infraexpert.com/study/tcpip8.html +struct tcphdr { + __be16 sport; + __be16 dport; + __be32 sequence; + __be32 acknowladge; + __u8 offset: 4; + __u8 yoyaku: 3; + __be16 controlflg: 9; + __be16 window; + __be16 checksum; + __be16 urg; +}; + +char __license[] SEC("license") = "Dual MIT/GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 1); +} pkt_count SEC(".maps"); + +struct { + __uint(type, BPF_MAP_TYPE_ARRAY); + __type(key, __u32); + __type(value, __u64); + __uint(max_entries, 1); +} arp_pkt_count SEC(".maps"); + +// __sk_buff について +// https://medium.com/@c0ngwang/understanding-struct-sk-buff-730cf847a722 + +SEC("tc") +int control_egress(struct __sk_buff *skb) +{ + void *data_end = (void *)(__u64)skb->data_end; + void *data = (void *)(__u64)skb->data; + struct ethhdr *eth; + struct iphdr *iph; + struct tcphdr *tcph; + + __u32 key = 0; + __u64 *count = bpf_map_lookup_elem(&pkt_count, &key); + __u64 *arp_count = bpf_map_lookup_elem(&arp_pkt_count, &key); + + // bpf_printk("proto: %x", skb->protocol); + // bpf_printk("data: %x", skb->data); + // bpf_printk("data_end: %x", skb->data_end); + + if (count) { + __sync_fetch_and_add(count, 1); + } + + eth = data; + if ((void *)(eth + 1) > data_end) { + bpf_printk("a"); + return TC_ACT_OK; + } + + iph = (struct iphdr *)(eth + 1); + if ((void *)(iph + 1) > data_end) { + bpf_printk("b"); + return TC_ACT_OK; + } + + if (bpf_ntohs(eth->h_proto) == ETH_P_ARP) { + bpf_printk("Ether Type: ARP"); + if (arp_count) { + __sync_fetch_and_add(arp_count, 1); + } + // return TC_ACT_SHOT; + return TC_ACT_OK; + } + + if (bpf_ntohs(eth->h_proto) == ETH_P_IPv4) { + bpf_printk("Ether Type : IP"); + bpf_printk(" tot_len : %d", bpf_ntohs(iph->tot_len)); + bpf_printk(" ttl : %d", iph->ttl); + bpf_printk(" protocol: %x", iph->protocol); + bpf_printk(" dst : %x", bpf_ntohl(iph->daddr)); + bpf_printk(" src : %x", bpf_ntohl(iph->saddr)); + + if (iph->protocol == IP_P_ICMP) { + bpf_printk("ICMP"); + return TC_ACT_OK; + } + if (iph->protocol == IP_P_UDP) { + bpf_printk("UDP"); + return TC_ACT_OK; + } + if (iph->protocol == IP_P_TCP) { + bpf_printk("TCP"); + + tcph = (struct tcphdr *)(iph + 1); + if ((void *)(tcph + 1) > data_end) { + bpf_printk("c"); + return TC_ACT_OK; + } + + bpf_printk(" sport : %x", bpf_ntohs(tcph->sport)); + bpf_printk(" dport : %x", bpf_ntohs(tcph->dport)); + bpf_printk(" controlflg: %x", bpf_ntohs(tcph->controlflg)); + bpf_printk(" controlflg: %x", tcph->controlflg); + + if (tcph->controlflg == TCP_FLG_RST_ACK) { + bpf_printk("TCP RST-ACK"); + return TC_ACT_SHOT; + // return TC_ACT_OK; + } + if (tcph->controlflg == TCP_FLG_RST) { + bpf_printk("TCP RST"); + return TC_ACT_SHOT; + // return TC_ACT_OK; + } + + return TC_ACT_OK; + } + + return TC_ACT_OK; + } + + return TC_ACT_OK; +} \ No newline at end of file diff --git a/cmd/packemon/egress_packet_bpfeb.go b/cmd/packemon/egress_packet_bpfeb.go new file mode 100644 index 0000000..3e4b138 --- /dev/null +++ b/cmd/packemon/egress_packet_bpfeb.go @@ -0,0 +1,122 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build mips || mips64 || ppc64 || s390x + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadEgress_packet returns the embedded CollectionSpec for egress_packet. +func loadEgress_packet() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_Egress_packetBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load egress_packet: %w", err) + } + + return spec, err +} + +// loadEgress_packetObjects loads egress_packet and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *egress_packetObjects +// *egress_packetPrograms +// *egress_packetMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadEgress_packetObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadEgress_packet() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// egress_packetSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetSpecs struct { + egress_packetProgramSpecs + egress_packetMapSpecs +} + +// egress_packetSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetProgramSpecs struct { + ControlEgress *ebpf.ProgramSpec `ebpf:"control_egress"` +} + +// egress_packetMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetMapSpecs struct { + ArpPktCount *ebpf.MapSpec `ebpf:"arp_pkt_count"` + PktCount *ebpf.MapSpec `ebpf:"pkt_count"` +} + +// egress_packetObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetObjects struct { + egress_packetPrograms + egress_packetMaps +} + +func (o *egress_packetObjects) Close() error { + return _Egress_packetClose( + &o.egress_packetPrograms, + &o.egress_packetMaps, + ) +} + +// egress_packetMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetMaps struct { + ArpPktCount *ebpf.Map `ebpf:"arp_pkt_count"` + PktCount *ebpf.Map `ebpf:"pkt_count"` +} + +func (m *egress_packetMaps) Close() error { + return _Egress_packetClose( + m.ArpPktCount, + m.PktCount, + ) +} + +// egress_packetPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetPrograms struct { + ControlEgress *ebpf.Program `ebpf:"control_egress"` +} + +func (p *egress_packetPrograms) Close() error { + return _Egress_packetClose( + p.ControlEgress, + ) +} + +func _Egress_packetClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed egress_packet_bpfeb.o +var _Egress_packetBytes []byte diff --git a/cmd/packemon/egress_packet_bpfeb.o b/cmd/packemon/egress_packet_bpfeb.o new file mode 100644 index 0000000..dec3f9e Binary files /dev/null and b/cmd/packemon/egress_packet_bpfeb.o differ diff --git a/cmd/packemon/egress_packet_bpfel.go b/cmd/packemon/egress_packet_bpfel.go new file mode 100644 index 0000000..0b9daae --- /dev/null +++ b/cmd/packemon/egress_packet_bpfel.go @@ -0,0 +1,122 @@ +// Code generated by bpf2go; DO NOT EDIT. +//go:build 386 || amd64 || arm || arm64 || loong64 || mips64le || mipsle || ppc64le || riscv64 + +package main + +import ( + "bytes" + _ "embed" + "fmt" + "io" + + "github.com/cilium/ebpf" +) + +// loadEgress_packet returns the embedded CollectionSpec for egress_packet. +func loadEgress_packet() (*ebpf.CollectionSpec, error) { + reader := bytes.NewReader(_Egress_packetBytes) + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + return nil, fmt.Errorf("can't load egress_packet: %w", err) + } + + return spec, err +} + +// loadEgress_packetObjects loads egress_packet and converts it into a struct. +// +// The following types are suitable as obj argument: +// +// *egress_packetObjects +// *egress_packetPrograms +// *egress_packetMaps +// +// See ebpf.CollectionSpec.LoadAndAssign documentation for details. +func loadEgress_packetObjects(obj interface{}, opts *ebpf.CollectionOptions) error { + spec, err := loadEgress_packet() + if err != nil { + return err + } + + return spec.LoadAndAssign(obj, opts) +} + +// egress_packetSpecs contains maps and programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetSpecs struct { + egress_packetProgramSpecs + egress_packetMapSpecs +} + +// egress_packetSpecs contains programs before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetProgramSpecs struct { + ControlEgress *ebpf.ProgramSpec `ebpf:"control_egress"` +} + +// egress_packetMapSpecs contains maps before they are loaded into the kernel. +// +// It can be passed ebpf.CollectionSpec.Assign. +type egress_packetMapSpecs struct { + ArpPktCount *ebpf.MapSpec `ebpf:"arp_pkt_count"` + PktCount *ebpf.MapSpec `ebpf:"pkt_count"` +} + +// egress_packetObjects contains all objects after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetObjects struct { + egress_packetPrograms + egress_packetMaps +} + +func (o *egress_packetObjects) Close() error { + return _Egress_packetClose( + &o.egress_packetPrograms, + &o.egress_packetMaps, + ) +} + +// egress_packetMaps contains all maps after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetMaps struct { + ArpPktCount *ebpf.Map `ebpf:"arp_pkt_count"` + PktCount *ebpf.Map `ebpf:"pkt_count"` +} + +func (m *egress_packetMaps) Close() error { + return _Egress_packetClose( + m.ArpPktCount, + m.PktCount, + ) +} + +// egress_packetPrograms contains all programs after they have been loaded into the kernel. +// +// It can be passed to loadEgress_packetObjects or ebpf.CollectionSpec.LoadAndAssign. +type egress_packetPrograms struct { + ControlEgress *ebpf.Program `ebpf:"control_egress"` +} + +func (p *egress_packetPrograms) Close() error { + return _Egress_packetClose( + p.ControlEgress, + ) +} + +func _Egress_packetClose(closers ...io.Closer) error { + for _, closer := range closers { + if err := closer.Close(); err != nil { + return err + } + } + return nil +} + +// Do not access this directly. +// +//go:embed egress_packet_bpfel.o +var _Egress_packetBytes []byte diff --git a/cmd/packemon/egress_packet_bpfel.o b/cmd/packemon/egress_packet_bpfel.o new file mode 100644 index 0000000..2a8d33e Binary files /dev/null and b/cmd/packemon/egress_packet_bpfel.o differ diff --git a/cmd/packemon/gen.go b/cmd/packemon/gen.go new file mode 100644 index 0000000..45f1a6a --- /dev/null +++ b/cmd/packemon/gen.go @@ -0,0 +1,3 @@ +package main + +//go:generate go run github.com/cilium/ebpf/cmd/bpf2go egress_packet egress_packet.bpf.c diff --git a/cmd/packemon/main.go b/cmd/packemon/main.go index 4bf74fe..758a01a 100644 --- a/cmd/packemon/main.go +++ b/cmd/packemon/main.go @@ -4,12 +4,20 @@ import ( "errors" "flag" "fmt" + "log" + "net" "os" + "os/signal" "strings" + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/rlimit" "github.com/ddddddO/packemon" "github.com/ddddddO/packemon/internal/debugging" "github.com/ddddddO/packemon/internal/tui" + "github.com/vishvananda/netlink" + + "golang.org/x/sys/unix" ) const DEFAULT_TARGET_NW_INTERFACE = "eth0" @@ -25,13 +33,94 @@ func main() { flag.StringVar(&protocol, "proto", "", "Specify either 'arp', 'icmp', 'tcp', 'dns' or 'http'.") flag.Parse() - if err := run(nwInterface, wantSend, debug, protocol); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) + if wantSend { + // Generator で3way handshake する際に、カーネルが自動でRSTパケットを送ってたため、ドロップするため + ebpfProg, qdisc, err := prepareDropingRSTPacket(nwInterface) + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + defer func() { + ebpfProg.Close() + // 以下で消しておかないと、再起動やtcコマンド使わない限り、RSTパケットがカーネルから送信されない状態になる + if err := netlink.QdiscDel(qdisc); err != nil { + log.Printf("Failed to QdiscDel. Please PC reboot... Error: %s\n", err) + } + }() + } + + stop := make(chan os.Signal, 5) + signal.Notify(stop, os.Interrupt) + // 以下でもctl-cしないといけない + go run(stop, nwInterface, wantSend, debug, protocol) + <-stop + log.Print("Received signal, exiting...") +} + +func prepareDropingRSTPacket(nwInterface string) (*egress_packetObjects, *netlink.GenericQdisc, error) { + // Remove resource limits for kernels <5.11. + if err := rlimit.RemoveMemlock(); err != nil { + return nil, nil, fmt.Errorf("removing memlock: %w", err) + } + + // Load the compiled eBPF ELF and load it into the kernel. + var objs egress_packetObjects + if err := loadEgress_packetObjects(&objs, nil); err != nil { + return nil, nil, fmt.Errorf("loading eBPF objects: %w", err) + } + + qdisc, err := attachFilter(nwInterface, objs.egress_packetPrograms.ControlEgress) + if err != nil { + return nil, nil, fmt.Errorf("failed to attach: %w", err) } + + return &objs, qdisc, nil } -func run(nwInterface string, wantSend bool, debug bool, protocol string) error { +// https://github.com/fedepaol/tc-return/blob/main/main.go +func attachFilter(attachTo string, program *ebpf.Program) (*netlink.GenericQdisc, error) { + devID, err := net.InterfaceByName(attachTo) + if err != nil { + return nil, fmt.Errorf("could not get interface ID: %w", err) + } + + qdisc := &netlink.GenericQdisc{ + QdiscAttrs: netlink.QdiscAttrs{ + LinkIndex: devID.Index, + Handle: netlink.MakeHandle(0xffff, 0), + Parent: netlink.HANDLE_CLSACT, + }, + QdiscType: "clsact", + } + + err = netlink.QdiscReplace(qdisc) + if err != nil { + return nil, fmt.Errorf("could not get replace qdisc: %w", err) + } + + filter := &netlink.BpfFilter{ + FilterAttrs: netlink.FilterAttrs{ + LinkIndex: devID.Index, + Parent: netlink.HANDLE_MIN_EGRESS, + Handle: 1, + Protocol: unix.ETH_P_ALL, + }, + Fd: program.FD(), + Name: program.String(), + DirectAction: true, + } + + if err := netlink.FilterReplace(filter); err != nil { + return nil, fmt.Errorf("failed to replace tc filter: %w", err) + } + + return qdisc, nil +} + +func run(stop <-chan os.Signal, nwInterface string, wantSend bool, debug bool, protocol string) error { + // packemonを終了した後でsignal飛ばしてもらって終了させてもらう、一旦 + fmt.Println("Terminate packemon with ctl-c.") + netIf, err := packemon.NewNetworkInterface(nwInterface) if err != nil { return err @@ -41,8 +130,6 @@ func run(nwInterface string, wantSend bool, debug bool, protocol string) error { tui.DEFAULT_MAC_SOURCE = fmt.Sprintf("0x%s", strings.ReplaceAll(netIf.Intf.HardwareAddr.String(), ":", "")) tui.DEFAULT_ARP_SENDER_MAC = tui.DEFAULT_MAC_SOURCE - fmt.Printf("Monitor interface: %v\n", netIf.Intf) - ipAddr, err := netIf.Intf.Addrs() if err != nil { return err @@ -68,31 +155,33 @@ func run(nwInterface string, wantSend bool, debug bool, protocol string) error { } // Monitor の debug は本チャンの networkinterface.go 使うようにする - go netIf.Recieve() - return debugPrint(netIf.PassiveCh) + go netIf.Recieve(stop) + return debugPrint(stop, netIf.PassiveCh) } if wantSend { tui.DEFAULT_NW_INTERFACE = nwInterface tui := tui.NewTUI(wantSend) - return tui.Generator(netIf.Send) + return tui.Generator(stop, netIf.Send) } else { tui := tui.NewTUI(wantSend) - go netIf.Recieve() + go netIf.Recieve(stop) return tui.Monitor(netIf.PassiveCh) } } -func debugPrint(passive <-chan *packemon.Passive) error { - for p := range passive { - if p.HighLayerProto() == "IPv6" { - fmt.Println("Passive!") - fmt.Printf("%x\n", p.IPv6) +func debugPrint(stop <-chan os.Signal, passive <-chan *packemon.Passive) error { + for { + select { + case <-stop: + return nil + case p := <-passive: + if p.HighLayerProto() == "IPv6" { + fmt.Println("Passive!") + fmt.Printf("%x\n", p.IPv6) + } } - } - - return nil } func debugMode(wantSend bool, protocol string, netIf *packemon.NetworkInterface, dstMacAddr [6]byte) error { diff --git a/go.mod b/go.mod index 0878239..48e51d6 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,14 @@ require ( ) require ( + github.com/cilium/ebpf v0.15.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/vishvananda/netlink v1.1.0 // indirect + github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df // indirect + golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect golang.org/x/term v0.17.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/go.sum b/go.sum index c0c2dc2..7b3f752 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cilium/ebpf v0.15.0 h1:7NxJhNiBT3NG8pZJ3c+yfrVdHY8ScgKD27sScgjLMMk= +github.com/cilium/ebpf v0.15.0/go.mod h1:DHp1WyrLeiBh19Cf/tfiSMhqheEiK8fXFZ4No0P1Hso= 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= @@ -12,9 +14,15 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ 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/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df h1:OviZH7qLw/7ZovXvuNyL3XQl8UFofeikI1NW1Gypu7k= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 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/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -25,6 +33,7 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ 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/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/http.go b/http.go index 7a554f5..42aa91e 100644 --- a/http.go +++ b/http.go @@ -126,7 +126,7 @@ func ParsedHTTPResponse(payload []byte) *HTTPResponse { continue } - log.Printf("not suported header: %s, len: %d\n", string(s), len(bytes.TrimSpace(s))) + // log.Printf("not suported header: %s, len: %d\n", string(s), len(bytes.TrimSpace(s))) } b := bytes.SplitAfter(payload, append(sep, sep...)) body := string(b[len(b)-1][0:header.ContentLength]) diff --git a/internal/tui/form.go b/internal/tui/form.go index 04d36c4..5b0d36d 100644 --- a/internal/tui/form.go +++ b/internal/tui/form.go @@ -2,6 +2,7 @@ package tui import ( "encoding/binary" + "os" "strconv" "strings" @@ -68,14 +69,14 @@ var ( // 長さとか他のフィールドに基づいて計算しないといけない値があるから、そこは固定値ではなくてリアルタイムに反映したい // とすると、高レイヤーの入力から埋めて進めていくようにしないといけなさそう. ユーザーが選べるようにするのがいいかも -func (t *tui) form(sendFn func(*packemon.EthernetFrame) error) error { +func (t *tui) form(stop <-chan os.Signal, sendFn func(*packemon.EthernetFrame) error) error { d, err := defaultPackets() if err != nil { return err } ethernetHeader, arp, ipv4, icmp, udp, tcp, dns, http := d.e, d.a, d.ip, d.ic, d.u, d.t, d.d, d.h - httpForm := t.httpForm(sendFn, ethernetHeader, ipv4, tcp, http) + httpForm := t.httpForm(stop, sendFn, ethernetHeader, ipv4, tcp, http) httpForm.SetBorder(true).SetTitle(" HTTP ").SetTitleAlign(tview.AlignLeft) dnsForm := t.dnsForm(sendFn, ethernetHeader, ipv4, udp, dns) dnsForm.SetBorder(true).SetTitle(" DNS ").SetTitleAlign(tview.AlignLeft) diff --git a/internal/tui/form_http.go b/internal/tui/form_http.go index f60e310..c158ecd 100644 --- a/internal/tui/form_http.go +++ b/internal/tui/form_http.go @@ -2,12 +2,31 @@ package tui import ( "encoding/binary" + "os" "github.com/ddddddO/packemon" "github.com/rivo/tview" ) -func (t *tui) httpForm(sendFn func(*packemon.EthernetFrame) error, ethernetHeader *packemon.EthernetHeader, ipv4 *packemon.IPv4, tcp *packemon.TCP, http *packemon.HTTP) *tview.Form { +// var threeWayHandshakeDescription = "When 3-way handshake for TCP is enabled, only \n\n - HTTP section\n - TCP Destination Port\n - IPv4 Destination IP Addr\n\nare reflected as input." + +var threeWayHandshakeDescription = "" + + "When 3-way handshake for TCP is enabled, only" + "\n\n" + + " HTTP" + "\n" + + " - All" + "\n" + + " TCP" + "\n" + + " - Source Port" + "\n" + + " - Destination Port" + "\n" + + " IPv4" + "\n" + + " - Source IP Addr" + "\n" + + " - Destination IP Addr" + "\n" + + " Ethernet" + "\n" + + " - Destination MAC Addr" + "\n" + + " - Source MAC Addr" + "\n" + + "\n" + + "are reflected as input." + +func (t *tui) httpForm(stop <-chan os.Signal, sendFn func(*packemon.EthernetFrame) error, ethernetHeader *packemon.EthernetHeader, ipv4 *packemon.IPv4, tcp *packemon.TCP, http *packemon.HTTP) *tview.Form { do3wayHandshake := false httpForm := tview.NewForm(). @@ -15,7 +34,7 @@ func (t *tui) httpForm(sendFn func(*packemon.EthernetFrame) error, ethernetHeade AddCheckbox("Do 3way handshake ?", do3wayHandshake, func(checked bool) { do3wayHandshake = checked }). - AddTextView("", "When 3-way handshake for TCP is enabled, only \n\n - HTTP section\n - TCP Destination Port\n - IPv4 Destination IP Addr\n\nare reflected as input.", 60, 7, true, true). + AddTextView("", threeWayHandshakeDescription, 60, 15, true, true). AddInputField("Method", DEFAULT_HTTP_METHOD, 10, func(textToCheck string, lastChar rune) bool { if len(textToCheck) <= 10 { http.Method = textToCheck @@ -65,16 +84,30 @@ func (t *tui) httpForm(sendFn func(*packemon.EthernetFrame) error, ethernetHeade if do3wayHandshake { dstIPAddr := make([]byte, 4) binary.BigEndian.PutUint32(dstIPAddr, ipv4.DstAddr) - if err := packemon.EstablishConnectionAndSendPayload( - DEFAULT_NW_INTERFACE, - dstIPAddr, - tcp.DstPort, - // []byte{0xc0, 0xa8, 0x0a, 0x6e}, - // 0x0050, - http.Bytes(), - ); err != nil { - t.addErrPage(err) - } + // if err := packemon.EstablishConnectionAndSendPayload( + // DEFAULT_NW_INTERFACE, + // dstIPAddr, + // tcp.DstPort, + // // []byte{0xc0, 0xa8, 0x0a, 0x6e}, + // // 0x0050, + // http.Bytes(), + // ); err != nil { + // t.addErrPage(err) + // } + + go func() { + if err := packemon.EstablishConnectionAndSendPayloadXxx( + stop, + DEFAULT_NW_INTERFACE, + ethernetHeader, + ipv4, + tcp, + http, + ); err != nil { + t.addErrPage(err) + } + }() + return } diff --git a/internal/tui/tui.go b/internal/tui/tui.go index c904c2f..49837da 100644 --- a/internal/tui/tui.go +++ b/internal/tui/tui.go @@ -1,6 +1,7 @@ package tui import ( + "os" "sync" "github.com/ddddddO/packemon" @@ -57,8 +58,8 @@ func newMonitor() *tui { } } -func (t *tui) Generator(sendFn func(*packemon.EthernetFrame) error) error { - if err := t.form(sendFn); err != nil { +func (t *tui) Generator(stop <-chan os.Signal, sendFn func(*packemon.EthernetFrame) error) error { + if err := t.form(stop, sendFn); err != nil { return err } return t.app.SetRoot(t.grid, true).EnableMouse(true).SetFocus(t.grid).Run() diff --git a/networkinterface.go b/networkinterface.go index 1cdf953..ee7ecea 100644 --- a/networkinterface.go +++ b/networkinterface.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "errors" "net" + "os" "strings" "golang.org/x/sys/unix" @@ -88,7 +89,7 @@ func (nw *NetworkInterface) Send(ethernetFrame *EthernetFrame) error { return unix.Sendto(nw.Socket, ethernetFrame.Bytes(), 0, &nw.SocketAddr) } -func (nw *NetworkInterface) Recieve() error { +func (nw *NetworkInterface) Recieve(stop <-chan os.Signal) error { epollfd, err := unix.EpollCreate1(0) if err != nil { return err @@ -108,28 +109,36 @@ func (nw *NetworkInterface) Recieve() error { events := make([]unix.EpollEvent, 10) for { - fds, err := unix.EpollWait(epollfd, events, -1) - if err != nil { - return err - } + select { + case <-stop: + return nil + default: + fds, err := unix.EpollWait(epollfd, events, -1) + if err != nil { + return err + } - for i := 0; i < fds; i++ { - if events[i].Fd == int32(nw.Socket) { - recieved := make([]byte, 1500) - n, _, err := unix.Recvfrom(nw.Socket, recieved, 0) - if err != nil { - if n == -1 { - continue + for i := 0; i < fds; i++ { + select { + case <-stop: + return nil + default: + if events[i].Fd == int32(nw.Socket) { + recieved := make([]byte, 1500) + n, _, err := unix.Recvfrom(nw.Socket, recieved, 0) + if err != nil { + if n == -1 { + continue + } + return err + } + + nw.PassiveCh <- ParsedPacket(recieved) } - return err } - - nw.PassiveCh <- ParsedPacket(recieved) } } } - - return nil } func (nw *NetworkInterface) Close() error { diff --git a/tcp.go b/tcp.go index 4cf2849..5b049fd 100644 --- a/tcp.go +++ b/tcp.go @@ -3,6 +3,9 @@ package packemon import ( "bytes" "encoding/binary" + "os" + + "golang.org/x/sys/unix" ) const ( @@ -171,8 +174,204 @@ func EstablishConnectionAndSendPayload(nwInterface string, dstIPAddr []byte, dst return nil } -// TODO: debugNetworkInterface の SendTCP3wayhandshake をベースに。上の関数はOSが良しなに3way handshakeしてくれるやつ -// L7用のコールバックを渡すようにするとイイかも。引数にtcpのシーケンスと確認応答の番号、返り値にEthernetFrameな感じで。引数はもう少し必要なものあるかな -func EstablishConnectionAndSendPayloadXxx() error { - return nil +// このなかで、ログ出力などしないこと。Monitor の下に出てくる +// 挙動を詳細に確認する場合は、internal内の SendTCP3wayhandshake 関数でやること +// TODO: 対向からRST,RST/ACKが来た時にreturnするようにする +// TODO: http専用になっちゃってるから、他のプロトコルでも使えるよう汎用的にする +func EstablishConnectionAndSendPayloadXxx(stop <-chan os.Signal, nwInterface string, fEthrh *EthernetHeader, fIpv4 *IPv4, fTcp *TCP, fHttp *HTTP) error { + nw, err := NewNetworkInterface(nwInterface) + if err != nil { + return err + } + + var srcPort uint16 = fTcp.SrcPort + var dstPort uint16 = fTcp.DstPort + var srcIPAddr uint32 = fIpv4.SrcAddr + var dstIPAddr uint32 = fIpv4.DstAddr + dstMACAddr := fEthrh.Dst + srcMACAddr := fEthrh.Src + + tcp := NewTCPSyn(srcPort, dstPort) + ipv4 := NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame := NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + + epollfd, err := unix.EpollCreate1(0) + if err != nil { + return err + } + + if err := unix.EpollCtl( + epollfd, + unix.EPOLL_CTL_ADD, + nw.Socket, + &unix.EpollEvent{ + Events: unix.EPOLLIN, + Fd: int32(nw.Socket), + }, + ); err != nil { + return err + } + + events := make([]unix.EpollEvent, 10) + for { + select { + case <-stop: + return nil + + default: + fds, err := unix.EpollWait(epollfd, events, -1) + if err != nil { + return err + } + + for i := 0; i < fds; i++ { + select { + case <-stop: + return nil + default: + if events[i].Fd == int32(nw.Socket) { + recieved := make([]byte, 1500) + n, _, err := unix.Recvfrom(nw.Socket, recieved, 0) + if err != nil { + if n == -1 { + continue + } + return err + } + + ethernetFrame := ParsedEthernetFrame(recieved) + + switch ethernetFrame.Header.Typ { + case ETHER_TYPE_IPv4: + ipv4 := ParsedIPv4(ethernetFrame.Data) + + switch ipv4.Protocol { + case IPv4_PROTO_TCP: + tcp := ParsedTCP(ipv4.Data) + + switch tcp.DstPort { + case srcPort: // synパケットの送信元ポート + if tcp.Flags == TCP_FLAGS_SYN_ACK { + // log.Println("passive TCP_FLAGS_SYN_ACK") + + // syn/ackを受け取ったのでack送信 + tcp := NewTCPAck(srcPort, dstPort, tcp.Sequence, tcp.Acknowledgment) + ipv4 := NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame := NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + + tcp = NewTCPWithData(srcPort, dstPort, fHttp.Bytes(), tcp.Sequence, tcp.Acknowledgment) + ipv4 = NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame = NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + + continue + } + + if tcp.Flags == TCP_FLAGS_ACK { + // log.Println("passive TCP_FLAGS_ACK") + continue + } + + if tcp.Flags == TCP_FLAGS_PSH_ACK { + lineLength := bytes.Index(tcp.Data, []byte{0x0d, 0x0a}) // "\r\n" + if lineLength == -1 { + // log.Println("-1") + continue + } + // log.Println("passive TCP_FLAGS_PSH_ACK") + + // HTTPレスポンス受信 + if tcp.SrcPort == PORT_HTTP { + resp := ParsedHTTPResponse(tcp.Data) + // log.Printf("%+v\n", resp) + + // そのackを返す + // log.Printf("Length of http resp: %d\n", resp.Len()) + + tcp := NewTCPAckForPassiveData(srcPort, dstPort, tcp.Sequence, tcp.Acknowledgment, resp.Len()) + ipv4 := NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame := NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + + // 続けてFinAck + tcp = NewTCPFinAck(srcPort, dstPort, tcp.Sequence, tcp.Acknowledgment) + ipv4 = NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame = NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + } + continue + } + + if tcp.Flags == TCP_FLAGS_FIN_ACK { + // log.Println("passive TCP_FLAGS_FIN_ACK") + + // それにack + tcp := NewTCPAck(srcPort, dstPort, tcp.Sequence, tcp.Acknowledgment) + ipv4 := NewIPv4(IPv4_PROTO_TCP, srcIPAddr, dstIPAddr) + tcp.CalculateChecksum(ipv4) + + ipv4.Data = tcp.Bytes() + ipv4.CalculateTotalLength() + ipv4.CalculateChecksum() + + ethernetFrame := NewEthernetFrame(dstMACAddr, srcMACAddr, ETHER_TYPE_IPv4, ipv4.Bytes()) + if err := nw.Send(ethernetFrame); err != nil { + return err + } + return nil + } + + continue + default: + // noop + } + } + } + } + } + } + } + } }