Compare commits
No commits in common. "burble.dn42" and "1.0" have entirely different histories.
burble.dn4
...
1.0
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
|
|
42
README.md
42
README.md
@ -1,50 +1,38 @@
|
|||||||
# 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
|
## Installing & Updating
|
||||||
|
|
||||||
1) Download the latest release from the [releases page](https://github.com/Kioubit/pndpd/releases) and move the binary to the ``/usr/local/bin/`` directory under the filename ``pndpd``.
|
1) Download the latest release from the releases page and move the binary to the ``/urs/bin/``
|
||||||
2) Allow executing the file by running ``chmod +x /usr/local/bin/pndpd``
|
2) For systemd users: Install the service
|
||||||
3) **For systemd users:** Install the service unit file
|
|
||||||
````
|
````
|
||||||
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.service -P /etc/systemd/system/
|
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.service
|
||||||
|
mv pndpd.service /usr/lib/systemd/system/
|
||||||
systemctl enable pndpd.service
|
systemctl enable pndpd.service
|
||||||
````
|
````
|
||||||
4) Download and install the config file
|
3) Download and install the config file
|
||||||
````
|
````
|
||||||
mkdir -p /etc/pndpd
|
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.conf
|
||||||
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.conf -P /etc/pndpd/
|
mkdir -p /etc/pndpd/
|
||||||
|
mv pndpd.conf /etc/pndpd/
|
||||||
````
|
````
|
||||||
5) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start``
|
4) 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 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
|
### 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:
|
||||||
````
|
````
|
||||||
|
137
config.go
137
config.go
@ -3,17 +3,35 @@ 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 {
|
||||||
fmt.Println("Error:", err.Error())
|
log.Fatal(err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
@ -34,40 +52,107 @@ func readConfig(dest string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasSuffix(line, "{") {
|
if strings.HasPrefix(line, "responder") && strings.Contains(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)
|
||||||
module, command := modules.GetCommand(option, modules.Config)
|
if modules.ModuleList != nil {
|
||||||
var lines = make([]string, 0)
|
for i := range modules.ModuleList {
|
||||||
if module != nil {
|
if (*modules.ModuleList[i]).Option == option {
|
||||||
|
var lines []string
|
||||||
for {
|
for {
|
||||||
if !scanner.Scan() {
|
scanner.Scan()
|
||||||
break
|
line = strings.TrimSpace(scanner.Text())
|
||||||
}
|
|
||||||
line := strings.TrimSpace(scanner.Text())
|
|
||||||
if strings.Contains(line, "}") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
}
|
if strings.HasPrefix(line, "}") {
|
||||||
modules.ExecuteInit(module, modules.CallbackInfo{
|
break
|
||||||
CallbackType: modules.Config,
|
}
|
||||||
Command: command,
|
}
|
||||||
Arguments: lines,
|
(*modules.ModuleList[i]).ConfigCallback(lines)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if modules.ExistsBlockingModule() {
|
|
||||||
modules.ExecuteComplete()
|
|
||||||
waitForSignal()
|
|
||||||
modules.ShutdownAll()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
82
main.go
82
main.go
@ -5,64 +5,72 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"pndpd/modules"
|
"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 1.0 - Kioubit 2021")
|
||||||
|
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:
|
||||||
module, command := modules.GetCommand(os.Args[1], modules.CommandLine)
|
for i := range modules.ModuleList {
|
||||||
if module != nil {
|
if (*modules.ModuleList[i]).Option == os.Args[1] {
|
||||||
modules.ExecuteInit(module, modules.CallbackInfo{
|
(*modules.ModuleList[i]).CommandLineCallback(os.Args)
|
||||||
CallbackType: modules.CommandLine,
|
return
|
||||||
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>")
|
||||||
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,6 +1,3 @@
|
|||||||
//go:build mod_example
|
|
||||||
// +build mod_example
|
|
||||||
|
|
||||||
package example
|
package example
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -8,45 +5,21 @@ import (
|
|||||||
"pndpd/modules"
|
"pndpd/modules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is an example module
|
// This is an example module that is not imported by the main program
|
||||||
func init() {
|
func init() {
|
||||||
commands := []modules.Command{{
|
modules.RegisterModule("Example", "example", "example <parameter 1> <parameter 2>", commandLineRead, configRead)
|
||||||
CommandText: "pndpd command1",
|
}
|
||||||
Description: "This is the usage description for command1",
|
|
||||||
BlockTerminate: true,
|
func configRead(s []string) {
|
||||||
CommandLineEnabled: true,
|
// Prints out the contents of the config file that are relevant for this module (that are inside the example{} option)
|
||||||
ConfigEnabled: true,
|
for _, n := range s {
|
||||||
}, {
|
fmt.Println(n)
|
||||||
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) {
|
func commandLineRead(s []string) {
|
||||||
if callback.CallbackType == modules.CommandLine {
|
// Prints out the command line options given to the program if the command starts with "example"
|
||||||
// The command registered by the module has been run in the commandline
|
for _, n := range s {
|
||||||
// "arguments" contains the os.Args[] passed to the program after the command registered by this module
|
fmt.Println(n)
|
||||||
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
|
|
@ -4,90 +4,18 @@ var ModuleList []*Module
|
|||||||
|
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Name string
|
Name string
|
||||||
Commands []Command
|
Option string
|
||||||
InitCallback func(CallbackInfo)
|
OptionDescription string
|
||||||
CompleteCallback func()
|
CommandLineCallback func([]string)
|
||||||
ShutdownCallback func()
|
ConfigCallback func([]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Command struct {
|
func RegisterModule(name string, option string, description string, commandLineCallback func([]string), configCallback func([]string)) {
|
||||||
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,
|
||||||
Commands: commands,
|
Option: option,
|
||||||
InitCallback: initCallback,
|
OptionDescription: description,
|
||||||
CompleteCallback: CompleteCallback,
|
CommandLineCallback: commandLineCallback,
|
||||||
ShutdownCallback: shutdownCallback,
|
ConfigCallback: configCallback,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
@ -12,8 +12,10 @@ type ndpRequest struct {
|
|||||||
srcIP []byte
|
srcIP []byte
|
||||||
answeringForIP []byte
|
answeringForIP []byte
|
||||||
dstIP []byte
|
dstIP []byte
|
||||||
|
mac []byte
|
||||||
|
receivedIfaceMac []byte
|
||||||
sourceIface string
|
sourceIface string
|
||||||
payload []byte
|
rawPacket []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
// ---------------------------------------------------------------------
|
|
||||||
}
|
|
@ -6,7 +6,6 @@ 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}
|
||||||
@ -135,7 +134,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(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,7 +143,12 @@ func checksumAddition(b []byte) uint32 {
|
|||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPacketChecksum(v6 *ipv6Header, payload []byte) bool {
|
func checkPacketChecksum(scrip, dstip, 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])
|
||||||
|
|
||||||
@ -158,6 +162,9 @@ func checkPacketChecksum(v6 *ipv6Header, 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 {
|
||||||
@ -169,5 +176,8 @@ func checkPacketChecksum(v6 *ipv6Header, payload []byte) bool {
|
|||||||
|
|
||||||
func isIpv6(ip string) bool {
|
func isIpv6(ip string) bool {
|
||||||
rip := net.ParseIP(ip)
|
rip := net.ParseIP(ip)
|
||||||
return rip != nil && strings.Contains(ip, ":")
|
if rip.To16() == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
@ -35,9 +35,7 @@ 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")
|
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,8 +57,7 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +128,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 +146,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
|
@ -4,11 +4,38 @@ 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))
|
||||||
@ -34,7 +61,6 @@ 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
|
||||||
}()
|
}()
|
||||||
@ -51,8 +77,6 @@ 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
|
||||||
@ -75,8 +99,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 86 bytes of the packet to userspace.
|
// Verdict is: send up to 4096 bytes of the packet to userspace.
|
||||||
bpf.RetConstant{Val: 86},
|
bpf.RetConstant{Val: 4096},
|
||||||
// Verdict is: "ignore packet."
|
// Verdict is: "ignore packet."
|
||||||
bpf.RetConstant{Val: 0},
|
bpf.RetConstant{Val: 0},
|
||||||
}
|
}
|
||||||
@ -87,12 +111,12 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 86)
|
buf := make([]byte, 4096)
|
||||||
numRead, err := syscall.Read(fd, buf)
|
numRead, err := syscall.Read(fd, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if numRead < 78 {
|
if numRead < 86 {
|
||||||
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])
|
||||||
@ -115,6 +139,8 @@ 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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,22 +151,15 @@ 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],
|
||||||
payload: buf[54:],
|
mac: buf[80:86],
|
||||||
|
receivedIfaceMac: niface.HardwareAddr,
|
||||||
sourceIface: iface,
|
sourceIface: iface,
|
||||||
|
rawPacket: buf[:numRead],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,12 +9,9 @@ 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)
|
||||||
@ -27,23 +24,44 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
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
|
||||||
|
}
|
||||||
|
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 req *ndpRequest
|
var n *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 req = <-requests:
|
case n = <-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
|
||||||
@ -51,30 +69,48 @@ 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 req = <-requests:
|
case n = <-requests:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if linkLocalSpace.Contains(req.answeringForIP) {
|
var _, LinkLocalSpace, _ = net.ParseCIDR("fe80::/10")
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
v6Header, err := newIpv6Header(req.srcIP, req.dstIP)
|
if n.requestType == ndp_ADV {
|
||||||
if err != nil {
|
if (n.rawPacket[78] != 0x02) || (n.rawPacket[79] != 0x01) {
|
||||||
|
if GlobalDebug {
|
||||||
|
fmt.Println("Dropping Advertisement packet without target Source address set")
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !checkPacketChecksum(v6Header, req.payload) {
|
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(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)
|
||||||
@ -82,11 +118,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 {
|
||||||
testIP, anet, err := net.ParseCIDR(l.String())
|
_, anet, err := net.ParseCIDR(l.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 +131,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 +148,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 +162,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,20 +207,18 @@ 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) {
|
||||||
@ -208,33 +242,3 @@ 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
|
|
||||||
}
|
|
||||||
|
@ -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
|
||||||
|
@ -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/local/bin/pndpd config /etc/pndpd/pndpd.conf
|
ExecStart=/usr/bin/pndpd config /etc/pndpd/pndpd.conf
|
||||||
|
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
|
AmbientCapabilities=CAP_NET_RAW
|
||||||
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
|
CapabilityBoundingSet=
|
||||||
ProtectHome=yes
|
ProtectHome=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user