diff --git a/NDPRequest.go b/NDPRequest.go index f69279c..b646f4f 100644 --- a/NDPRequest.go +++ b/NDPRequest.go @@ -1,6 +1,15 @@ package main +type NDPType int + +const ( + NDP_ADV NDPType = 0 + NDP_SOL NDPType = 1 +) + type NDRequest struct { + requestType NDPType + //TODO use global unicast for router advertisements srcIP []byte answeringForIP []byte mac []byte diff --git a/main.go b/main.go index 803bafc..49c0186 100644 --- a/main.go +++ b/main.go @@ -6,14 +6,53 @@ import ( ) func main() { + fmt.Println("Usage: pndpd respond ") + fmt.Println("Usage: pndpd proxy ") + if len(os.Args) <= 1 { - fmt.Println("Specify interface") + fmt.Println("Specify command") os.Exit(1) } - iface := os.Args[1] - requests := make(chan *NDRequest, 100) - defer close(requests) - go respond(iface, requests) - listen(iface, requests) + if os.Args[1] == "respond" { + simpleRespond(os.Args[2]) + } + if os.Args[1] == "proxy" { + proxy(os.Args[2], os.Args[3]) + } + +} + +func simpleRespond(iface string) { + requests := make(chan *NDRequest, 100) + defer close(requests) + go respond(iface, requests, NDP_ADV) + go listen(iface, requests, NDP_SOL) + select {} + //TODO os.signal +} + +func proxy(iface1, iface2 string) { + req_iface1_sol_iface2 := make(chan *NDRequest, 100) + defer close(req_iface1_sol_iface2) + go listen(iface1, req_iface1_sol_iface2, NDP_SOL) + go respond(iface2, req_iface1_sol_iface2, NDP_SOL) + + req_iface2_sol_iface1 := make(chan *NDRequest, 100) + defer close(req_iface2_sol_iface1) + go listen(iface2, req_iface2_sol_iface1, NDP_SOL) + go respond(iface1, req_iface2_sol_iface1, NDP_SOL) + + req_iface1_adv_iface2 := make(chan *NDRequest, 100) + defer close(req_iface1_adv_iface2) + go listen(iface1, req_iface1_adv_iface2, NDP_ADV) + go respond(iface2, req_iface1_adv_iface2, NDP_ADV) + + req_iface2_adv_iface1 := make(chan *NDRequest, 100) + defer close(req_iface2_adv_iface1) + go listen(iface2, req_iface2_adv_iface1, NDP_ADV) + go respond(iface1, req_iface2_adv_iface1, NDP_ADV) + + select {} + // TODO os.signal } diff --git a/packet.go b/packet.go index 7b4d9a5..05bf817 100644 --- a/packet.go +++ b/packet.go @@ -55,28 +55,39 @@ func (h *IPv6Header) constructPacket() []byte { return final } -type NDPAdvPayload struct { +type NdpPayload struct { + packetType NDPType answeringForIP []byte mac []byte } -func newNdpPacket(answeringForIP []byte, mac []byte) *NDPAdvPayload { - return &NDPAdvPayload{ +func newNdpPacket(answeringForIP []byte, mac []byte, packetType NDPType) *NdpPayload { + return &NdpPayload{ + packetType: packetType, answeringForIP: answeringForIP, mac: mac, } } -func (p *NDPAdvPayload) constructPacket() ([]byte, int) { +func (p *NdpPayload) constructPacket() ([]byte, int) { + var protocol byte + var flags byte + if p.packetType == NDP_SOL { + protocol = 0x87 + flags = 0x0 + } else { + protocol = 0x88 + flags = 0x60 + } 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 + protocol, // Type: NDPType + 0x0, // Code + 0x0, // Checksum filled in later + 0x0, // Checksum filled in later + flags, // Flags (Solicited,Override) + 0x0, // Reserved + 0x0, // Reserved + 0x0, // Reserved } if len(p.answeringForIP) != 16 { panic("malformed IP") diff --git a/rawsocket.go b/rawsocket.go index 58eff10..fa9dceb 100644 --- a/rawsocket.go +++ b/rawsocket.go @@ -40,7 +40,7 @@ func htons(v uint16) int { } func htons16(v uint16) uint16 { return v<<8 | v>>8 } -func listen(iface string, responder chan *NDRequest) { +func listen(iface string, responder chan *NDRequest, requestType NDPType) { niface, err := net.InterfaceByName(iface) if err != nil { @@ -68,23 +68,45 @@ func listen(iface string, responder chan *NDRequest) { 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}, + var f Filter + if requestType == NDP_SOL { + f = []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}, + } + } else { + f = []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 Advertisement. + bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: 0x88, 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) @@ -106,9 +128,10 @@ func listen(iface string, responder chan *NDRequest) { fmt.Printf("% X\n", buf[:numRead][80:86]) fmt.Println() responder <- &NDRequest{ + requestType: requestType, srcIP: buf[:numRead][22:38], answeringForIP: buf[:numRead][62:78], - mac: niface.HardwareAddr, + mac: buf[:numRead][80:86], } } } diff --git a/responder.go b/responder.go index 9665f66..6eac2a5 100644 --- a/responder.go +++ b/responder.go @@ -1,29 +1,40 @@ package main import ( + "net" "syscall" ) var fd int -func respond(iface string, requests chan *NDRequest) { +func respond(iface string, requests chan *NDRequest, respondType NDPType) { fd, _ = syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW) syscall.BindToDevice(fd, iface) + niface, err := net.InterfaceByName(iface) + if err != nil { + panic(err.Error()) + } + for { n := <-requests - pkt(n.srcIP, n.answeringForIP, n.mac) + pkt(n.srcIP, n.answeringForIP, niface.HardwareAddr, respondType) } } -func pkt(srcip []byte, tgtip []byte, mac []byte) { +func pkt(srcip []byte, tgtip []byte, mac []byte, respondType NDPType) { v6 := newIpv6Header(emptyIpv6, srcip) - NDPa := newNdpPacket(tgtip, mac) + NDPa := newNdpPacket(tgtip, mac, respondType) v6.addPayload(NDPa) response := v6.constructPacket() var t [16]byte - copy(t[:], srcip) + if respondType == NDP_SOL { + copy(t[:], []byte{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x02}) + } else { + copy(t[:], srcip) + } + d := syscall.SockaddrInet6{ Port: 0, Addr: t,