Compare commits

...

28 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
19 changed files with 702 additions and 339 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 *.iml
.idea .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,38 +1,50 @@
# 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 ## Installing & Updating
1) Download the latest release from the releases page and move the binary to the ``/urs/bin/`` 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) For systemd users: Install the service 2) Allow executing the file by running ``chmod +x /usr/local/bin/pndpd``
3) **For systemd users:** Install the service unit file
```` ````
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.service wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.service -P /etc/systemd/system/
mv pndpd.service /usr/lib/systemd/system/
systemctl enable pndpd.service systemctl enable pndpd.service
```` ````
3) Download and install the config file 4) Download and install the config file
```` ````
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.conf mkdir -p /etc/pndpd
mkdir -p /etc/pndpd/ wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.conf -P /etc/pndpd/
mv pndpd.conf /etc/pndpd/
```` ````
4) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start`` 5) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start``
## Manual Usage ## Manual Usage
```` ````
pndpd config <path to file> 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> 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 ### 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:
```` ````

139
config.go
View File

@ -3,35 +3,17 @@ package main
import ( import (
"bufio" "bufio"
"fmt" "fmt"
"log"
"os" "os"
"pndpd/modules" "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 {
log.Fatal(err) fmt.Println("Error:", err.Error())
os.Exit(1)
} }
defer func(file *os.File) { defer func(file *os.File) {
_ = file.Close() _ = file.Close()
@ -52,107 +34,40 @@ func readConfig(dest string) {
continue continue
} }
if strings.HasPrefix(line, "responder") && strings.Contains(line, "{") { if strings.HasSuffix(line, "{") {
obj := configResponder{}
filter := ""
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, ";")
break
}
}
allResponders = append(allResponders, &obj)
} else 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)
} else if strings.Contains(line, "{") {
option := strings.TrimSuffix(strings.TrimSpace(line), "{") option := strings.TrimSuffix(strings.TrimSpace(line), "{")
option = strings.TrimSpace(option) option = strings.TrimSpace(option)
if modules.ModuleList != nil { module, command := modules.GetCommand(option, modules.Config)
for i := range modules.ModuleList { var lines = make([]string, 0)
if (*modules.ModuleList[i]).Option == option { if module != nil {
var lines []string for {
for { if !scanner.Scan() {
scanner.Scan() break
line = strings.TrimSpace(scanner.Text())
lines = append(lines, line)
if strings.HasPrefix(line, "}") {
break
}
}
(*modules.ModuleList[i]).ConfigCallback(lines)
} }
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,
})
} }
} }
}
if modules.ExistsBlockingModule() {
modules.ExecuteComplete()
waitForSignal()
modules.ShutdownAll()
} }
if err := scanner.Err(); err != nil { if err := scanner.Err(); err != nil {
log.Fatal(err) panic(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()
} }
} }

82
main.go
View File

@ -5,72 +5,64 @@ import (
"os" "os"
"os/signal" "os/signal"
"pndpd/modules" "pndpd/modules"
"pndpd/pndp"
"syscall" "syscall"
) )
import (
// Modules
_ "pndpd/modules/example"
_ "pndpd/modules/userInterface"
)
// WaitForSignal Waits (blocking) for the program to be interrupted by the OS var Version = "Development"
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 1.0 - Kioubit 2021") fmt.Println("PNDPD Version", Version, "- Kioubit 2022")
if modules.ModuleList != nil {
fmt.Print("Loaded Modules: ")
for i := range modules.ModuleList {
fmt.Print((*modules.ModuleList[i]).Name + " ")
}
fmt.Println()
}
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, "")
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:
for i := range modules.ModuleList { module, command := modules.GetCommand(os.Args[1], modules.CommandLine)
if (*modules.ModuleList[i]).Option == os.Args[1] { if module != nil {
(*modules.ModuleList[i]).CommandLineCallback(os.Args) modules.ExecuteInit(module, modules.CallbackInfo{
return CallbackType: modules.CommandLine,
Command: command,
Arguments: os.Args[2:],
})
if modules.ExistsBlockingModule() {
modules.ExecuteComplete()
waitForSignal()
modules.ShutdownAll()
} }
} else {
printUsage()
} }
printUsage()
return
} }
} }
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>")
fmt.Println("pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>") for i := range modules.ModuleList {
fmt.Println("pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>") for d := range (*modules.ModuleList[i]).Commands {
fmt.Println("More options and additional documentation in the example config file") 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

@ -1,3 +1,6 @@
//go:build mod_example
// +build mod_example
package example package example
import ( import (
@ -5,21 +8,45 @@ import (
"pndpd/modules" "pndpd/modules"
) )
// This is an example module that is not imported by the main program // This is an example module
func init() { func init() {
modules.RegisterModule("Example", "example", "example <parameter 1> <parameter 2>", commandLineRead, configRead) 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 configRead(s []string) { func initCallback(callback modules.CallbackInfo) {
// Prints out the contents of the config file that are relevant for this module (that are inside the example{} option) if callback.CallbackType == modules.CommandLine {
for _, n := range s { // The command registered by the module has been run in the commandline
fmt.Println(n) // "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 commandLineRead(s []string) { func completeCallback() {
// Prints out the command line options given to the program if the command starts with "example" //Called after the program has passed all options by calls to initCallback()
for _, n := range s { }
fmt.Println(n)
} func shutdownCallback() {
fmt.Println("Terminate all work")
} }

View File

@ -0,0 +1 @@
package example

View File

@ -3,19 +3,91 @@ package modules
var ModuleList []*Module var ModuleList []*Module
type Module struct { type Module struct {
Name string Name string
Option string Commands []Command
OptionDescription string InitCallback func(CallbackInfo)
CommandLineCallback func([]string) CompleteCallback func()
ConfigCallback func([]string) ShutdownCallback func()
} }
func RegisterModule(name string, option string, description string, commandLineCallback func([]string), configCallback func([]string)) { 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{ ModuleList = append(ModuleList, &Module{
Name: name, Name: name,
Option: option, Commands: commands,
OptionDescription: description, InitCallback: initCallback,
CommandLineCallback: commandLineCallback, CompleteCallback: CompleteCallback,
ConfigCallback: configCallback, 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

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

View File

@ -35,7 +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. // 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 {
fmt.Println("WARNING: You should use a whitelist for the responder unless you really know what you are doing") 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{}),
@ -57,7 +59,8 @@ 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.Println("Started responder instance on interface", obj.iface) fmt.Printf("Started responder instance on interface %s", obj.iface)
fmt.Println()
<-obj.stopChan <-obj.stopChan
} }
@ -128,7 +131,8 @@ 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.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 <-obj.stopChan
} }
@ -146,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 { func ParseFilter(f string) []*net.IPNet {
if f == "" { if f == "" {
return nil 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)
}
// ---------------------------------------------------------------------
}

View File

@ -4,38 +4,11 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"golang.org/x/net/bpf" "golang.org/x/net/bpf"
"golang.org/x/sys/unix"
"net" "net"
"sync" "sync"
"syscall" "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) // Htons Convert a uint16 to host byte order (big endian)
func htons(v uint16) int { func htons(v uint16) int {
return int((v << 8) | (v >> 8)) return int((v << 8) | (v >> 8))
@ -61,6 +34,7 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
} }
go func() { go func() {
<-stopChan <-stopChan
setPromisc(fd, iface, false, false)
_ = syscall.Close(fd) _ = syscall.Close(fd)
stopWG.Done() // syscall.read does not release when the file descriptor is closed stopWG.Done() // syscall.read does not release when the file descriptor is closed
}() }()
@ -77,6 +51,8 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
panic(err.Error()) panic(err.Error())
} }
setPromisc(fd, iface, true, false)
var protocolNo uint32 var protocolNo uint32
if requestType == ndp_SOL { if requestType == ndp_SOL {
//Neighbor Solicitation //Neighbor Solicitation
@ -99,8 +75,8 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
bpf.LoadAbsolute{Off: 54, Size: 1}, bpf.LoadAbsolute{Off: 54, Size: 1},
// Jump to the drop packet instruction if Type is not Neighbor Solicitation / Advertisement. // Jump to the drop packet instruction if Type is not Neighbor Solicitation / Advertisement.
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1}, bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1},
// Verdict is: send up to 4096 bytes of the packet to userspace. // Verdict is: send up to 86 bytes of the packet to userspace.
bpf.RetConstant{Val: 4096}, bpf.RetConstant{Val: 86},
// Verdict is: "ignore packet." // Verdict is: "ignore packet."
bpf.RetConstant{Val: 0}, bpf.RetConstant{Val: 0},
} }
@ -111,12 +87,12 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
} }
for { for {
buf := make([]byte, 4096) buf := make([]byte, 86)
numRead, err := syscall.Read(fd, buf) numRead, err := syscall.Read(fd, buf)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if numRead < 86 { if numRead < 78 {
if GlobalDebug { if GlobalDebug {
fmt.Println("Dropping packet since it does not meet the minimum length requirement") fmt.Println("Dropping packet since it does not meet the minimum length requirement")
fmt.Printf("% X\n", buf[:numRead]) fmt.Printf("% X\n", buf[:numRead])
@ -139,8 +115,6 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
fmt.Println("NDP Flags") fmt.Println("NDP Flags")
fmt.Printf("% X\n", buf[58]) fmt.Printf("% X\n", buf[58])
} }
fmt.Println("NDP MAC:")
fmt.Printf("% X\n", buf[80:86])
fmt.Println() fmt.Println()
} }
@ -151,15 +125,22 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
continue continue
} }
if requestType == ndp_ADV {
if buf[58] == 0x0 {
if GlobalDebug {
fmt.Println("Dropping Advertisement packet without any NDP flags set")
}
continue
}
}
responder <- &ndpRequest{ responder <- &ndpRequest{
requestType: requestType, requestType: requestType,
srcIP: buf[22:38], srcIP: buf[22:38],
dstIP: buf[38:54], dstIP: buf[38:54],
answeringForIP: buf[62:78], answeringForIP: buf[62:78],
mac: buf[80:86], payload: buf[54:],
receivedIfaceMac: niface.HardwareAddr, sourceIface: iface,
sourceIface: iface,
rawPacket: buf[:numRead],
} }
} }
} }

View File

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"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}
@ -134,7 +135,7 @@ func checksumAddition(b []byte) uint32 {
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)-1 == i {
sum += uint32(uint16(b[i])<<8 | uint16(0x0)) sum += uint32(uint16(b[i])<<8 | uint16(0x00))
} else { } else {
sum += uint32(uint16(b[i])<<8 | uint16(b[i+1])) sum += uint32(uint16(b[i])<<8 | uint16(b[i+1]))
} }
@ -143,12 +144,7 @@ func checksumAddition(b []byte) uint32 {
return sum return sum
} }
func checkPacketChecksum(scrip, dstip, payload []byte) bool { func checkPacketChecksum(v6 *ipv6Header, payload []byte) bool {
v6, err := newIpv6Header(scrip, dstip)
if err != nil {
return false
}
packetsum := make([]byte, 2) packetsum := make([]byte, 2)
copy(packetsum, payload[2:4]) copy(packetsum, payload[2:4])
@ -162,9 +158,6 @@ func checkPacketChecksum(scrip, dstip, payload []byte) bool {
bChecksum := make([]byte, 2) bChecksum := make([]byte, 2)
binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload)) binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload))
if bytes.Equal(packetsum, bChecksum) { if bytes.Equal(packetsum, bChecksum) {
if GlobalDebug {
fmt.Println("Verified received packet checksum")
}
return true return true
} else { } else {
if GlobalDebug { if GlobalDebug {
@ -176,8 +169,5 @@ func checkPacketChecksum(scrip, dstip, payload []byte) bool {
func isIpv6(ip string) bool { func isIpv6(ip string) bool {
rip := net.ParseIP(ip) rip := net.ParseIP(ip)
if rip.To16() == nil { return rip != nil && strings.Contains(ip, ":")
return false
}
return true
} }

View File

@ -9,9 +9,12 @@ 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, 40)
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)
@ -24,44 +27,23 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
panic(err) panic(err)
} }
nIface, err := net.InterfaceByName(iface) respondIface, err := net.InterfaceByName(iface)
if err != nil { if err != nil {
panic(err.Error()) panic(err.Error())
} }
var result = emptyIpv6 var result = selectSourceIP(respondIface)
ifaceaddrs, err := nIface.Addrs()
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
_, tnet, _ := net.ParseCIDR("fc00::/7")
if !tnet.Contains(tip) {
break
}
} else if tip.IsLinkLocalUnicast() && !haveUla {
result = tip
}
}
}
for { for {
var n *ndpRequest var req *ndpRequest
if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) { if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) {
select { select {
case <-stopChan: case <-stopChan:
return return
case n = <-requests: case req = <-requests:
} }
} else { } else {
// THis is if ndpQuestionChan != nil && respondType == ndp_ADV // This is if ndpQuestionChan != nil && respondType == ndp_ADV
select { select {
case <-stopChan: case <-stopChan:
return return
@ -69,48 +51,30 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
ndpQuestionsList = append(ndpQuestionsList, q) ndpQuestionsList = append(ndpQuestionsList, q)
ndpQuestionsList = cleanupQuestionList(ndpQuestionsList) ndpQuestionsList = cleanupQuestionList(ndpQuestionsList)
continue continue
case n = <-requests: case req = <-requests:
} }
} }
var _, LinkLocalSpace, _ = net.ParseCIDR("fe80::/10") if linkLocalSpace.Contains(req.answeringForIP) {
if LinkLocalSpace.Contains(n.answeringForIP) {
if GlobalDebug { if GlobalDebug {
fmt.Println("Dropping packet asking for a link-local IP") fmt.Println("Dropping packet asking for a link-local IP")
} }
continue continue
} }
if n.requestType == ndp_ADV { v6Header, err := newIpv6Header(req.srcIP, req.dstIP)
if (n.rawPacket[78] != 0x02) || (n.rawPacket[79] != 0x01) { if err != nil {
if GlobalDebug { continue
fmt.Println("Dropping Advertisement packet without target Source address set")
}
continue
}
if n.rawPacket[58] == 0x0 {
if GlobalDebug {
fmt.Println("Dropping Advertisement packet without any NDP flags set")
}
continue
}
} else {
if (n.rawPacket[78] != 0x01) || (n.rawPacket[79] != 0x01) {
if GlobalDebug {
fmt.Println("Dropping Solicitation packet without Source address set")
}
continue
}
} }
if !checkPacketChecksum(v6Header, req.payload) {
if !checkPacketChecksum(n.srcIP, n.dstIP, n.rawPacket[54:]) {
if GlobalDebug {
fmt.Println("Dropping packet because of invalid checksum")
}
continue 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)
@ -118,11 +82,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
autoifaceaddrs, err := autoiface.Addrs() autoifaceaddrs, err := autoiface.Addrs()
for _, l := range autoifaceaddrs { for _, l := range autoifaceaddrs {
_, anet, err := net.ParseCIDR(l.String()) testIP, anet, err := net.ParseCIDR(l.String())
if err != nil { if err != nil {
break break
} }
if isIpv6(anet.String()) { if isIpv6(testIP.String()) {
filter = append(filter, anet) filter = append(filter, anet)
} }
} }
@ -131,9 +95,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(n.answeringForIP) { if i.Contains(req.answeringForIP) {
if GlobalDebug { if GlobalDebug {
fmt.Println("Responded for whitelisted IP", n.answeringForIP) fmt.Println("Responded for whitelisted IP", req.answeringForIP)
} }
ok = true ok = true
break break
@ -148,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) fmt.Println("Getting ready to send packet of type", respondType, "out on interface", iface)
} }
if n.sourceIface == iface { if req.sourceIface == iface {
pkt(fd, result, n.srcIP, n.answeringForIP, nIface.HardwareAddr, respondType) pkt(fd, result, req.srcIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
} else { } else {
if respondType == ndp_ADV { if respondType == ndp_ADV {
success := false success := false
n.dstIP, success = getAddressFromQuestionListRetry(n.answeringForIP, ndpQuestionChan, ndpQuestionsList) req.dstIP, success = getAddressFromQuestionListRetry(req.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")
@ -162,11 +126,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
} }
} else { } else {
ndpQuestionChan <- &ndpQuestion{ ndpQuestionChan <- &ndpQuestion{
targetIP: n.answeringForIP, targetIP: req.answeringForIP,
askedBy: n.srcIP, askedBy: req.srcIP,
} }
} }
pkt(fd, result, n.dstIP, n.answeringForIP, nIface.HardwareAddr, respondType) pkt(fd, result, req.dstIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
} }
} }
} }
@ -207,18 +171,20 @@ func getAddressFromQuestionListRetry(targetIP []byte, ndpQuestionChan chan *ndpQ
if success { if success {
return result, true return result, true
} }
forloop:
for { gotBuffered := false
select { select {
case q := <-ndpQuestionChan: case q := <-ndpQuestionChan:
ndpQuestionsList = append(ndpQuestionsList, q) ndpQuestionsList = append(ndpQuestionsList, q)
default: gotBuffered = true
break forloop default:
}
} }
result, success = getAddressFromQuestionList(targetIP, ndpQuestionsList) if gotBuffered {
return result, success result, success = getAddressFromQuestionList(targetIP, ndpQuestionsList)
}
return nil, false
} }
func getAddressFromQuestionList(targetIP []byte, ndpQuestionsList []*ndpQuestion) ([]byte, bool) { func getAddressFromQuestionList(targetIP []byte, ndpQuestionsList []*ndpQuestion) ([]byte, bool) {
@ -242,3 +208,33 @@ func cleanupQuestionList(s []*ndpQuestion) []*ndpQuestion {
} }
return s 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 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
@ -12,6 +13,7 @@ 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

@ -7,11 +7,11 @@ After=network.target network-online.target
Type=simple Type=simple
Restart=on-failure Restart=on-failure
RestartSec=5s RestartSec=5s
ExecStart=/usr/bin/pndpd config /etc/pndpd/pndpd.conf ExecStart=/usr/local/bin/pndpd config /etc/pndpd/pndpd.conf
DynamicUser=yes DynamicUser=yes
AmbientCapabilities=CAP_NET_RAW AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
CapabilityBoundingSet= CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
ProtectHome=yes ProtectHome=yes
[Install] [Install]