Compare commits

...

35 Commits

Author SHA1 Message Date
e5b04565ec
add go vet
All checks were successful
continuous-integration/drone/push Build is passing
2022-01-27 15:16:02 +00:00
58ac05adc9
fix drone 2022-01-27 15:16:02 +00:00
b3d2305f9d
burble.dn42 customizations 2022-01-27 15:16:02 +00:00
Kioubit
dff2f9ab10 Update README.md 2022-01-09 08:49:20 -05:00
Kioubit
49c6c333e9 Better user input validation, always clear filter slice 2022-01-09 08:32:25 -05:00
Kioubit
a7eb52c0c5 No longer set SIOCGIFFLAGS, update README.md 2022-01-07 07:20:32 -05:00
Kioubit
ede44e8426 Set PACKET_ADD_MEMBERSHIP 2022-01-04 17:40:40 -05:00
Kioubit
d8c439aad9 Remove unnecessary makefile option 2022-01-04 15:49:31 -05:00
Kioubit
48486cc205 set IFF_PROMISC 2022-01-04 15:48:28 -05:00
Kioubit
1f294666f3 Refresh selected source IP when autosense 2021-12-28 15:42:13 -05:00
Kioubit
95bf95784e Add version information to binaries 2021-12-28 12:52:06 -05:00
Kioubit
1da76a4547 Don't check for unnecessary flags, fix potential bottleneck 2021-12-28 12:48:52 -05:00
Kioubit
b7c5e6afba Add build for multiple architectures, streamline process 2021-12-28 07:43:59 -05:00
Kioubit
1ec9477d69 Fix autosense not working 2021-12-28 06:44:49 -05:00
Kioubit
2e62c0b5d5 Build tags for modules 2021-12-28 06:19:19 -05:00
Kioubit
ff27eb5141 Documentation update 2021-12-27 12:09:59 -05:00
Kioubit
4ddaa98351
Merge pull request #1 from jlu5/patch-1
Simplify wget instructions; use FHS compliant paths
2021-12-27 12:02:59 -05:00
Kioubit
6ebee06861 Make the user interface a module, Rework modules 2021-12-27 11:25:52 -05:00
Kioubit
0f580cbbbd Small code optimizations 2021-12-27 08:33:10 -05:00
Kioubit
5a901eada3 Fix unbuffered os.Signal channel error 2021-12-27 06:37:08 -05:00
James Lu
504daad9e1
Simplify wget instructions; use FHS compliant paths
Paths under /usr/bin and /usr/lib are generally managed by the system package manager; use /usr/local/bin and /etc instead
2021-12-27 01:52:30 -08:00
Kioubit
11110fb66b Update Documentation 2021-12-26 08:02:49 -05:00
Kioubit
e3d463e815 Bump Version 2021-12-26 07:15:10 -05:00
Kioubit
45b9ac5e86 Set Interface Allmulti flag 2021-12-26 07:13:48 -05:00
Kioubit
de4d228950 Bump Version 2021-12-26 11:53:27 +01:00
Kioubit
d196469c01 isIpv6 fix 2021-12-26 11:50:35 +01:00
Kioubit
f8f0d0d055 Documentation 2021-12-25 22:25:58 +01:00
Kioubit
d6421a007e Better module support 2021-12-25 13:03:11 -05:00
Kioubit
6948f5a30d Support modules 2021-12-25 09:28:59 -05:00
Kioubit
3491a47811 Add installation instructions 2021-12-25 08:29:57 -05:00
Kioubit
d3a5059aa2 More Fixes and optimization 2021-12-25 08:21:07 -05:00
Kioubit
ce659c31d8 Fixes 2021-12-24 18:06:32 -05:00
Kioubit
bef2b5d3ef Fix BPF Filter 2021-12-24 18:04:09 -05:00
Kioubit
8562c2f533 Bump Version 2021-12-24 17:14:09 -05:00
Kioubit
7d831f2f0b Fix checksum 2021-12-24 17:11:11 -05:00
20 changed files with 944 additions and 403 deletions

55
.drone.yml Normal file
View File

@ -0,0 +1,55 @@
---
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,2 +1,7 @@
*.iml
.idea
bin/
*~
pndpd

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
# 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,21 +1,51 @@
# PNDPD - NDP Responder + Proxy
## Features
- Efficiently process incoming packets using bpf (which runs in the kernel)
- Respond to all NDP solicitations on an interface
- Respond to NDP solicitations for whitelisted addresses on an interface
- Proxy NDP between interfaces with an optional whitelist
- Optionally determine whitelist automatically based on the IPs assigned to the interfaces
- Permissions required: root or CAP_NET_RAW
## Usage
[![Build Status](https://ci.burble.dn42/api/badges/mirrors/Pndpd/status.svg?ref=refs/heads/burble.dn42)](https://ci.burble.dn42/mirrors/Pndpd)
## Features
- **Efficiently** process incoming packets using bpf (which runs in the kernel)
- Respond to all NDP solicitations on an interface
- **Respond** to NDP solicitations for whitelisted addresses on an interface
- **Proxy** NDP between interfaces with an optional whitelist
- Optionally determine whitelist **automatically** based on the IPs assigned to the interfaces
- Permissions required: root or CAP_NET_RAW
- Easily expandable with modules
## Installing & Updating
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 respond <interface> <optional whitelist of CIDRs separated by a semicolon>
pndpd responder <interface> <optional whitelist of CIDRs separated by a semicolon>
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:
````
package main
@ -31,4 +61,6 @@ proxyInstance := pndp.NewProxy(iface1 string, iface2 string, filter []*net.IPNet
proxyInstance.Start()
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.

129
config.go
View File

@ -3,42 +3,27 @@ package main
import (
"bufio"
"fmt"
"log"
"os"
"pndpd/modules"
"pndpd/pndp"
"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) {
file, err := os.Open(dest)
if err != nil {
log.Fatal(err)
fmt.Println("Error:", err.Error())
os.Exit(1)
}
defer file.Close()
defer func(file *os.File) {
_ = file.Close()
}(file)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "//") {
if strings.HasPrefix(line, "//") || strings.TrimSpace(line) == "" {
continue
}
if strings.HasPrefix(line, "debug") {
@ -48,89 +33,41 @@ func readConfig(dest string) {
}
continue
}
if strings.HasPrefix(line, "responder") && strings.Contains(line, "{") {
obj := configResponder{}
filter := ""
if strings.HasSuffix(line, "{") {
option := strings.TrimSuffix(strings.TrimSpace(line), "{")
option = strings.TrimSpace(option)
module, command := modules.GetCommand(option, modules.Config)
var lines = make([]string, 0)
if module != nil {
for {
scanner.Scan()
line = strings.TrimSpace(scanner.Text())
if strings.HasPrefix(line, "iface") {
obj.Iface = strings.TrimSpace(strings.TrimPrefix(line, "iface"))
}
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, ";")
if !scanner.Scan() {
break
}
line := strings.TrimSpace(scanner.Text())
if strings.Contains(line, "}") {
break
}
lines = append(lines, line)
}
modules.ExecuteInit(module, modules.CallbackInfo{
CallbackType: modules.Config,
Command: command,
Arguments: lines,
})
}
}
allResponders = append(allResponders, &obj)
}
if strings.HasPrefix(line, "proxy") && strings.Contains(line, "{") {
obj := configProxy{}
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 modules.ExistsBlockingModule() {
modules.ExecuteComplete()
waitForSignal()
modules.ShutdownAll()
}
if err := scanner.Err(); err != nil {
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()
panic(err)
}
}

73
main.go
View File

@ -4,60 +4,65 @@ import (
"fmt"
"os"
"os/signal"
"pndpd/pndp"
"pndpd/modules"
"syscall"
)
import (
// Modules
_ "pndpd/modules/example"
_ "pndpd/modules/userInterface"
)
// 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)
}
var Version = "Development"
func main() {
fmt.Println("PNDPD Version 0.7 - Kioubit 2021")
fmt.Println("PNDPD Version", Version, "- Kioubit 2022")
if len(os.Args) <= 2 {
printUsage()
return
}
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":
readConfig(os.Args[2])
default:
module, command := modules.GetCommand(os.Args[1], modules.CommandLine)
if module != nil {
modules.ExecuteInit(module, modules.CallbackInfo{
CallbackType: modules.CommandLine,
Command: command,
Arguments: os.Args[2:],
})
if modules.ExistsBlockingModule() {
modules.ExecuteComplete()
waitForSignal()
modules.ShutdownAll()
}
} else {
printUsage()
return
}
}
}
func printUsage() {
fmt.Println("More options and additional documentation in the example config file")
fmt.Println("Usage:")
fmt.Println("pndpd config <path to file>")
fmt.Println("pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>")
fmt.Println("pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>")
fmt.Println("More options and additional documentation in the example config file")
for i := range modules.ModuleList {
for d := range (*modules.ModuleList[i]).Commands {
if (*modules.ModuleList[i]).Commands[d].CommandLineEnabled {
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

@ -0,0 +1,52 @@
//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

@ -0,0 +1 @@
package example

93
modules/modules.go Normal file
View File

@ -0,0 +1,93 @@
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

@ -0,0 +1 @@
package userInterface

View File

@ -0,0 +1,195 @@
//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

@ -12,9 +12,8 @@ type ndpRequest struct {
srcIP []byte
answeringForIP []byte
dstIP []byte
mac []byte
receivedIfaceMac []byte
sourceIface string
payload []byte
}
type ndpQuestion struct {

View File

@ -35,6 +35,9 @@ 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.
// Start() must be called on the object to actually start responding
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
return &ResponderObj{
stopChan: make(chan struct{}),
@ -56,12 +59,13 @@ func (obj *ResponderObj) start() {
}()
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)
fmt.Println("Started responder instance on interface ", obj.iface)
fmt.Printf("Started responder instance on interface %s", obj.iface)
fmt.Println()
<-obj.stopChan
}
//Stop a running Responder instance
// Returns false on success
// Returns false on error
func (obj *ResponderObj) Stop() bool {
close(obj.stopChan)
fmt.Println("Shutting down responder instance..")
@ -127,7 +131,8 @@ func (obj *ProxyObj) start() {
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)
fmt.Println("Started Proxy instance for interfaces: ", obj.iface1, " and ", obj.iface2)
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()
<-obj.stopChan
}
@ -145,7 +150,7 @@ func (obj *ProxyObj) Stop() bool {
}
}
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist for SimpleRespond
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist
func ParseFilter(f string) []*net.IPNet {
if f == "" {
return nil

96
pndp/interface.go Normal file
View File

@ -0,0 +1,96 @@
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)
}
// ---------------------------------------------------------------------
}

146
pndp/listener.go Normal file
View File

@ -0,0 +1,146 @@
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,14 +1,15 @@
package pndp
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"net"
"strings"
)
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 {
constructPacket() ([]byte, int)
@ -115,6 +116,11 @@ func (p *ndpPayload) constructPacket() ([]byte, int) {
}
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)
sumPayload := checksumAddition(payload)
sumTotal := sumPayload + sumPseudoHeader
@ -128,8 +134,8 @@ func checksumAddition(b []byte) uint32 {
var sum uint32 = 0
for i := 0; i < len(b); i++ {
if i%2 == 0 {
if len(b) == i {
sum += uint32(uint16(b[i])<<8 | uint16(0x0))
if len(b)-1 == i {
sum += uint32(uint16(b[i])<<8 | uint16(0x00))
} else {
sum += uint32(uint16(b[i])<<8 | uint16(b[i+1]))
}
@ -138,6 +144,29 @@ func checksumAddition(b []byte) uint32 {
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 {
rip := net.ParseIP(ip)
return rip != nil && strings.Contains(ip, ":")

View File

@ -1,199 +0,0 @@
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,75 +9,84 @@ import (
)
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)
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)
if err != nil {
panic(err)
}
defer syscall.Close(fd)
defer func(fd int) {
_ = syscall.Close(fd)
}(fd)
err = syscall.BindToDevice(fd, iface)
if err != nil {
panic(err)
}
niface, err := net.InterfaceByName(iface)
respondIface, err := net.InterfaceByName(iface)
if err != nil {
panic(err.Error())
}
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
}
}
}
}
var result = selectSourceIP(respondIface)
for {
var n *ndpRequest
if ndpQuestionChan == nil && respondType == ndp_ADV {
var req *ndpRequest
if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) {
select {
case <-stopChan:
return
case n = <-requests:
case req = <-requests:
}
} else {
// This is if ndpQuestionChan != nil && respondType == ndp_ADV
select {
case <-stopChan:
return
case q := <-ndpQuestionChan:
ndpQuestionsList = append(ndpQuestionsList, q)
ndpQuestionsList = cleanupQuestionList(ndpQuestionsList)
continue
case n = <-requests:
case req = <-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 != "" {
//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)
if err != nil {
panic(err)
}
autoifaceaddrs, err := autoiface.Addrs()
for _, n := range autoifaceaddrs {
_, anet, err := net.ParseCIDR(n.String())
for _, l := range autoifaceaddrs {
testIP, anet, err := net.ParseCIDR(l.String())
if err != nil {
break
}
if isIpv6(anet.String()) {
if isIpv6(testIP.String()) {
filter = append(filter, anet)
}
}
@ -86,9 +95,9 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
if filter != nil {
ok := false
for _, i := range filter {
if i.Contains(n.answeringForIP) {
if i.Contains(req.answeringForIP) {
if GlobalDebug {
fmt.Println("Responded for whitelisted IP", n.answeringForIP)
fmt.Println("Responded for whitelisted IP", req.answeringForIP)
}
ok = true
break
@ -103,12 +112,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)
}
if n.sourceIface == iface {
pkt(fd, result, n.srcIP, n.answeringForIP, niface.HardwareAddr, respondType)
if req.sourceIface == iface {
pkt(fd, result, req.srcIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
} else {
if respondType == ndp_ADV {
success := false
n.dstIP, success = getAddressFromQuestionListRetry(n.answeringForIP, ndpQuestionChan, ndpQuestionsList)
req.dstIP, success = getAddressFromQuestionListRetry(req.answeringForIP, ndpQuestionChan, ndpQuestionsList)
if !success {
if GlobalDebug {
fmt.Println("Nobody has asked for this IP")
@ -117,11 +126,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
}
} else {
ndpQuestionChan <- &ndpQuestion{
targetIP: n.answeringForIP,
askedBy: n.srcIP,
targetIP: req.answeringForIP,
askedBy: req.srcIP,
}
}
pkt(fd, result, n.dstIP, n.answeringForIP, niface.HardwareAddr, respondType)
pkt(fd, result, req.dstIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
}
}
}
@ -162,22 +171,24 @@ func getAddressFromQuestionListRetry(targetIP []byte, ndpQuestionChan chan *ndpQ
if success {
return result, true
}
forloop:
for {
gotBuffered := false
select {
case q := <-ndpQuestionChan:
ndpQuestionsList = append(ndpQuestionsList, q)
gotBuffered = true
default:
break forloop
}
}
if gotBuffered {
result, success = getAddressFromQuestionList(targetIP, ndpQuestionsList)
return result, success
}
return nil, false
}
func getAddressFromQuestionList(targetIP []byte, ndpQuestionsList []*ndpQuestion) ([]byte, bool) {
for i, _ := range ndpQuestionsList {
for i := range ndpQuestionsList {
if bytes.Equal((*ndpQuestionsList[i]).targetIP, targetIP) {
result := (*ndpQuestionsList[i]).askedBy
ndpQuestionsList = removeFromQuestionList(ndpQuestionsList, i)
@ -190,3 +201,40 @@ func removeFromQuestionList(s []*ndpQuestion, i int) []*ndpQuestion {
s[i] = 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,6 +5,7 @@
debug off
// Responder example
// Create an NDP responder that answers on interface "eth0"
responder {
iface eth0
filter fd01::/64
@ -12,6 +13,7 @@ responder {
}
// Proxy example
// Create an NDP proxy for proxying NDP between iface1 ("eth0") and iface2 ("eth1")
// The whitelist is applied on iface2
proxy {
iface1 eth0

18
pndpd.service Normal file
View File

@ -0,0 +1,18 @@
[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