From 7682e19876a75f5986f6cfb93f4583cdc9cabc13 Mon Sep 17 00:00:00 2001 From: Kioubit Date: Mon, 20 Dec 2021 14:53:42 -0500 Subject: [PATCH] Initial commit --- .idea/.gitignore | 8 +++ .idea/modules.xml | 8 +++ .idea/pndpd.iml | 9 ++++ .idea/remote-targets.xml | 20 +++++++ .idea/vcs.xml | 6 +++ NDPRequest.go | 7 +++ go.mod | 8 +++ go.sum | 8 +++ main.go | 19 +++++++ packet.go | 114 +++++++++++++++++++++++++++++++++++++++ rawsocket.go | 114 +++++++++++++++++++++++++++++++++++++++ responder.go | 37 +++++++++++++ 12 files changed, 358 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/modules.xml create mode 100644 .idea/pndpd.iml create mode 100644 .idea/remote-targets.xml create mode 100644 .idea/vcs.xml create mode 100644 NDPRequest.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 packet.go create mode 100644 rawsocket.go create mode 100644 responder.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..1c2fda5 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..ee87094 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/pndpd.iml b/.idea/pndpd.iml new file mode 100644 index 0000000..338a266 --- /dev/null +++ b/.idea/pndpd.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/remote-targets.xml b/.idea/remote-targets.xml new file mode 100644 index 0000000..0a14203 --- /dev/null +++ b/.idea/remote-targets.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/NDPRequest.go b/NDPRequest.go new file mode 100644 index 0000000..f69279c --- /dev/null +++ b/NDPRequest.go @@ -0,0 +1,7 @@ +package main + +type NDRequest struct { + srcIP []byte + answeringForIP []byte + mac []byte +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4ec71c1 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module pndpd + +go 1.17 + +require ( + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f + golang.org/x/sys v0.0.0-20210423082822-04245dca01da +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3bd6ac1 --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f h1:hEYJvxw1lSnWIl8X9ofsYMklzaDs90JI2az5YMd4fPM= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/main.go b/main.go new file mode 100644 index 0000000..803bafc --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os" +) + +func main() { + if len(os.Args) <= 1 { + fmt.Println("Specify interface") + os.Exit(1) + } + iface := os.Args[1] + requests := make(chan *NDRequest, 100) + defer close(requests) + go respond(iface, requests) + listen(iface, requests) + +} diff --git a/packet.go b/packet.go new file mode 100644 index 0000000..7b4d9a5 --- /dev/null +++ b/packet.go @@ -0,0 +1,114 @@ +package main + +import ( + "encoding/binary" +) + +var emptyIpv6 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + +type Payload interface { + constructPacket() ([]byte, int) +} + +type IPv6Header struct { + protocol byte + srcIP []byte + dstIP []byte + payloadLen []byte + payload []byte +} + +func newIpv6Header(srcIp []byte, dstIp []byte) *IPv6Header { + return &IPv6Header{dstIP: dstIp, srcIP: srcIp, protocol: 0x3a} +} + +func (h *IPv6Header) addPayload(payload Payload) { + bPayload, checksumPos := payload.constructPacket() + bPayloadLen := make([]byte, 2) + binary.BigEndian.PutUint16(bPayloadLen, uint16(len(bPayload))) + h.payloadLen = bPayloadLen + + if checksumPos > 0 { + bChecksum := make([]byte, 2) + binary.BigEndian.PutUint16(bChecksum, calculateChecksum(h, bPayload)) + bPayload[checksumPos] = bChecksum[0] + bPayload[checksumPos+1] = bChecksum[1] + } + + h.payload = bPayload +} + +func (h *IPv6Header) constructPacket() []byte { + header := []byte{ + 0x60, // v6 + 0, // qos + 0, // qos + 0, // qos + h.payloadLen[0], // Payload Length + h.payloadLen[1], // Payload Length + h.protocol, // Protocol next header + 0xff, // Hop limit + } + final := append(header, h.srcIP...) + final = append(final, h.dstIP...) + final = append(final, h.payload...) + return final +} + +type NDPAdvPayload struct { + answeringForIP []byte + mac []byte +} + +func newNdpPacket(answeringForIP []byte, mac []byte) *NDPAdvPayload { + return &NDPAdvPayload{ + answeringForIP: answeringForIP, + mac: mac, + } +} + +func (p *NDPAdvPayload) constructPacket() ([]byte, int) { + header := []byte{ + 0x88, // Type: Neighbor Advertisement + 0x0, // Code + 0x0, // Checksum filled in later + 0x0, // Checksum filled in later + 0x60, // Flags (Solicited,Override) + 0x0, // Reserved + 0x0, // Reserved + 0x0, // Reserved + } + if len(p.answeringForIP) != 16 { + panic("malformed IP") + } //TODO check IP lengths on assign everywhere + final := append(header, p.answeringForIP...) + + secondHeader := []byte{ + 0x02, // Type: Target link-layer address (2) + 0x01, // Length: 1 (8 bytes) + } + final = append(final, secondHeader...) + + final = append(final, p.mac...) + return final, 2 +} + +func calculateChecksum(h *IPv6Header, payload []byte) uint16 { + sumPseudoHeader := checksumAddition(h.srcIP) + checksumAddition(h.dstIP) + checksumAddition([]byte{0x00, h.protocol}) + checksumAddition(h.payloadLen) + sumPayload := checksumAddition(payload) + sumTotal := sumPayload + sumPseudoHeader + for sumTotal>>16 > 0x0 { + sumTotal = (sumTotal & 0xffff) + (sumTotal >> 16) + } + return uint16(sumTotal) ^ 0xFFFF + +} +func checksumAddition(b []byte) uint32 { + var sum uint32 = 0 + for i := 0; i < len(b); i++ { + if i%2 == 0 { + sum += uint32(uint16(b[i])<<8 | uint16(b[i+1])) + } + } + return sum +} diff --git a/rawsocket.go b/rawsocket.go new file mode 100644 index 0000000..58eff10 --- /dev/null +++ b/rawsocket.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "golang.org/x/net/bpf" + "golang.org/x/sys/unix" + "net" + "syscall" + "unsafe" +) + +// Filter represents a classic BPF filter program that can be applied to a socket +type Filter []bpf.Instruction + +// ApplyTo applies the current filter onto the provided file descriptor +func (filter Filter) ApplyTo(fd int) (err error) { + var assembled []bpf.RawInstruction + if assembled, err = bpf.Assemble(filter); err != nil { + return err + } + + var program = unix.SockFprog{ + Len: uint16(len(assembled)), + Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])), + } + var b = (*[unix.SizeofSockFprog]byte)(unsafe.Pointer(&program))[:unix.SizeofSockFprog] + + if _, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT, + uintptr(fd), uintptr(syscall.SOL_SOCKET), uintptr(syscall.SO_ATTACH_FILTER), + uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0); errno != 0 { + return errno + } + + return nil +} + +// Htons Convert a uint16 to host byte order (big endian) +func htons(v uint16) int { + return int((v << 8) | (v >> 8)) +} +func htons16(v uint16) uint16 { return v<<8 | v>>8 } + +func listen(iface string, responder chan *NDRequest) { + + niface, err := net.InterfaceByName(iface) + if err != nil { + panic(err.Error()) + } + tiface := &syscall.SockaddrLinklayer{ + Protocol: htons16(syscall.ETH_P_IPV6), + Ifindex: niface.Index, + } + fmt.Println(niface.HardwareAddr) + + fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, htons(syscall.ETH_P_IPV6)) + if err != nil { + fmt.Println(err.Error()) + } + defer syscall.Close(fd) + fmt.Println("Obtained fd ", fd) + + if len([]byte(iface)) > syscall.IFNAMSIZ { + panic("Interface size larger then maximum allowed by the kernel") + } + + err = syscall.Bind(fd, tiface) + if err != nil { + fmt.Println(err.Error()) + } + + var f Filter = []bpf.Instruction{ + // Load "EtherType" field from the ethernet header. + bpf.LoadAbsolute{Off: 12, Size: 2}, + // Jump to the drop packet instruction if EtherType is not IPv6. + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x86dd, SkipTrue: 4}, + // Load "Next Header" field from IPV6 header. + bpf.LoadAbsolute{Off: 20, Size: 1}, + // Jump to the drop packet instruction if Next Header is not ICMPv6. + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x3a, SkipTrue: 2}, + // Load "Type" field from ICMPv6 header. + bpf.LoadAbsolute{Off: 54, Size: 1}, + // Jump to the drop packet instruction if Type is not Neighbor Solicitation. + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x87, SkipTrue: 1}, + // Verdict is "send up to 4k of the packet to userspace." + bpf.RetConstant{Val: 4096}, + // Verdict is "ignore packet." + bpf.RetConstant{Val: 0}, + } + + err = f.ApplyTo(fd) + if err != nil { + panic(err.Error()) + } + + for { + buf := make([]byte, 4096) + numRead, err := syscall.Read(fd, buf) + if err != nil { + panic(err) + } + fmt.Println("Source IP:") + fmt.Printf("% X\n", buf[:numRead][22:38]) + fmt.Println("Requested IP:") + fmt.Printf("% X\n", buf[:numRead][62:78]) + fmt.Println("Source MAC") + fmt.Printf("% X\n", buf[:numRead][80:86]) + fmt.Println() + responder <- &NDRequest{ + srcIP: buf[:numRead][22:38], + answeringForIP: buf[:numRead][62:78], + mac: niface.HardwareAddr, + } + } +} diff --git a/responder.go b/responder.go new file mode 100644 index 0000000..9665f66 --- /dev/null +++ b/responder.go @@ -0,0 +1,37 @@ +package main + +import ( + "syscall" +) + +var fd int + +func respond(iface string, requests chan *NDRequest) { + fd, _ = syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW) + syscall.BindToDevice(fd, iface) + + for { + n := <-requests + pkt(n.srcIP, n.answeringForIP, n.mac) + } +} + +func pkt(srcip []byte, tgtip []byte, mac []byte) { + v6 := newIpv6Header(emptyIpv6, srcip) + NDPa := newNdpPacket(tgtip, mac) + v6.addPayload(NDPa) + response := v6.constructPacket() + + var t [16]byte + copy(t[:], srcip) + d := syscall.SockaddrInet6{ + Port: 0, + Addr: t, + } + err := syscall.Sendto(fd, response, 0, &d) + if err != nil { + panic(err) + } + + syscall.Close(fd) +}