Compare commits

..

No commits in common. "burble.dn42" and "0.7" have entirely different histories.

20 changed files with 402 additions and 943 deletions

View File

@ -1,55 +0,0 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: build
image: golang
environment:
CGO_ENABLED: 0
commands:
- go vet
- go build
- name: upload binary
image: plugins/s3
settings:
bucket: artifacts
access_key:
from_secret: MINIO_ACCESS_KEY
secret_key:
from_secret: MINIO_SECRET_KEY
endpoint: https://minio.burble.dn42
region: uk-lon3
path_style: true
source: pndpd
target: /pndpd/${DRONE_BRANCH}
- name: upload service
image: plugins/s3
settings:
bucket: artifacts
access_key:
from_secret: MINIO_ACCESS_KEY
secret_key:
from_secret: MINIO_SECRET_KEY
endpoint: https://minio.burble.dn42
region: uk-lon3
path_style: true
source: pndpd.service
target: /pndpd/${DRONE_BRANCH}
---
kind: secret
name: MINIO_ACCESS_KEY
get:
path: burble.dn42/kv/data/drone/minio
name: ACCESS_KEY
---
kind: secret
name: MINIO_SECRET_KEY
get:
path: burble.dn42/kv/data/drone/minio
name: SECRET_KEY

5
.gitignore vendored
View File

@ -1,7 +1,2 @@
*.iml *.iml
.idea .idea
bin/
*~
pndpd

View File

@ -1,21 +0,0 @@
# Makefile for PNDPD
BINARY=pndpd
MODULES=
VERSION=`git describe --tags`
LDFLAGS=-ldflags "-X main.Version=${VERSION}"
build:
go build -tags=${MODULES} -o bin/${BINARY} .
release:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_amd64.bin .
release-all:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_amd64.bin
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_arm64.bin
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_arm.bin
clean:
find bin/ -type f -delete
if [ -d "bin/" ]; then rm -d bin/ ;fi

View File

@ -1,51 +1,21 @@
# PNDPD - NDP Responder + Proxy # PNDPD - NDP Responder + Proxy
[![Build Status](https://ci.burble.dn42/api/badges/mirrors/Pndpd/status.svg?ref=refs/heads/burble.dn42)](https://ci.burble.dn42/mirrors/Pndpd)
## Features ## Features
- **Efficiently** process incoming packets using bpf (which runs in the kernel) - Efficiently process incoming packets using bpf (which runs in the kernel)
- Respond to all NDP solicitations on an interface - Respond to all NDP solicitations on an interface
- **Respond** to NDP solicitations for whitelisted addresses on an interface - Respond to NDP solicitations for whitelisted addresses on an interface
- **Proxy** NDP between interfaces with an optional whitelist - Proxy NDP between interfaces with an optional whitelist
- Optionally determine whitelist **automatically** based on the IPs assigned to the interfaces - Optionally determine whitelist automatically based on the IPs assigned to the interfaces
- Permissions required: root or CAP_NET_RAW - Permissions required: root or CAP_NET_RAW
- Easily expandable with modules
## Installing & Updating ## Usage
1) Download the latest release from the [releases page](https://github.com/Kioubit/pndpd/releases) and move the binary to the ``/usr/local/bin/`` directory under the filename ``pndpd``.
2) Allow executing the file by running ``chmod +x /usr/local/bin/pndpd``
3) **For systemd users:** Install the service unit file
````
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.service -P /etc/systemd/system/
systemctl enable pndpd.service
````
4) Download and install the config file
````
mkdir -p /etc/pndpd
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.conf -P /etc/pndpd/
````
5) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start``
## Manual Usage
```` ````
pndpd config <path to file> pndpd config <path to file>
pndpd responder <interface> <optional whitelist of CIDRs separated by a semicolon> pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>
pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2> pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>
```` ````
More options and additional documentation in the example config file (``pndpd.conf``). More options and additional documentation in the example config file (pndpd.conf).
## Developing ### Developing
### Building
For building, the version of go needs to be installed that is specified in the go.mod file. A makefile is available. Optionally adjust the ``MODULES`` variable to include or exclude modules from the modules directory.
````
make clean
make release-all
````
Find the binaries in the ``bin/`` directory
### Adding Modules
It is easy to add functionality to PNDPD. For additions outside the core functionality you only need to keep the following methods in mind: It is easy to add functionality to PNDPD. For additions outside the core functionality you only need to keep the following methods in mind:
```` ````
package main package main
@ -61,6 +31,4 @@ proxyInstance := pndp.NewProxy(iface1 string, iface2 string, filter []*net.IPNet
proxyInstance.Start() proxyInstance.Start()
proxyInstance.Stop() proxyInstance.Stop()
```` ````
New functionality should be implemented as a module. You will find an example module under ``modules/example/``.
Pull requests are welcome for any functionality you add. Pull requests are welcome for any functionality you add.

135
config.go
View File

@ -3,27 +3,42 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"log"
"os" "os"
"pndpd/modules"
"pndpd/pndp" "pndpd/pndp"
"strings" "strings"
) )
type configResponder struct {
Iface string
Filter string
autosense string
instance *pndp.ResponderObj
}
type configProxy struct {
Iface1 string
Iface2 string
Filter string
autosense string
instance *pndp.ProxyObj
}
var allResponders []*configResponder
var allProxies []*configProxy
func readConfig(dest string) { func readConfig(dest string) {
file, err := os.Open(dest) file, err := os.Open(dest)
if err != nil { if err != nil {
fmt.Println("Error:", err.Error()) log.Fatal(err)
os.Exit(1)
} }
defer func(file *os.File) { defer file.Close()
_ = file.Close()
}(file)
scanner := bufio.NewScanner(file) scanner := bufio.NewScanner(file)
for scanner.Scan() { for scanner.Scan() {
line := scanner.Text() line := scanner.Text()
if strings.HasPrefix(line, "//") || strings.TrimSpace(line) == "" { if strings.HasPrefix(line, "//") {
continue continue
} }
if strings.HasPrefix(line, "debug") { if strings.HasPrefix(line, "debug") {
@ -33,41 +48,89 @@ func readConfig(dest string) {
} }
continue continue
} }
if strings.HasPrefix(line, "responder") && strings.Contains(line, "{") {
if strings.HasSuffix(line, "{") { obj := configResponder{}
option := strings.TrimSuffix(strings.TrimSpace(line), "{") filter := ""
option = strings.TrimSpace(option) for {
module, command := modules.GetCommand(option, modules.Config) scanner.Scan()
var lines = make([]string, 0) line = strings.TrimSpace(scanner.Text())
if module != nil { if strings.HasPrefix(line, "iface") {
for { obj.Iface = strings.TrimSpace(strings.TrimPrefix(line, "iface"))
if !scanner.Scan() { }
break if strings.HasPrefix(line, "filter") {
} filter += strings.TrimSpace(strings.TrimPrefix(line, "filter")) + ";"
line := strings.TrimSpace(scanner.Text()) if strings.Contains(line, ";") {
if strings.Contains(line, "}") { panic("Invalid config file syntax")
break }
} }
if strings.HasPrefix(line, "autosense") {
lines = append(lines, line) obj.autosense = strings.TrimSpace(strings.TrimPrefix(line, "autosense"))
}
if strings.HasPrefix(line, "}") {
obj.Filter = strings.TrimSuffix(filter, ";")
break
} }
modules.ExecuteInit(module, modules.CallbackInfo{
CallbackType: modules.Config,
Command: command,
Arguments: lines,
})
} }
}
} allResponders = append(allResponders, &obj)
if modules.ExistsBlockingModule() { }
modules.ExecuteComplete() if strings.HasPrefix(line, "proxy") && strings.Contains(line, "{") {
waitForSignal() obj := configProxy{}
modules.ShutdownAll() filter := ""
for {
scanner.Scan()
line = strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "iface1") {
obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(line, "iface1"))
}
if strings.HasPrefix(line, "iface2") {
obj.Iface2 = strings.TrimSpace(strings.TrimPrefix(line, "iface2"))
}
if strings.HasPrefix(line, "filter") {
filter += strings.TrimSpace(strings.TrimPrefix(line, "filter")) + ";"
if strings.Contains(line, ";") {
panic("Invalid config file syntax")
}
}
if strings.HasPrefix(line, "autosense") {
obj.autosense = strings.TrimSpace(strings.TrimPrefix(line, "autosense"))
}
if strings.HasPrefix(line, "}") {
obj.Filter = strings.TrimSuffix(filter, ";")
break
}
if strings.HasPrefix(line, "}") {
break
}
}
allProxies = append(allProxies, &obj)
}
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
panic(err) log.Fatal(err)
}
for _, n := range allProxies {
o := pndp.NewProxy(n.Iface1, n.Iface2, pndp.ParseFilter(n.Filter), n.autosense)
n.instance = o
o.Start()
}
for _, n := range allResponders {
o := pndp.NewResponder(n.Iface, pndp.ParseFilter(n.Filter), n.autosense)
n.instance = o
o.Start()
}
WaitForSignal()
for _, n := range allProxies {
n.instance.Stop()
}
for _, n := range allResponders {
n.instance.Stop()
} }
} }

75
main.go
View File

@ -4,65 +4,60 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"pndpd/modules" "pndpd/pndp"
"syscall" "syscall"
) )
import (
// Modules
_ "pndpd/modules/example"
_ "pndpd/modules/userInterface"
)
var Version = "Development" // WaitForSignal Waits (blocking) for the program to be interrupted by the OS
func WaitForSignal() {
var sigCh = make(chan os.Signal)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
close(sigCh)
}
func main() { func main() {
fmt.Println("PNDPD Version", Version, "- Kioubit 2022") fmt.Println("PNDPD Version 0.7 - Kioubit 2021")
if len(os.Args) <= 2 { if len(os.Args) <= 2 {
printUsage() printUsage()
return return
} }
switch os.Args[1] { switch os.Args[1] {
case "respond":
var r *pndp.ResponderObj
if len(os.Args) == 4 {
r = pndp.NewResponder(os.Args[2], pndp.ParseFilter(os.Args[3]), "")
r.Start()
} else {
r = pndp.NewResponder(os.Args[2], nil, "")
fmt.Println("WARNING: You should use a whitelist unless you know what you are doing")
r.Start()
}
WaitForSignal()
r.Stop()
case "proxy":
var p *pndp.ProxyObj
if len(os.Args) == 5 {
p = pndp.NewProxy(os.Args[2], os.Args[3], pndp.ParseFilter(os.Args[4]), "")
} else {
p = pndp.NewProxy(os.Args[2], os.Args[3], nil, "")
}
WaitForSignal()
p.Stop()
case "config": case "config":
readConfig(os.Args[2]) readConfig(os.Args[2])
default: default:
module, command := modules.GetCommand(os.Args[1], modules.CommandLine) printUsage()
if module != nil { return
modules.ExecuteInit(module, modules.CallbackInfo{
CallbackType: modules.CommandLine,
Command: command,
Arguments: os.Args[2:],
})
if modules.ExistsBlockingModule() {
modules.ExecuteComplete()
waitForSignal()
modules.ShutdownAll()
}
} else {
printUsage()
}
} }
} }
func printUsage() { func printUsage() {
fmt.Println("More options and additional documentation in the example config file")
fmt.Println("Usage:") fmt.Println("Usage:")
fmt.Println("pndpd config <path to file>") fmt.Println("pndpd config <path to file>")
for i := range modules.ModuleList { fmt.Println("pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>")
for d := range (*modules.ModuleList[i]).Commands { fmt.Println("pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>")
if (*modules.ModuleList[i]).Commands[d].CommandLineEnabled { fmt.Println("More options and additional documentation in the example config file")
fmt.Println((*modules.ModuleList[i]).Commands[d].Description)
}
}
}
}
// waitForSignal Waits (blocking) for the program to be interrupted by the OS
func waitForSignal() {
var sigCh = make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
close(sigCh)
} }

View File

@ -1,52 +0,0 @@
//go:build mod_example
// +build mod_example
package example
import (
"fmt"
"pndpd/modules"
)
// This is an example module
func init() {
commands := []modules.Command{{
CommandText: "pndpd command1",
Description: "This is the usage description for command1",
BlockTerminate: true,
CommandLineEnabled: true,
ConfigEnabled: true,
}, {
CommandText: "pndpd command2",
Description: "This is the usage description for command2",
BlockTerminate: false,
CommandLineEnabled: false,
ConfigEnabled: true,
},
}
modules.RegisterModule("Example", commands, initCallback, completeCallback, shutdownCallback)
}
func initCallback(callback modules.CallbackInfo) {
if callback.CallbackType == modules.CommandLine {
// The command registered by the module has been run in the commandline
// "arguments" contains the os.Args[] passed to the program after the command registered by this module
fmt.Println("Command: ", callback.Command.CommandText)
fmt.Println(callback.Arguments)
} else {
// The command registered by the module was found in the config file
// "arguments" contains the lines between the curly braces
fmt.Println("Command: ", callback.Command.CommandText)
fmt.Println(callback.Arguments)
}
fmt.Println()
}
func completeCallback() {
//Called after the program has passed all options by calls to initCallback()
}
func shutdownCallback() {
fmt.Println("Terminate all work")
}

View File

@ -1 +0,0 @@
package example

View File

@ -1,93 +0,0 @@
package modules
var ModuleList []*Module
type Module struct {
Name string
Commands []Command
InitCallback func(CallbackInfo)
CompleteCallback func()
ShutdownCallback func()
}
type Command struct {
CommandText string
Description string
BlockTerminate bool
CommandLineEnabled bool
ConfigEnabled bool
}
type CallbackType int
const (
CommandLine CallbackType = 0
Config CallbackType = 1
)
type CallbackInfo struct {
CallbackType CallbackType
Command Command
Arguments []string
}
func RegisterModule(name string, commands []Command, initCallback func(CallbackInfo), CompleteCallback func(), shutdownCallback func()) {
ModuleList = append(ModuleList, &Module{
Name: name,
Commands: commands,
InitCallback: initCallback,
CompleteCallback: CompleteCallback,
ShutdownCallback: shutdownCallback,
})
}
func GetCommand(target string, scope CallbackType) (*Module, Command) {
for i := range ModuleList {
for _, command := range ModuleList[i].Commands {
if command.CommandText == target {
if scope == CommandLine && command.CommandLineEnabled {
return ModuleList[i], command
}
if scope == Config && command.ConfigEnabled {
return ModuleList[i], command
}
return nil, Command{}
}
}
}
return nil, Command{}
}
var runningModules []*Module
func ExecuteInit(module *Module, info CallbackInfo) {
if info.Command.BlockTerminate {
found := false
for _, n := range runningModules {
if n == module {
found = true
break
}
}
if !found {
runningModules = append(runningModules, module)
}
}
module.InitCallback(info)
}
func ExecuteComplete() {
for i := range runningModules {
(*runningModules[i]).CompleteCallback()
}
}
func ShutdownAll() {
for i := range runningModules {
(*runningModules[i]).ShutdownCallback()
}
}
func ExistsBlockingModule() bool {
return len(runningModules) != 0
}

View File

@ -1 +0,0 @@
package userInterface

View File

@ -1,195 +0,0 @@
//go:build !noUserInterface
// +build !noUserInterface
package userInterface
import (
"fmt"
"os"
"pndpd/modules"
"pndpd/pndp"
"strings"
)
func init() {
commands := []modules.Command{{
CommandText: "proxy",
Description: "pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>",
BlockTerminate: true,
ConfigEnabled: true,
CommandLineEnabled: true,
}, {
CommandText: "responder",
Description: "pndpd responder <interface> <optional whitelist of CIDRs separated by a semicolon>",
BlockTerminate: true,
ConfigEnabled: true,
CommandLineEnabled: true,
}, {
CommandText: "modules",
Description: "pndpd modules available - list available modules",
BlockTerminate: false,
ConfigEnabled: false,
CommandLineEnabled: true,
}}
modules.RegisterModule("Core", commands, initCallback, completeCallback, shutdownCallback)
}
type configResponder struct {
Iface string
Filter string
autosense string
instance *pndp.ResponderObj
}
type configProxy struct {
Iface1 string
Iface2 string
Filter string
autosense string
instance *pndp.ProxyObj
}
var allResponders []*configResponder
var allProxies []*configProxy
func initCallback(callback modules.CallbackInfo) {
if callback.CallbackType == modules.CommandLine {
switch callback.Command.CommandText {
case "proxy":
if len(callback.Arguments) == 3 {
allProxies = append(allProxies, &configProxy{
Iface1: callback.Arguments[0],
Iface2: callback.Arguments[1],
Filter: callback.Arguments[2],
autosense: "",
instance: nil,
})
} else {
allProxies = append(allProxies, &configProxy{
Iface1: callback.Arguments[0],
Iface2: callback.Arguments[1],
Filter: "",
autosense: "",
instance: nil,
})
}
case "responder":
if len(callback.Arguments) == 2 {
allResponders = append(allResponders, &configResponder{
Iface: callback.Arguments[0],
Filter: callback.Arguments[1],
autosense: "",
instance: nil,
})
} else {
allResponders = append(allResponders, &configResponder{
Iface: callback.Arguments[0],
Filter: "",
autosense: "",
instance: nil,
})
}
case "modules":
if modules.ModuleList != nil {
fmt.Print("Available Modules: ")
for i := range modules.ModuleList {
fmt.Print((*modules.ModuleList[i]).Name + " ")
}
fmt.Println()
}
}
} else {
switch callback.Command.CommandText {
case "proxy":
obj := configProxy{}
filter := ""
for _, n := range callback.Arguments {
if strings.HasPrefix(n, "iface1") {
obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(n, "iface1"))
}
if strings.HasPrefix(n, "iface2") {
obj.Iface2 = strings.TrimSpace(strings.TrimPrefix(n, "iface2"))
}
if strings.HasPrefix(n, "filter") {
filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";"
if strings.Contains(n, ";") {
showError("config: the use of semicolons is not allowed in the filter arguments")
}
}
if strings.HasPrefix(n, "autosense") {
obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense"))
}
if strings.Contains(n, "//") {
showError("config: comments are not allowed after arguments")
}
}
obj.Filter = strings.TrimSuffix(filter, ";")
if obj.autosense != "" && obj.Filter != "" {
showError("config: cannot have both a filter and autosense enabled on a proxy object")
}
if obj.Iface2 == "" || obj.Iface1 == "" {
showError("config: two interfaces need to be specified in the config file for a proxy object. (iface1 and iface2 parameters)")
}
allProxies = append(allProxies, &obj)
case "responder":
obj := configResponder{}
filter := ""
for _, n := range callback.Arguments {
if strings.HasPrefix(n, "iface") {
obj.Iface = strings.TrimSpace(strings.TrimPrefix(n, "iface"))
}
if strings.HasPrefix(n, "filter") {
filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";"
if strings.Contains(n, ";") {
showError("config: the use of semicolons is not allowed in the filter arguments")
}
}
if strings.HasPrefix(n, "autosense") {
obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense"))
}
if obj.autosense != "" && obj.Filter != "" {
showError("config: cannot have both a filter and autosense enabled on a responder object")
}
if obj.Iface == "" {
showError("config: interface not specified in the responder object. (iface parameter)")
}
if strings.Contains(n, "//") {
showError("config: comments are not allowed after arguments")
}
}
obj.Filter = strings.TrimSuffix(filter, ";")
allResponders = append(allResponders, &obj)
}
}
}
func completeCallback() {
for _, n := range allProxies {
o := pndp.NewProxy(n.Iface1, n.Iface2, pndp.ParseFilter(n.Filter), n.autosense)
n.instance = o
o.Start()
}
for _, n := range allResponders {
o := pndp.NewResponder(n.Iface, pndp.ParseFilter(n.Filter), n.autosense)
n.instance = o
o.Start()
}
}
func shutdownCallback() {
for _, n := range allProxies {
n.instance.Stop()
}
for _, n := range allResponders {
n.instance.Stop()
}
}
func showError(error string) {
fmt.Println(error)
fmt.Println("Exiting due to error")
os.Exit(1)
}

View File

@ -8,12 +8,13 @@ const (
) )
type ndpRequest struct { type ndpRequest struct {
requestType ndpType requestType ndpType
srcIP []byte srcIP []byte
answeringForIP []byte answeringForIP []byte
dstIP []byte dstIP []byte
sourceIface string mac []byte
payload []byte receivedIfaceMac []byte
sourceIface string
} }
type ndpQuestion struct { type ndpQuestion struct {

View File

@ -1,96 +0,0 @@
package pndp
import (
"golang.org/x/net/bpf"
"golang.org/x/sys/unix"
"net"
"syscall"
"unsafe"
)
// bpfFilter represents a classic BPF filter program that can be applied to a socket
type bpfFilter []bpf.Instruction
// ApplyTo applies the current filter onto the provided file descriptor
func (filter bpfFilter) 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
}
type iflags struct {
name [syscall.IFNAMSIZ]byte
flags uint16
}
func setPromisc(fd int, iface string, enable bool, withInterfaceFlags bool) {
//TODO re-test ALLMULTI
// -------------------------- Interface flags --------------------------
if withInterfaceFlags {
tFD, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0)
if err != nil {
panic(err)
}
var ifl iflags
copy(ifl.name[:], []byte(iface))
_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tFD), syscall.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifl)))
if ep != 0 {
panic(ep)
}
if enable {
ifl.flags |= uint16(syscall.IFF_PROMISC)
} else {
ifl.flags &^= uint16(syscall.IFF_PROMISC)
}
_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(tFD), syscall.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifl)))
if ep != 0 {
panic(ep)
}
_ = syscall.Close(tFD)
}
// ---------------------------------------------------------------------
// -------------------------- Socket Options ---------------------------
iFace, err := net.InterfaceByName(iface)
if err != nil {
panic(err.Error())
}
mReq := unix.PacketMreq{
Ifindex: int32(iFace.Index),
Type: unix.PACKET_MR_PROMISC,
}
var opt int
if enable {
opt = unix.PACKET_ADD_MEMBERSHIP
} else {
opt = unix.PACKET_DROP_MEMBERSHIP
}
err = unix.SetsockoptPacketMreq(fd, unix.SOL_PACKET, opt, &mReq)
if err != nil {
panic(err)
}
// ---------------------------------------------------------------------
}

View File

@ -1,146 +0,0 @@
package pndp
import (
"bytes"
"fmt"
"golang.org/x/net/bpf"
"net"
"sync"
"syscall"
)
// 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 *ndpRequest, requestType ndpType, stopWG *sync.WaitGroup, stopChan chan struct{}) {
stopWG.Add(1)
defer stopWG.Done()
niface, err := net.InterfaceByName(iface)
if err != nil {
panic(err.Error())
}
tiface := &syscall.SockaddrLinklayer{
Protocol: htons16(syscall.ETH_P_IPV6),
Ifindex: niface.Index,
}
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, htons(syscall.ETH_P_IPV6))
if err != nil {
fmt.Println(err.Error())
}
go func() {
<-stopChan
setPromisc(fd, iface, false, false)
_ = syscall.Close(fd)
stopWG.Done() // syscall.read does not release when the file descriptor is closed
}()
if GlobalDebug {
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 {
panic(err.Error())
}
setPromisc(fd, iface, true, false)
var protocolNo uint32
if requestType == ndp_SOL {
//Neighbor Solicitation
protocolNo = 0x87
} else {
//Neighbor Advertisement
protocolNo = 0x88
}
var f bpfFilter
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: 5},
// 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: 3},
// Load "Type" field from ICMPv6 header.
bpf.LoadAbsolute{Off: 54, Size: 1},
// Jump to the drop packet instruction if Type is not Neighbor Solicitation / Advertisement.
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1},
// Verdict is: send up to 86 bytes of the packet to userspace.
bpf.RetConstant{Val: 86},
// Verdict is: "ignore packet."
bpf.RetConstant{Val: 0},
}
err = f.ApplyTo(fd)
if err != nil {
panic(err.Error())
}
for {
buf := make([]byte, 86)
numRead, err := syscall.Read(fd, buf)
if err != nil {
panic(err)
}
if numRead < 78 {
if GlobalDebug {
fmt.Println("Dropping packet since it does not meet the minimum length requirement")
fmt.Printf("% X\n", buf[:numRead])
}
continue
}
if GlobalDebug {
fmt.Println("Got packet on", iface, "of type", requestType)
fmt.Printf("% X\n", buf[:numRead])
fmt.Println("Source mac on ethernet layer:")
fmt.Printf("% X\n", buf[6:12])
fmt.Println("Source IP:")
fmt.Printf("% X\n", buf[22:38])
fmt.Println("Destination IP:")
fmt.Printf("% X\n", buf[38:54])
fmt.Println("Requested IP:")
fmt.Printf("% X\n", buf[62:78])
if requestType == ndp_ADV {
fmt.Println("NDP Flags")
fmt.Printf("% X\n", buf[58])
}
fmt.Println()
}
if bytes.Equal(buf[6:12], niface.HardwareAddr) {
if GlobalDebug {
fmt.Println("Dropping packet from ourselves")
}
continue
}
if requestType == ndp_ADV {
if buf[58] == 0x0 {
if GlobalDebug {
fmt.Println("Dropping Advertisement packet without any NDP flags set")
}
continue
}
}
responder <- &ndpRequest{
requestType: requestType,
srcIP: buf[22:38],
dstIP: buf[38:54],
answeringForIP: buf[62:78],
payload: buf[54:],
sourceIface: iface,
}
}
}

View File

@ -1,15 +1,14 @@
package pndp package pndp
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt"
"net" "net"
"strings" "strings"
) )
var emptyIpv6 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} var emptyIpv6 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
var allNodesIpv6 = []byte{0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}
type payload interface { type payload interface {
constructPacket() ([]byte, int) constructPacket() ([]byte, int)
@ -116,11 +115,6 @@ func (p *ndpPayload) constructPacket() ([]byte, int) {
} }
func calculateChecksum(h *ipv6Header, payload []byte) uint16 { func calculateChecksum(h *ipv6Header, payload []byte) uint16 {
if payload == nil {
return 0x0000
} else if len(payload) == 0 {
return 0x0000
}
sumPseudoHeader := checksumAddition(h.srcIP) + checksumAddition(h.dstIP) + checksumAddition([]byte{0x00, h.protocol}) + checksumAddition(h.payloadLen) sumPseudoHeader := checksumAddition(h.srcIP) + checksumAddition(h.dstIP) + checksumAddition([]byte{0x00, h.protocol}) + checksumAddition(h.payloadLen)
sumPayload := checksumAddition(payload) sumPayload := checksumAddition(payload)
sumTotal := sumPayload + sumPseudoHeader sumTotal := sumPayload + sumPseudoHeader
@ -134,8 +128,8 @@ func checksumAddition(b []byte) uint32 {
var sum uint32 = 0 var sum uint32 = 0
for i := 0; i < len(b); i++ { for i := 0; i < len(b); i++ {
if i%2 == 0 { if i%2 == 0 {
if len(b)-1 == i { if len(b) == i {
sum += uint32(uint16(b[i])<<8 | uint16(0x00)) sum += uint32(uint16(b[i])<<8 | uint16(0x0))
} else { } else {
sum += uint32(uint16(b[i])<<8 | uint16(b[i+1])) sum += uint32(uint16(b[i])<<8 | uint16(b[i+1]))
} }
@ -144,29 +138,6 @@ func checksumAddition(b []byte) uint32 {
return sum return sum
} }
func checkPacketChecksum(v6 *ipv6Header, payload []byte) bool {
packetsum := make([]byte, 2)
copy(packetsum, payload[2:4])
bPayloadLen := make([]byte, 2)
binary.BigEndian.PutUint16(bPayloadLen, uint16(len(payload)))
v6.payloadLen = bPayloadLen
payload[2] = 0x0
payload[3] = 0x0
bChecksum := make([]byte, 2)
binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload))
if bytes.Equal(packetsum, bChecksum) {
return true
} else {
if GlobalDebug {
fmt.Println("Received packet checksum validation failed")
}
return false
}
}
func isIpv6(ip string) bool { func isIpv6(ip string) bool {
rip := net.ParseIP(ip) rip := net.ParseIP(ip)
return rip != nil && strings.Contains(ip, ":") return rip != nil && strings.Contains(ip, ":")

View File

@ -35,9 +35,6 @@ type ProxyObj struct {
// With the optional autosenseInterface argument, the whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently. // With the optional autosenseInterface argument, the whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently.
// Start() must be called on the object to actually start responding // Start() must be called on the object to actually start responding
func NewResponder(iface string, filter []*net.IPNet, autosenseInterface string) *ResponderObj { func NewResponder(iface string, filter []*net.IPNet, autosenseInterface string) *ResponderObj {
if filter == nil && autosenseInterface == "" {
fmt.Println("WARNING: You should use a whitelist for the responder unless you really know what you are doing")
}
var s sync.WaitGroup var s sync.WaitGroup
return &ResponderObj{ return &ResponderObj{
stopChan: make(chan struct{}), stopChan: make(chan struct{}),
@ -59,13 +56,12 @@ func (obj *ResponderObj) start() {
}() }()
go respond(obj.iface, requests, ndp_ADV, nil, obj.filter, obj.autosense, obj.stopWG, obj.stopChan) go respond(obj.iface, requests, ndp_ADV, nil, obj.filter, obj.autosense, obj.stopWG, obj.stopChan)
go listen(obj.iface, requests, ndp_SOL, obj.stopWG, obj.stopChan) go listen(obj.iface, requests, ndp_SOL, obj.stopWG, obj.stopChan)
fmt.Printf("Started responder instance on interface %s", obj.iface) fmt.Println("Started responder instance on interface ", obj.iface)
fmt.Println()
<-obj.stopChan <-obj.stopChan
} }
//Stop a running Responder instance //Stop a running Responder instance
// Returns false on error // Returns false on success
func (obj *ResponderObj) Stop() bool { func (obj *ResponderObj) Stop() bool {
close(obj.stopChan) close(obj.stopChan)
fmt.Println("Shutting down responder instance..") fmt.Println("Shutting down responder instance..")
@ -131,8 +127,7 @@ func (obj *ProxyObj) start() {
go listen(obj.iface2, req_iface2_adv_iface1, ndp_ADV, obj.stopWG, obj.stopChan) go listen(obj.iface2, req_iface2_adv_iface1, ndp_ADV, obj.stopWG, obj.stopChan)
go respond(obj.iface1, req_iface2_adv_iface1, ndp_ADV, out_iface2_sol_questions_iface1_adv, nil, "", obj.stopWG, obj.stopChan) go respond(obj.iface1, req_iface2_adv_iface1, ndp_ADV, out_iface2_sol_questions_iface1_adv, nil, "", obj.stopWG, obj.stopChan)
fmt.Printf("Started Proxy instance on interfaces %s and %s (if enabled, the whitelist is applied on %s)", obj.iface1, obj.iface2, obj.iface2) fmt.Println("Started Proxy instance for interfaces: ", obj.iface1, " and ", obj.iface2)
fmt.Println()
<-obj.stopChan <-obj.stopChan
} }
@ -150,7 +145,7 @@ func (obj *ProxyObj) Stop() bool {
} }
} }
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist // ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist for SimpleRespond
func ParseFilter(f string) []*net.IPNet { func ParseFilter(f string) []*net.IPNet {
if f == "" { if f == "" {
return nil return nil

199
pndp/rawsocket.go Normal file
View File

@ -0,0 +1,199 @@
package pndp
import (
"bytes"
"encoding/binary"
"fmt"
"golang.org/x/net/bpf"
"golang.org/x/sys/unix"
"net"
"sync"
"syscall"
"unsafe"
)
// bpfFilter represents a classic BPF filter program that can be applied to a socket
type bpfFilter []bpf.Instruction
// ApplyTo applies the current filter onto the provided file descriptor
func (filter bpfFilter) 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 *ndpRequest, requestType ndpType, stopWG *sync.WaitGroup, stopChan chan struct{}) {
stopWG.Add(1)
defer stopWG.Done()
niface, err := net.InterfaceByName(iface)
if err != nil {
panic(err.Error())
}
tiface := &syscall.SockaddrLinklayer{
Protocol: htons16(syscall.ETH_P_IPV6),
Ifindex: niface.Index,
}
fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, htons(syscall.ETH_P_IPV6))
if err != nil {
fmt.Println(err.Error())
}
go func() {
<-stopChan
syscall.Close(fd)
stopWG.Done() // syscall.read does not release when the file descriptor is closed
}()
if GlobalDebug {
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 {
panic(err.Error())
}
var protocolNo uint32
if requestType == ndp_SOL {
//Neighbor Solicitation
protocolNo = 0x87
} else {
//Neighbor Advertisement
protocolNo = 0x88
}
var f bpfFilter = []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 / Advertisement.
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1},
// Verdict is "send up to 4k of the packet to userspace."buf
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)
}
if numRead < 86 {
if GlobalDebug {
fmt.Println("Dropping packet since it does not meet the minimum length requirement")
fmt.Printf("% X\n", buf[:numRead])
}
continue
}
if GlobalDebug {
fmt.Println("Got packet on", iface, "of type", requestType)
fmt.Printf("% X\n", buf[:numRead])
fmt.Println("Source MAC ETHER")
fmt.Printf("% X\n", buf[:numRead][6:12])
fmt.Println("Source IP:")
fmt.Printf("% X\n", buf[:numRead][22:38])
fmt.Println("Destination IP:")
fmt.Printf("% X\n", buf[:numRead][38:54])
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()
}
if bytes.Equal(buf[:numRead][6:12], niface.HardwareAddr) {
if GlobalDebug {
fmt.Println("Dropping packet from ourselves")
}
continue
}
if !checkPacketChecksum(buf[:numRead][22:38], buf[:numRead][38:54], buf[:numRead][54:numRead]) {
if GlobalDebug {
fmt.Println("Dropping packet because of invalid checksum")
}
continue
}
responder <- &ndpRequest{
requestType: requestType,
srcIP: buf[:numRead][22:38],
dstIP: buf[:numRead][38:54],
answeringForIP: buf[:numRead][62:78],
mac: buf[:numRead][80:86],
receivedIfaceMac: niface.HardwareAddr,
sourceIface: iface,
}
}
}
func checkPacketChecksum(scrip, dstip, payload []byte) bool {
v6, err := newIpv6Header(scrip, dstip)
if err != nil {
return false
}
packetsum := make([]byte, 2)
copy(packetsum, payload[2:4])
bPayloadLen := make([]byte, 2)
binary.BigEndian.PutUint16(bPayloadLen, uint16(len(payload)))
v6.payloadLen = bPayloadLen
payload[2] = 0x0
payload[3] = 0x0
bChecksum := make([]byte, 2)
binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload))
if bytes.Equal(packetsum, bChecksum) {
if GlobalDebug {
fmt.Println("Verified received packet checksum")
}
return true
} else {
if GlobalDebug {
fmt.Println("Received packet checksum validation failed")
}
return false
}
}

View File

@ -9,84 +9,75 @@ import (
) )
func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQuestionChan chan *ndpQuestion, filter []*net.IPNet, autoSense string, stopWG *sync.WaitGroup, stopChan chan struct{}) { func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQuestionChan chan *ndpQuestion, filter []*net.IPNet, autoSense string, stopWG *sync.WaitGroup, stopChan chan struct{}) {
var ndpQuestionsList = make([]*ndpQuestion, 0, 100)
stopWG.Add(1) stopWG.Add(1)
defer stopWG.Done() defer stopWG.Done()
var ndpQuestionsList = make([]*ndpQuestion, 0, 40)
var _, linkLocalSpace, _ = net.ParseCIDR("fe80::/10")
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW) fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
if err != nil { if err != nil {
panic(err) panic(err)
} }
defer func(fd int) { defer syscall.Close(fd)
_ = syscall.Close(fd)
}(fd)
err = syscall.BindToDevice(fd, iface) err = syscall.BindToDevice(fd, iface)
if err != nil { if err != nil {
panic(err) panic(err)
} }
respondIface, err := net.InterfaceByName(iface) niface, err := net.InterfaceByName(iface)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
var result = selectSourceIP(respondIface) var result = emptyIpv6
ifaceaddrs, err := niface.Addrs()
for _, n := range ifaceaddrs {
tip, _, err := net.ParseCIDR(n.String())
if err != nil {
break
}
if isIpv6(tip.String()) {
if tip.IsGlobalUnicast() {
result = tip
_, tnet, _ := net.ParseCIDR("fc00::/7")
if !tnet.Contains(tip) {
break
}
}
}
}
for { for {
var req *ndpRequest var n *ndpRequest
if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) { if ndpQuestionChan == nil && respondType == ndp_ADV {
select { select {
case <-stopChan: case <-stopChan:
return return
case req = <-requests: case n = <-requests:
} }
} else { } else {
// This is if ndpQuestionChan != nil && respondType == ndp_ADV
select { select {
case <-stopChan: case <-stopChan:
return return
case q := <-ndpQuestionChan: case q := <-ndpQuestionChan:
ndpQuestionsList = append(ndpQuestionsList, q) ndpQuestionsList = append(ndpQuestionsList, q)
ndpQuestionsList = cleanupQuestionList(ndpQuestionsList)
continue continue
case req = <-requests: case n = <-requests:
} }
} }
if linkLocalSpace.Contains(req.answeringForIP) {
if GlobalDebug {
fmt.Println("Dropping packet asking for a link-local IP")
}
continue
}
v6Header, err := newIpv6Header(req.srcIP, req.dstIP)
if err != nil {
continue
}
if !checkPacketChecksum(v6Header, req.payload) {
continue
}
// Auto-sense
if autoSense != "" { if autoSense != "" {
//TODO Future work: Use another sub goroutine to monitor the interface instead of checking here
filter = make([]*net.IPNet, 0)
result = selectSourceIP(respondIface)
autoiface, err := net.InterfaceByName(autoSense) autoiface, err := net.InterfaceByName(autoSense)
if err != nil { if err != nil {
panic(err) panic(err)
} }
autoifaceaddrs, err := autoiface.Addrs() autoifaceaddrs, err := autoiface.Addrs()
for _, l := range autoifaceaddrs { for _, n := range autoifaceaddrs {
testIP, anet, err := net.ParseCIDR(l.String()) _, anet, err := net.ParseCIDR(n.String())
if err != nil { if err != nil {
break break
} }
if isIpv6(testIP.String()) { if isIpv6(anet.String()) {
filter = append(filter, anet) filter = append(filter, anet)
} }
} }
@ -95,9 +86,9 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
if filter != nil { if filter != nil {
ok := false ok := false
for _, i := range filter { for _, i := range filter {
if i.Contains(req.answeringForIP) { if i.Contains(n.answeringForIP) {
if GlobalDebug { if GlobalDebug {
fmt.Println("Responded for whitelisted IP", req.answeringForIP) fmt.Println("Responded for whitelisted IP", n.answeringForIP)
} }
ok = true ok = true
break break
@ -112,12 +103,12 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
fmt.Println("Getting ready to send packet of type", respondType, "out on interface", iface) fmt.Println("Getting ready to send packet of type", respondType, "out on interface", iface)
} }
if req.sourceIface == iface { if n.sourceIface == iface {
pkt(fd, result, req.srcIP, req.answeringForIP, respondIface.HardwareAddr, respondType) pkt(fd, result, n.srcIP, n.answeringForIP, niface.HardwareAddr, respondType)
} else { } else {
if respondType == ndp_ADV { if respondType == ndp_ADV {
success := false success := false
req.dstIP, success = getAddressFromQuestionListRetry(req.answeringForIP, ndpQuestionChan, ndpQuestionsList) n.dstIP, success = getAddressFromQuestionListRetry(n.answeringForIP, ndpQuestionChan, ndpQuestionsList)
if !success { if !success {
if GlobalDebug { if GlobalDebug {
fmt.Println("Nobody has asked for this IP") fmt.Println("Nobody has asked for this IP")
@ -126,11 +117,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
} }
} else { } else {
ndpQuestionChan <- &ndpQuestion{ ndpQuestionChan <- &ndpQuestion{
targetIP: req.answeringForIP, targetIP: n.answeringForIP,
askedBy: req.srcIP, askedBy: n.srcIP,
} }
} }
pkt(fd, result, req.dstIP, req.answeringForIP, respondIface.HardwareAddr, respondType) pkt(fd, result, n.dstIP, n.answeringForIP, niface.HardwareAddr, respondType)
} }
} }
} }
@ -171,24 +162,22 @@ func getAddressFromQuestionListRetry(targetIP []byte, ndpQuestionChan chan *ndpQ
if success { if success {
return result, true return result, true
} }
forloop:
gotBuffered := false for {
select { select {
case q := <-ndpQuestionChan: case q := <-ndpQuestionChan:
ndpQuestionsList = append(ndpQuestionsList, q) ndpQuestionsList = append(ndpQuestionsList, q)
gotBuffered = true default:
default: break forloop
}
} }
if gotBuffered { result, success = getAddressFromQuestionList(targetIP, ndpQuestionsList)
result, success = getAddressFromQuestionList(targetIP, ndpQuestionsList) return result, success
}
return nil, false
} }
func getAddressFromQuestionList(targetIP []byte, ndpQuestionsList []*ndpQuestion) ([]byte, bool) { func getAddressFromQuestionList(targetIP []byte, ndpQuestionsList []*ndpQuestion) ([]byte, bool) {
for i := range ndpQuestionsList { for i, _ := range ndpQuestionsList {
if bytes.Equal((*ndpQuestionsList[i]).targetIP, targetIP) { if bytes.Equal((*ndpQuestionsList[i]).targetIP, targetIP) {
result := (*ndpQuestionsList[i]).askedBy result := (*ndpQuestionsList[i]).askedBy
ndpQuestionsList = removeFromQuestionList(ndpQuestionsList, i) ndpQuestionsList = removeFromQuestionList(ndpQuestionsList, i)
@ -201,40 +190,3 @@ func removeFromQuestionList(s []*ndpQuestion, i int) []*ndpQuestion {
s[i] = s[len(s)-1] s[i] = s[len(s)-1]
return s[:len(s)-1] return s[:len(s)-1]
} }
func cleanupQuestionList(s []*ndpQuestion) []*ndpQuestion {
for len(s) >= 40 {
s = removeFromQuestionList(s, 0)
}
return s
}
func selectSourceIP(iface *net.Interface) []byte {
var _, ulaSpace, _ = net.ParseCIDR("fc00::/7")
var result = emptyIpv6
ifaceaddrs, err := iface.Addrs()
if err != nil {
return result
}
for _, n := range ifaceaddrs {
tip, _, err := net.ParseCIDR(n.String())
if err != nil {
break
}
var haveUla = false
if isIpv6(tip.String()) {
if tip.IsGlobalUnicast() {
haveUla = true
result = tip
if !ulaSpace.Contains(tip) {
break
}
} else if tip.IsLinkLocalUnicast() && !haveUla {
result = tip
}
}
}
return result
}

View File

@ -5,7 +5,6 @@
debug off debug off
// Responder example // Responder example
// Create an NDP responder that answers on interface "eth0"
responder { responder {
iface eth0 iface eth0
filter fd01::/64 filter fd01::/64
@ -13,7 +12,6 @@ responder {
} }
// Proxy example // Proxy example
// Create an NDP proxy for proxying NDP between iface1 ("eth0") and iface2 ("eth1")
// The whitelist is applied on iface2 // The whitelist is applied on iface2
proxy { proxy {
iface1 eth0 iface1 eth0

View File

@ -1,18 +0,0 @@
[Unit]
Description=Proxy NDP Daemon
Wants=network-online.target
After=network.target network-online.target
[Service]
Type=simple
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/pndpd config /etc/pndpd/pndpd.conf
DynamicUser=yes
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
ProtectHome=yes
[Install]
WantedBy=multi-user.target