Compare commits
No commits in common. "burble.dn42" and "0.7" have entirely different histories.
burble.dn4
...
0.7
55
.drone.yml
55
.drone.yml
@ -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
5
.gitignore
vendored
@ -1,7 +1,2 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
bin/
|
|
||||||
|
|
||||||
*~
|
|
||||||
pndpd
|
|
||||||
|
21
Makefile
21
Makefile
@ -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
|
|
48
README.md
48
README.md
@ -1,51 +1,21 @@
|
|||||||
# PNDPD - NDP Responder + Proxy
|
# PNDPD - NDP Responder + Proxy
|
||||||
|
|
||||||
[](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
135
config.go
@ -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
75
main.go
@ -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)
|
|
||||||
}
|
}
|
||||||
|
@ -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")
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package example
|
|
@ -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
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package userInterface
|
|
@ -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)
|
|
||||||
}
|
|
@ -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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
}
|
|
146
pndp/listener.go
146
pndp/listener.go
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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, ":")
|
||||||
|
@ -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
199
pndp/rawsocket.go
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
Loading…
x
Reference in New Issue
Block a user