Compare commits
28 Commits
1.0
...
burble.dn4
Author | SHA1 | Date | |
---|---|---|---|
e5b04565ec | |||
58ac05adc9 | |||
b3d2305f9d | |||
|
dff2f9ab10 | ||
|
49c6c333e9 | ||
|
a7eb52c0c5 | ||
|
ede44e8426 | ||
|
d8c439aad9 | ||
|
48486cc205 | ||
|
1f294666f3 | ||
|
95bf95784e | ||
|
1da76a4547 | ||
|
b7c5e6afba | ||
|
1ec9477d69 | ||
|
2e62c0b5d5 | ||
|
ff27eb5141 | ||
|
4ddaa98351 | ||
|
6ebee06861 | ||
|
0f580cbbbd | ||
|
5a901eada3 | ||
|
504daad9e1 | ||
|
11110fb66b | ||
|
e3d463e815 | ||
|
45b9ac5e86 | ||
|
de4d228950 | ||
|
d196469c01 | ||
|
f8f0d0d055 | ||
|
d6421a007e |
55
.drone.yml
Normal file
55
.drone.yml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: golang
|
||||||
|
environment:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
commands:
|
||||||
|
- go vet
|
||||||
|
- go build
|
||||||
|
|
||||||
|
- name: upload binary
|
||||||
|
image: plugins/s3
|
||||||
|
settings:
|
||||||
|
bucket: artifacts
|
||||||
|
access_key:
|
||||||
|
from_secret: MINIO_ACCESS_KEY
|
||||||
|
secret_key:
|
||||||
|
from_secret: MINIO_SECRET_KEY
|
||||||
|
endpoint: https://minio.burble.dn42
|
||||||
|
region: uk-lon3
|
||||||
|
path_style: true
|
||||||
|
source: pndpd
|
||||||
|
target: /pndpd/${DRONE_BRANCH}
|
||||||
|
|
||||||
|
- name: upload service
|
||||||
|
image: plugins/s3
|
||||||
|
settings:
|
||||||
|
bucket: artifacts
|
||||||
|
access_key:
|
||||||
|
from_secret: MINIO_ACCESS_KEY
|
||||||
|
secret_key:
|
||||||
|
from_secret: MINIO_SECRET_KEY
|
||||||
|
endpoint: https://minio.burble.dn42
|
||||||
|
region: uk-lon3
|
||||||
|
path_style: true
|
||||||
|
source: pndpd.service
|
||||||
|
target: /pndpd/${DRONE_BRANCH}
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: MINIO_ACCESS_KEY
|
||||||
|
get:
|
||||||
|
path: burble.dn42/kv/data/drone/minio
|
||||||
|
name: ACCESS_KEY
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: secret
|
||||||
|
name: MINIO_SECRET_KEY
|
||||||
|
get:
|
||||||
|
path: burble.dn42/kv/data/drone/minio
|
||||||
|
name: SECRET_KEY
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,2 +1,7 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
bin/
|
||||||
|
|
||||||
|
*~
|
||||||
|
pndpd
|
||||||
|
21
Makefile
Normal file
21
Makefile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Makefile for PNDPD
|
||||||
|
|
||||||
|
BINARY=pndpd
|
||||||
|
MODULES=
|
||||||
|
VERSION=`git describe --tags`
|
||||||
|
LDFLAGS=-ldflags "-X main.Version=${VERSION}"
|
||||||
|
|
||||||
|
build:
|
||||||
|
go build -tags=${MODULES} -o bin/${BINARY} .
|
||||||
|
|
||||||
|
release:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_amd64.bin .
|
||||||
|
|
||||||
|
release-all:
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_amd64.bin
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_arm64.bin
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -tags=${MODULES} ${LDFLAGS} -o bin/${BINARY}_${VERSION}_linux_arm.bin
|
||||||
|
|
||||||
|
clean:
|
||||||
|
find bin/ -type f -delete
|
||||||
|
if [ -d "bin/" ]; then rm -d bin/ ;fi
|
42
README.md
42
README.md
@ -1,38 +1,50 @@
|
|||||||
# 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 and move the binary to the ``/urs/bin/``
|
1) Download the latest release from the [releases page](https://github.com/Kioubit/pndpd/releases) and move the binary to the ``/usr/local/bin/`` directory under the filename ``pndpd``.
|
||||||
2) For systemd users: Install the service
|
2) Allow executing the file by running ``chmod +x /usr/local/bin/pndpd``
|
||||||
|
3) **For systemd users:** Install the service unit file
|
||||||
````
|
````
|
||||||
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.service
|
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.service -P /etc/systemd/system/
|
||||||
mv pndpd.service /usr/lib/systemd/system/
|
|
||||||
systemctl enable pndpd.service
|
systemctl enable pndpd.service
|
||||||
````
|
````
|
||||||
3) Download and install the config file
|
4) Download and install the config file
|
||||||
````
|
````
|
||||||
wget https://git.dn42.dev/Kioubit/Pndpd/src/branch/master/pndpd.conf
|
mkdir -p /etc/pndpd
|
||||||
mkdir -p /etc/pndpd/
|
wget https://raw.githubusercontent.com/Kioubit/pndpd/master/pndpd.conf -P /etc/pndpd/
|
||||||
mv pndpd.conf /etc/pndpd/
|
|
||||||
````
|
````
|
||||||
4) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start``
|
5) Edit the config at ``/etc/pndpd/pndpd.conf`` and then start the service using ``service pndpd start``
|
||||||
|
|
||||||
## Manual Usage
|
## Manual Usage
|
||||||
````
|
````
|
||||||
pndpd config <path to file>
|
pndpd config <path to file>
|
||||||
pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>
|
pndpd responder <interface> <optional whitelist of CIDRs separated by a semicolon>
|
||||||
pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>
|
pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>
|
||||||
````
|
````
|
||||||
More options and additional documentation in the example config file (pndpd.conf).
|
More options and additional documentation in the example config file (``pndpd.conf``).
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
|
### Building
|
||||||
|
For building, the version of go needs to be installed that is specified in the go.mod file. A makefile is available. Optionally adjust the ``MODULES`` variable to include or exclude modules from the modules directory.
|
||||||
|
````
|
||||||
|
make clean
|
||||||
|
make release-all
|
||||||
|
````
|
||||||
|
Find the binaries in the ``bin/`` directory
|
||||||
|
|
||||||
### Adding Modules
|
### Adding Modules
|
||||||
It is easy to add functionality to PNDPD. For additions outside the core functionality you only need to keep the following methods in mind:
|
It is easy to add functionality to PNDPD. For additions outside the core functionality you only need to keep the following methods in mind:
|
||||||
````
|
````
|
||||||
|
139
config.go
139
config.go
@ -3,35 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"pndpd/modules"
|
"pndpd/modules"
|
||||||
"pndpd/pndp"
|
"pndpd/pndp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configResponder struct {
|
|
||||||
Iface string
|
|
||||||
Filter string
|
|
||||||
autosense string
|
|
||||||
instance *pndp.ResponderObj
|
|
||||||
}
|
|
||||||
|
|
||||||
type configProxy struct {
|
|
||||||
Iface1 string
|
|
||||||
Iface2 string
|
|
||||||
Filter string
|
|
||||||
autosense string
|
|
||||||
instance *pndp.ProxyObj
|
|
||||||
}
|
|
||||||
|
|
||||||
var allResponders []*configResponder
|
|
||||||
var allProxies []*configProxy
|
|
||||||
|
|
||||||
func readConfig(dest string) {
|
func readConfig(dest string) {
|
||||||
file, err := os.Open(dest)
|
file, err := os.Open(dest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
fmt.Println("Error:", err.Error())
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer func(file *os.File) {
|
defer func(file *os.File) {
|
||||||
_ = file.Close()
|
_ = file.Close()
|
||||||
@ -52,107 +34,40 @@ func readConfig(dest string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(line, "responder") && strings.Contains(line, "{") {
|
if strings.HasSuffix(line, "{") {
|
||||||
obj := configResponder{}
|
option := strings.TrimSuffix(strings.TrimSpace(line), "{")
|
||||||
filter := ""
|
option = strings.TrimSpace(option)
|
||||||
|
module, command := modules.GetCommand(option, modules.Config)
|
||||||
|
var lines = make([]string, 0)
|
||||||
|
if module != nil {
|
||||||
for {
|
for {
|
||||||
scanner.Scan()
|
if !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
|
break
|
||||||
}
|
}
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if strings.Contains(line, "}") {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
lines = append(lines, line)
|
||||||
|
}
|
||||||
|
modules.ExecuteInit(module, modules.CallbackInfo{
|
||||||
|
CallbackType: modules.Config,
|
||||||
|
Command: command,
|
||||||
|
Arguments: lines,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
allResponders = append(allResponders, &obj)
|
|
||||||
} 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.TrimSpace(option)
|
|
||||||
if modules.ModuleList != nil {
|
|
||||||
for i := range modules.ModuleList {
|
|
||||||
if (*modules.ModuleList[i]).Option == option {
|
|
||||||
var lines []string
|
|
||||||
for {
|
|
||||||
scanner.Scan()
|
|
||||||
line = strings.TrimSpace(scanner.Text())
|
|
||||||
lines = append(lines, line)
|
|
||||||
if strings.HasPrefix(line, "}") {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(*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 {
|
||||||
log.Fatal(err)
|
panic(err)
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range allProxies {
|
|
||||||
o := pndp.NewProxy(n.Iface1, n.Iface2, pndp.ParseFilter(n.Filter), n.autosense)
|
|
||||||
n.instance = o
|
|
||||||
o.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range allResponders {
|
|
||||||
o := pndp.NewResponder(n.Iface, pndp.ParseFilter(n.Filter), n.autosense)
|
|
||||||
n.instance = o
|
|
||||||
o.Start()
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForSignal()
|
|
||||||
|
|
||||||
for _, n := range allProxies {
|
|
||||||
n.instance.Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, n := range allResponders {
|
|
||||||
n.instance.Stop()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
82
main.go
82
main.go
@ -5,72 +5,64 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"pndpd/modules"
|
"pndpd/modules"
|
||||||
"pndpd/pndp"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
import (
|
||||||
|
// Modules
|
||||||
|
_ "pndpd/modules/example"
|
||||||
|
_ "pndpd/modules/userInterface"
|
||||||
|
)
|
||||||
|
|
||||||
// WaitForSignal Waits (blocking) for the program to be interrupted by the OS
|
var Version = "Development"
|
||||||
func WaitForSignal() {
|
|
||||||
var sigCh = make(chan os.Signal)
|
|
||||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-sigCh
|
|
||||||
close(sigCh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("PNDPD Version 1.0 - Kioubit 2021")
|
fmt.Println("PNDPD Version", Version, "- Kioubit 2022")
|
||||||
if modules.ModuleList != nil {
|
|
||||||
fmt.Print("Loaded Modules: ")
|
|
||||||
for i := range modules.ModuleList {
|
|
||||||
fmt.Print((*modules.ModuleList[i]).Name + " ")
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(os.Args) <= 2 {
|
if len(os.Args) <= 2 {
|
||||||
printUsage()
|
printUsage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "respond":
|
|
||||||
var r *pndp.ResponderObj
|
|
||||||
if len(os.Args) == 4 {
|
|
||||||
r = pndp.NewResponder(os.Args[2], pndp.ParseFilter(os.Args[3]), "")
|
|
||||||
r.Start()
|
|
||||||
} else {
|
|
||||||
r = pndp.NewResponder(os.Args[2], nil, "")
|
|
||||||
r.Start()
|
|
||||||
}
|
|
||||||
WaitForSignal()
|
|
||||||
r.Stop()
|
|
||||||
case "proxy":
|
|
||||||
var p *pndp.ProxyObj
|
|
||||||
if len(os.Args) == 5 {
|
|
||||||
p = pndp.NewProxy(os.Args[2], os.Args[3], pndp.ParseFilter(os.Args[4]), "")
|
|
||||||
} else {
|
|
||||||
p = pndp.NewProxy(os.Args[2], os.Args[3], nil, "")
|
|
||||||
}
|
|
||||||
WaitForSignal()
|
|
||||||
p.Stop()
|
|
||||||
case "config":
|
case "config":
|
||||||
readConfig(os.Args[2])
|
readConfig(os.Args[2])
|
||||||
default:
|
default:
|
||||||
for i := range modules.ModuleList {
|
module, command := modules.GetCommand(os.Args[1], modules.CommandLine)
|
||||||
if (*modules.ModuleList[i]).Option == os.Args[1] {
|
if module != nil {
|
||||||
(*modules.ModuleList[i]).CommandLineCallback(os.Args)
|
modules.ExecuteInit(module, modules.CallbackInfo{
|
||||||
return
|
CallbackType: modules.CommandLine,
|
||||||
}
|
Command: command,
|
||||||
|
Arguments: os.Args[2:],
|
||||||
|
})
|
||||||
|
if modules.ExistsBlockingModule() {
|
||||||
|
modules.ExecuteComplete()
|
||||||
|
waitForSignal()
|
||||||
|
modules.ShutdownAll()
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
printUsage()
|
printUsage()
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
|
fmt.Println("More options and additional documentation in the example config file")
|
||||||
fmt.Println("Usage:")
|
fmt.Println("Usage:")
|
||||||
fmt.Println("pndpd config <path to file>")
|
fmt.Println("pndpd config <path to file>")
|
||||||
fmt.Println("pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>")
|
for i := range modules.ModuleList {
|
||||||
fmt.Println("pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>")
|
for d := range (*modules.ModuleList[i]).Commands {
|
||||||
fmt.Println("More options and additional documentation in the example config file")
|
if (*modules.ModuleList[i]).Commands[d].CommandLineEnabled {
|
||||||
|
fmt.Println((*modules.ModuleList[i]).Commands[d].Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForSignal Waits (blocking) for the program to be interrupted by the OS
|
||||||
|
func waitForSignal() {
|
||||||
|
var sigCh = make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||||
|
<-sigCh
|
||||||
|
close(sigCh)
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
//go:build mod_example
|
||||||
|
// +build mod_example
|
||||||
|
|
||||||
package example
|
package example
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -5,21 +8,45 @@ import (
|
|||||||
"pndpd/modules"
|
"pndpd/modules"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is an example module that is not imported by the main program
|
// This is an example module
|
||||||
func init() {
|
func init() {
|
||||||
modules.RegisterModule("Example", "example", "example <parameter 1> <parameter 2>", commandLineRead, configRead)
|
commands := []modules.Command{{
|
||||||
|
CommandText: "pndpd command1",
|
||||||
|
Description: "This is the usage description for command1",
|
||||||
|
BlockTerminate: true,
|
||||||
|
CommandLineEnabled: true,
|
||||||
|
ConfigEnabled: true,
|
||||||
|
}, {
|
||||||
|
CommandText: "pndpd command2",
|
||||||
|
Description: "This is the usage description for command2",
|
||||||
|
BlockTerminate: false,
|
||||||
|
CommandLineEnabled: false,
|
||||||
|
ConfigEnabled: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
modules.RegisterModule("Example", commands, initCallback, completeCallback, shutdownCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configRead(s []string) {
|
func initCallback(callback modules.CallbackInfo) {
|
||||||
// Prints out the contents of the config file that are relevant for this module (that are inside the example{} option)
|
if callback.CallbackType == modules.CommandLine {
|
||||||
for _, n := range s {
|
// The command registered by the module has been run in the commandline
|
||||||
fmt.Println(n)
|
// "arguments" contains the os.Args[] passed to the program after the command registered by this module
|
||||||
|
fmt.Println("Command: ", callback.Command.CommandText)
|
||||||
|
fmt.Println(callback.Arguments)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// The command registered by the module was found in the config file
|
||||||
|
// "arguments" contains the lines between the curly braces
|
||||||
|
fmt.Println("Command: ", callback.Command.CommandText)
|
||||||
|
fmt.Println(callback.Arguments)
|
||||||
}
|
}
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandLineRead(s []string) {
|
func completeCallback() {
|
||||||
// Prints out the command line options given to the program if the command starts with "example"
|
//Called after the program has passed all options by calls to initCallback()
|
||||||
for _, n := range s {
|
}
|
||||||
fmt.Println(n)
|
|
||||||
}
|
func shutdownCallback() {
|
||||||
|
fmt.Println("Terminate all work")
|
||||||
}
|
}
|
||||||
|
1
modules/example/module.go
Normal file
1
modules/example/module.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package example
|
@ -4,18 +4,90 @@ var ModuleList []*Module
|
|||||||
|
|
||||||
type Module struct {
|
type Module struct {
|
||||||
Name string
|
Name string
|
||||||
Option string
|
Commands []Command
|
||||||
OptionDescription string
|
InitCallback func(CallbackInfo)
|
||||||
CommandLineCallback func([]string)
|
CompleteCallback func()
|
||||||
ConfigCallback func([]string)
|
ShutdownCallback func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterModule(name string, option string, description string, commandLineCallback func([]string), configCallback func([]string)) {
|
type Command struct {
|
||||||
|
CommandText string
|
||||||
|
Description string
|
||||||
|
BlockTerminate bool
|
||||||
|
CommandLineEnabled bool
|
||||||
|
ConfigEnabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallbackType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandLine CallbackType = 0
|
||||||
|
Config CallbackType = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type CallbackInfo struct {
|
||||||
|
CallbackType CallbackType
|
||||||
|
Command Command
|
||||||
|
Arguments []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterModule(name string, commands []Command, initCallback func(CallbackInfo), CompleteCallback func(), shutdownCallback func()) {
|
||||||
ModuleList = append(ModuleList, &Module{
|
ModuleList = append(ModuleList, &Module{
|
||||||
Name: name,
|
Name: name,
|
||||||
Option: option,
|
Commands: commands,
|
||||||
OptionDescription: description,
|
InitCallback: initCallback,
|
||||||
CommandLineCallback: commandLineCallback,
|
CompleteCallback: CompleteCallback,
|
||||||
ConfigCallback: configCallback,
|
ShutdownCallback: shutdownCallback,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetCommand(target string, scope CallbackType) (*Module, Command) {
|
||||||
|
for i := range ModuleList {
|
||||||
|
for _, command := range ModuleList[i].Commands {
|
||||||
|
if command.CommandText == target {
|
||||||
|
if scope == CommandLine && command.CommandLineEnabled {
|
||||||
|
return ModuleList[i], command
|
||||||
|
}
|
||||||
|
if scope == Config && command.ConfigEnabled {
|
||||||
|
return ModuleList[i], command
|
||||||
|
}
|
||||||
|
return nil, Command{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, Command{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var runningModules []*Module
|
||||||
|
|
||||||
|
func ExecuteInit(module *Module, info CallbackInfo) {
|
||||||
|
if info.Command.BlockTerminate {
|
||||||
|
found := false
|
||||||
|
for _, n := range runningModules {
|
||||||
|
if n == module {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
runningModules = append(runningModules, module)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
module.InitCallback(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecuteComplete() {
|
||||||
|
for i := range runningModules {
|
||||||
|
(*runningModules[i]).CompleteCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShutdownAll() {
|
||||||
|
for i := range runningModules {
|
||||||
|
(*runningModules[i]).ShutdownCallback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExistsBlockingModule() bool {
|
||||||
|
return len(runningModules) != 0
|
||||||
|
}
|
||||||
|
1
modules/userInterface/module.go
Normal file
1
modules/userInterface/module.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package userInterface
|
195
modules/userInterface/userInterface.go
Normal file
195
modules/userInterface/userInterface.go
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
//go:build !noUserInterface
|
||||||
|
// +build !noUserInterface
|
||||||
|
|
||||||
|
package userInterface
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"pndpd/modules"
|
||||||
|
"pndpd/pndp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands := []modules.Command{{
|
||||||
|
CommandText: "proxy",
|
||||||
|
Description: "pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>",
|
||||||
|
BlockTerminate: true,
|
||||||
|
ConfigEnabled: true,
|
||||||
|
CommandLineEnabled: true,
|
||||||
|
}, {
|
||||||
|
CommandText: "responder",
|
||||||
|
Description: "pndpd responder <interface> <optional whitelist of CIDRs separated by a semicolon>",
|
||||||
|
BlockTerminate: true,
|
||||||
|
ConfigEnabled: true,
|
||||||
|
CommandLineEnabled: true,
|
||||||
|
}, {
|
||||||
|
CommandText: "modules",
|
||||||
|
Description: "pndpd modules available - list available modules",
|
||||||
|
BlockTerminate: false,
|
||||||
|
ConfigEnabled: false,
|
||||||
|
CommandLineEnabled: true,
|
||||||
|
}}
|
||||||
|
modules.RegisterModule("Core", commands, initCallback, completeCallback, shutdownCallback)
|
||||||
|
}
|
||||||
|
|
||||||
|
type configResponder struct {
|
||||||
|
Iface string
|
||||||
|
Filter string
|
||||||
|
autosense string
|
||||||
|
instance *pndp.ResponderObj
|
||||||
|
}
|
||||||
|
|
||||||
|
type configProxy struct {
|
||||||
|
Iface1 string
|
||||||
|
Iface2 string
|
||||||
|
Filter string
|
||||||
|
autosense string
|
||||||
|
instance *pndp.ProxyObj
|
||||||
|
}
|
||||||
|
|
||||||
|
var allResponders []*configResponder
|
||||||
|
var allProxies []*configProxy
|
||||||
|
|
||||||
|
func initCallback(callback modules.CallbackInfo) {
|
||||||
|
if callback.CallbackType == modules.CommandLine {
|
||||||
|
switch callback.Command.CommandText {
|
||||||
|
case "proxy":
|
||||||
|
if len(callback.Arguments) == 3 {
|
||||||
|
allProxies = append(allProxies, &configProxy{
|
||||||
|
Iface1: callback.Arguments[0],
|
||||||
|
Iface2: callback.Arguments[1],
|
||||||
|
Filter: callback.Arguments[2],
|
||||||
|
autosense: "",
|
||||||
|
instance: nil,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
allProxies = append(allProxies, &configProxy{
|
||||||
|
Iface1: callback.Arguments[0],
|
||||||
|
Iface2: callback.Arguments[1],
|
||||||
|
Filter: "",
|
||||||
|
autosense: "",
|
||||||
|
instance: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "responder":
|
||||||
|
if len(callback.Arguments) == 2 {
|
||||||
|
allResponders = append(allResponders, &configResponder{
|
||||||
|
Iface: callback.Arguments[0],
|
||||||
|
Filter: callback.Arguments[1],
|
||||||
|
autosense: "",
|
||||||
|
instance: nil,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
allResponders = append(allResponders, &configResponder{
|
||||||
|
Iface: callback.Arguments[0],
|
||||||
|
Filter: "",
|
||||||
|
autosense: "",
|
||||||
|
instance: nil,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case "modules":
|
||||||
|
if modules.ModuleList != nil {
|
||||||
|
fmt.Print("Available Modules: ")
|
||||||
|
for i := range modules.ModuleList {
|
||||||
|
fmt.Print((*modules.ModuleList[i]).Name + " ")
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
switch callback.Command.CommandText {
|
||||||
|
case "proxy":
|
||||||
|
obj := configProxy{}
|
||||||
|
filter := ""
|
||||||
|
for _, n := range callback.Arguments {
|
||||||
|
if strings.HasPrefix(n, "iface1") {
|
||||||
|
obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(n, "iface1"))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(n, "iface2") {
|
||||||
|
obj.Iface2 = strings.TrimSpace(strings.TrimPrefix(n, "iface2"))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(n, "filter") {
|
||||||
|
filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";"
|
||||||
|
if strings.Contains(n, ";") {
|
||||||
|
showError("config: the use of semicolons is not allowed in the filter arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(n, "autosense") {
|
||||||
|
obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense"))
|
||||||
|
}
|
||||||
|
if strings.Contains(n, "//") {
|
||||||
|
showError("config: comments are not allowed after arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.Filter = strings.TrimSuffix(filter, ";")
|
||||||
|
if obj.autosense != "" && obj.Filter != "" {
|
||||||
|
showError("config: cannot have both a filter and autosense enabled on a proxy object")
|
||||||
|
}
|
||||||
|
if obj.Iface2 == "" || obj.Iface1 == "" {
|
||||||
|
showError("config: two interfaces need to be specified in the config file for a proxy object. (iface1 and iface2 parameters)")
|
||||||
|
}
|
||||||
|
allProxies = append(allProxies, &obj)
|
||||||
|
case "responder":
|
||||||
|
obj := configResponder{}
|
||||||
|
filter := ""
|
||||||
|
for _, n := range callback.Arguments {
|
||||||
|
if strings.HasPrefix(n, "iface") {
|
||||||
|
obj.Iface = strings.TrimSpace(strings.TrimPrefix(n, "iface"))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(n, "filter") {
|
||||||
|
filter += strings.TrimSpace(strings.TrimPrefix(n, "filter")) + ";"
|
||||||
|
if strings.Contains(n, ";") {
|
||||||
|
showError("config: the use of semicolons is not allowed in the filter arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(n, "autosense") {
|
||||||
|
obj.autosense = strings.TrimSpace(strings.TrimPrefix(n, "autosense"))
|
||||||
|
}
|
||||||
|
if obj.autosense != "" && obj.Filter != "" {
|
||||||
|
showError("config: cannot have both a filter and autosense enabled on a responder object")
|
||||||
|
}
|
||||||
|
if obj.Iface == "" {
|
||||||
|
showError("config: interface not specified in the responder object. (iface parameter)")
|
||||||
|
}
|
||||||
|
if strings.Contains(n, "//") {
|
||||||
|
showError("config: comments are not allowed after arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.Filter = strings.TrimSuffix(filter, ";")
|
||||||
|
allResponders = append(allResponders, &obj)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func completeCallback() {
|
||||||
|
for _, n := range allProxies {
|
||||||
|
o := pndp.NewProxy(n.Iface1, n.Iface2, pndp.ParseFilter(n.Filter), n.autosense)
|
||||||
|
n.instance = o
|
||||||
|
o.Start()
|
||||||
|
}
|
||||||
|
for _, n := range allResponders {
|
||||||
|
o := pndp.NewResponder(n.Iface, pndp.ParseFilter(n.Filter), n.autosense)
|
||||||
|
n.instance = o
|
||||||
|
o.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func shutdownCallback() {
|
||||||
|
for _, n := range allProxies {
|
||||||
|
n.instance.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range allResponders {
|
||||||
|
n.instance.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func showError(error string) {
|
||||||
|
fmt.Println(error)
|
||||||
|
fmt.Println("Exiting due to error")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
@ -12,10 +12,8 @@ type ndpRequest struct {
|
|||||||
srcIP []byte
|
srcIP []byte
|
||||||
answeringForIP []byte
|
answeringForIP []byte
|
||||||
dstIP []byte
|
dstIP []byte
|
||||||
mac []byte
|
|
||||||
receivedIfaceMac []byte
|
|
||||||
sourceIface string
|
sourceIface string
|
||||||
rawPacket []byte
|
payload []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type ndpQuestion struct {
|
type ndpQuestion struct {
|
||||||
|
@ -35,7 +35,9 @@ type ProxyObj struct {
|
|||||||
// With the optional autosenseInterface argument, the whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently.
|
// With the optional autosenseInterface argument, the whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently.
|
||||||
// Start() must be called on the object to actually start responding
|
// Start() must be called on the object to actually start responding
|
||||||
func NewResponder(iface string, filter []*net.IPNet, autosenseInterface string) *ResponderObj {
|
func NewResponder(iface string, filter []*net.IPNet, autosenseInterface string) *ResponderObj {
|
||||||
|
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{}),
|
||||||
@ -57,7 +59,8 @@ func (obj *ResponderObj) start() {
|
|||||||
}()
|
}()
|
||||||
go respond(obj.iface, requests, ndp_ADV, nil, obj.filter, obj.autosense, obj.stopWG, obj.stopChan)
|
go respond(obj.iface, requests, ndp_ADV, nil, obj.filter, obj.autosense, obj.stopWG, obj.stopChan)
|
||||||
go listen(obj.iface, requests, ndp_SOL, obj.stopWG, obj.stopChan)
|
go listen(obj.iface, requests, ndp_SOL, obj.stopWG, obj.stopChan)
|
||||||
fmt.Println("Started responder instance on interface", obj.iface)
|
fmt.Printf("Started responder instance on interface %s", obj.iface)
|
||||||
|
fmt.Println()
|
||||||
<-obj.stopChan
|
<-obj.stopChan
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +131,8 @@ func (obj *ProxyObj) start() {
|
|||||||
go listen(obj.iface2, req_iface2_adv_iface1, ndp_ADV, obj.stopWG, obj.stopChan)
|
go listen(obj.iface2, req_iface2_adv_iface1, ndp_ADV, obj.stopWG, obj.stopChan)
|
||||||
go respond(obj.iface1, req_iface2_adv_iface1, ndp_ADV, out_iface2_sol_questions_iface1_adv, nil, "", obj.stopWG, obj.stopChan)
|
go respond(obj.iface1, req_iface2_adv_iface1, ndp_ADV, out_iface2_sol_questions_iface1_adv, nil, "", obj.stopWG, obj.stopChan)
|
||||||
|
|
||||||
fmt.Println("Started Proxy instance for interfaces:", obj.iface1, "and", obj.iface2)
|
fmt.Printf("Started Proxy instance on interfaces %s and %s (if enabled, the whitelist is applied on %s)", obj.iface1, obj.iface2, obj.iface2)
|
||||||
|
fmt.Println()
|
||||||
<-obj.stopChan
|
<-obj.stopChan
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +150,7 @@ func (obj *ProxyObj) Stop() bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist for SimpleRespond
|
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist
|
||||||
func ParseFilter(f string) []*net.IPNet {
|
func ParseFilter(f string) []*net.IPNet {
|
||||||
if f == "" {
|
if f == "" {
|
||||||
return nil
|
return nil
|
96
pndp/interface.go
Normal file
96
pndp/interface.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package pndp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/bpf"
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// bpfFilter represents a classic BPF filter program that can be applied to a socket
|
||||||
|
type bpfFilter []bpf.Instruction
|
||||||
|
|
||||||
|
// ApplyTo applies the current filter onto the provided file descriptor
|
||||||
|
func (filter bpfFilter) ApplyTo(fd int) (err error) {
|
||||||
|
var assembled []bpf.RawInstruction
|
||||||
|
if assembled, err = bpf.Assemble(filter); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var program = unix.SockFprog{
|
||||||
|
Len: uint16(len(assembled)),
|
||||||
|
Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])),
|
||||||
|
}
|
||||||
|
var b = (*[unix.SizeofSockFprog]byte)(unsafe.Pointer(&program))[:unix.SizeofSockFprog]
|
||||||
|
|
||||||
|
if _, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT,
|
||||||
|
uintptr(fd), uintptr(syscall.SOL_SOCKET), uintptr(syscall.SO_ATTACH_FILTER),
|
||||||
|
uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0); errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type iflags struct {
|
||||||
|
name [syscall.IFNAMSIZ]byte
|
||||||
|
flags uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPromisc(fd int, iface string, enable bool, withInterfaceFlags bool) {
|
||||||
|
//TODO re-test ALLMULTI
|
||||||
|
|
||||||
|
// -------------------------- Interface flags --------------------------
|
||||||
|
if withInterfaceFlags {
|
||||||
|
tFD, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ifl iflags
|
||||||
|
copy(ifl.name[:], []byte(iface))
|
||||||
|
_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(tFD), syscall.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifl)))
|
||||||
|
if ep != 0 {
|
||||||
|
panic(ep)
|
||||||
|
}
|
||||||
|
|
||||||
|
if enable {
|
||||||
|
ifl.flags |= uint16(syscall.IFF_PROMISC)
|
||||||
|
} else {
|
||||||
|
ifl.flags &^= uint16(syscall.IFF_PROMISC)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, ep = syscall.Syscall(syscall.SYS_IOCTL, uintptr(tFD), syscall.SIOCSIFFLAGS, uintptr(unsafe.Pointer(&ifl)))
|
||||||
|
if ep != 0 {
|
||||||
|
panic(ep)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = syscall.Close(tFD)
|
||||||
|
}
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
// -------------------------- Socket Options ---------------------------
|
||||||
|
iFace, err := net.InterfaceByName(iface)
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
mReq := unix.PacketMreq{
|
||||||
|
Ifindex: int32(iFace.Index),
|
||||||
|
Type: unix.PACKET_MR_PROMISC,
|
||||||
|
}
|
||||||
|
|
||||||
|
var opt int
|
||||||
|
if enable {
|
||||||
|
opt = unix.PACKET_ADD_MEMBERSHIP
|
||||||
|
} else {
|
||||||
|
opt = unix.PACKET_DROP_MEMBERSHIP
|
||||||
|
}
|
||||||
|
|
||||||
|
err = unix.SetsockoptPacketMreq(fd, unix.SOL_PACKET, opt, &mReq)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
// ---------------------------------------------------------------------
|
||||||
|
}
|
@ -4,38 +4,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"golang.org/x/net/bpf"
|
"golang.org/x/net/bpf"
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// bpfFilter represents a classic BPF filter program that can be applied to a socket
|
|
||||||
type bpfFilter []bpf.Instruction
|
|
||||||
|
|
||||||
// ApplyTo applies the current filter onto the provided file descriptor
|
|
||||||
func (filter bpfFilter) ApplyTo(fd int) (err error) {
|
|
||||||
var assembled []bpf.RawInstruction
|
|
||||||
if assembled, err = bpf.Assemble(filter); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var program = unix.SockFprog{
|
|
||||||
Len: uint16(len(assembled)),
|
|
||||||
Filter: (*unix.SockFilter)(unsafe.Pointer(&assembled[0])),
|
|
||||||
}
|
|
||||||
var b = (*[unix.SizeofSockFprog]byte)(unsafe.Pointer(&program))[:unix.SizeofSockFprog]
|
|
||||||
|
|
||||||
if _, _, errno := syscall.Syscall6(syscall.SYS_SETSOCKOPT,
|
|
||||||
uintptr(fd), uintptr(syscall.SOL_SOCKET), uintptr(syscall.SO_ATTACH_FILTER),
|
|
||||||
uintptr(unsafe.Pointer(&b[0])), uintptr(len(b)), 0); errno != 0 {
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Htons Convert a uint16 to host byte order (big endian)
|
// Htons Convert a uint16 to host byte order (big endian)
|
||||||
func htons(v uint16) int {
|
func htons(v uint16) int {
|
||||||
return int((v << 8) | (v >> 8))
|
return int((v << 8) | (v >> 8))
|
||||||
@ -61,6 +34,7 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
<-stopChan
|
<-stopChan
|
||||||
|
setPromisc(fd, iface, false, false)
|
||||||
_ = syscall.Close(fd)
|
_ = syscall.Close(fd)
|
||||||
stopWG.Done() // syscall.read does not release when the file descriptor is closed
|
stopWG.Done() // syscall.read does not release when the file descriptor is closed
|
||||||
}()
|
}()
|
||||||
@ -77,6 +51,8 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPromisc(fd, iface, true, false)
|
||||||
|
|
||||||
var protocolNo uint32
|
var protocolNo uint32
|
||||||
if requestType == ndp_SOL {
|
if requestType == ndp_SOL {
|
||||||
//Neighbor Solicitation
|
//Neighbor Solicitation
|
||||||
@ -99,8 +75,8 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
bpf.LoadAbsolute{Off: 54, Size: 1},
|
bpf.LoadAbsolute{Off: 54, Size: 1},
|
||||||
// Jump to the drop packet instruction if Type is not Neighbor Solicitation / Advertisement.
|
// Jump to the drop packet instruction if Type is not Neighbor Solicitation / Advertisement.
|
||||||
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1},
|
bpf.JumpIf{Cond: bpf.JumpNotEqual, Val: protocolNo, SkipTrue: 1},
|
||||||
// Verdict is: send up to 4096 bytes of the packet to userspace.
|
// Verdict is: send up to 86 bytes of the packet to userspace.
|
||||||
bpf.RetConstant{Val: 4096},
|
bpf.RetConstant{Val: 86},
|
||||||
// Verdict is: "ignore packet."
|
// Verdict is: "ignore packet."
|
||||||
bpf.RetConstant{Val: 0},
|
bpf.RetConstant{Val: 0},
|
||||||
}
|
}
|
||||||
@ -111,12 +87,12 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 86)
|
||||||
numRead, err := syscall.Read(fd, buf)
|
numRead, err := syscall.Read(fd, buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
if numRead < 86 {
|
if numRead < 78 {
|
||||||
if GlobalDebug {
|
if GlobalDebug {
|
||||||
fmt.Println("Dropping packet since it does not meet the minimum length requirement")
|
fmt.Println("Dropping packet since it does not meet the minimum length requirement")
|
||||||
fmt.Printf("% X\n", buf[:numRead])
|
fmt.Printf("% X\n", buf[:numRead])
|
||||||
@ -139,8 +115,6 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
fmt.Println("NDP Flags")
|
fmt.Println("NDP Flags")
|
||||||
fmt.Printf("% X\n", buf[58])
|
fmt.Printf("% X\n", buf[58])
|
||||||
}
|
}
|
||||||
fmt.Println("NDP MAC:")
|
|
||||||
fmt.Printf("% X\n", buf[80:86])
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,15 +125,22 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopW
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if requestType == ndp_ADV {
|
||||||
|
if buf[58] == 0x0 {
|
||||||
|
if GlobalDebug {
|
||||||
|
fmt.Println("Dropping Advertisement packet without any NDP flags set")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
responder <- &ndpRequest{
|
responder <- &ndpRequest{
|
||||||
requestType: requestType,
|
requestType: requestType,
|
||||||
srcIP: buf[22:38],
|
srcIP: buf[22:38],
|
||||||
dstIP: buf[38:54],
|
dstIP: buf[38:54],
|
||||||
answeringForIP: buf[62:78],
|
answeringForIP: buf[62:78],
|
||||||
mac: buf[80:86],
|
payload: buf[54:],
|
||||||
receivedIfaceMac: niface.HardwareAddr,
|
|
||||||
sourceIface: iface,
|
sourceIface: iface,
|
||||||
rawPacket: buf[:numRead],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,6 +6,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var emptyIpv6 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
var emptyIpv6 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||||
@ -134,7 +135,7 @@ func checksumAddition(b []byte) uint32 {
|
|||||||
for i := 0; i < len(b); i++ {
|
for i := 0; i < len(b); i++ {
|
||||||
if i%2 == 0 {
|
if i%2 == 0 {
|
||||||
if len(b)-1 == i {
|
if len(b)-1 == i {
|
||||||
sum += uint32(uint16(b[i])<<8 | uint16(0x0))
|
sum += uint32(uint16(b[i])<<8 | uint16(0x00))
|
||||||
} else {
|
} else {
|
||||||
sum += uint32(uint16(b[i])<<8 | uint16(b[i+1]))
|
sum += uint32(uint16(b[i])<<8 | uint16(b[i+1]))
|
||||||
}
|
}
|
||||||
@ -143,12 +144,7 @@ func checksumAddition(b []byte) uint32 {
|
|||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkPacketChecksum(scrip, dstip, payload []byte) bool {
|
func checkPacketChecksum(v6 *ipv6Header, payload []byte) bool {
|
||||||
v6, err := newIpv6Header(scrip, dstip)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
packetsum := make([]byte, 2)
|
packetsum := make([]byte, 2)
|
||||||
copy(packetsum, payload[2:4])
|
copy(packetsum, payload[2:4])
|
||||||
|
|
||||||
@ -162,9 +158,6 @@ func checkPacketChecksum(scrip, dstip, payload []byte) bool {
|
|||||||
bChecksum := make([]byte, 2)
|
bChecksum := make([]byte, 2)
|
||||||
binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload))
|
binary.BigEndian.PutUint16(bChecksum, calculateChecksum(v6, payload))
|
||||||
if bytes.Equal(packetsum, bChecksum) {
|
if bytes.Equal(packetsum, bChecksum) {
|
||||||
if GlobalDebug {
|
|
||||||
fmt.Println("Verified received packet checksum")
|
|
||||||
}
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
if GlobalDebug {
|
if GlobalDebug {
|
||||||
@ -176,8 +169,5 @@ func checkPacketChecksum(scrip, dstip, payload []byte) bool {
|
|||||||
|
|
||||||
func isIpv6(ip string) bool {
|
func isIpv6(ip string) bool {
|
||||||
rip := net.ParseIP(ip)
|
rip := net.ParseIP(ip)
|
||||||
if rip.To16() == nil {
|
return rip != nil && strings.Contains(ip, ":")
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQuestionChan chan *ndpQuestion, filter []*net.IPNet, autoSense string, stopWG *sync.WaitGroup, stopChan chan struct{}) {
|
func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQuestionChan chan *ndpQuestion, filter []*net.IPNet, autoSense string, stopWG *sync.WaitGroup, stopChan chan struct{}) {
|
||||||
var ndpQuestionsList = make([]*ndpQuestion, 0, 40)
|
|
||||||
stopWG.Add(1)
|
stopWG.Add(1)
|
||||||
defer stopWG.Done()
|
defer stopWG.Done()
|
||||||
|
|
||||||
|
var ndpQuestionsList = make([]*ndpQuestion, 0, 40)
|
||||||
|
var _, linkLocalSpace, _ = net.ParseCIDR("fe80::/10")
|
||||||
|
|
||||||
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -24,44 +27,23 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
nIface, err := net.InterfaceByName(iface)
|
respondIface, err := net.InterfaceByName(iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = emptyIpv6
|
var result = selectSourceIP(respondIface)
|
||||||
ifaceaddrs, err := nIface.Addrs()
|
|
||||||
|
|
||||||
for _, n := range ifaceaddrs {
|
|
||||||
tip, _, err := net.ParseCIDR(n.String())
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var haveUla = false
|
|
||||||
if isIpv6(tip.String()) {
|
|
||||||
if tip.IsGlobalUnicast() {
|
|
||||||
haveUla = true
|
|
||||||
result = tip
|
|
||||||
_, tnet, _ := net.ParseCIDR("fc00::/7")
|
|
||||||
if !tnet.Contains(tip) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if tip.IsLinkLocalUnicast() && !haveUla {
|
|
||||||
result = tip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
var n *ndpRequest
|
var req *ndpRequest
|
||||||
if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) {
|
if (ndpQuestionChan == nil && respondType == ndp_ADV) || (ndpQuestionChan != nil && respondType == ndp_SOL) {
|
||||||
select {
|
select {
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
return
|
return
|
||||||
case n = <-requests:
|
case req = <-requests:
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// THis is if ndpQuestionChan != nil && respondType == ndp_ADV
|
// This is if ndpQuestionChan != nil && respondType == ndp_ADV
|
||||||
select {
|
select {
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
return
|
return
|
||||||
@ -69,48 +51,30 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
ndpQuestionsList = append(ndpQuestionsList, q)
|
ndpQuestionsList = append(ndpQuestionsList, q)
|
||||||
ndpQuestionsList = cleanupQuestionList(ndpQuestionsList)
|
ndpQuestionsList = cleanupQuestionList(ndpQuestionsList)
|
||||||
continue
|
continue
|
||||||
case n = <-requests:
|
case req = <-requests:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _, LinkLocalSpace, _ = net.ParseCIDR("fe80::/10")
|
if linkLocalSpace.Contains(req.answeringForIP) {
|
||||||
if LinkLocalSpace.Contains(n.answeringForIP) {
|
|
||||||
if GlobalDebug {
|
if GlobalDebug {
|
||||||
fmt.Println("Dropping packet asking for a link-local IP")
|
fmt.Println("Dropping packet asking for a link-local IP")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.requestType == ndp_ADV {
|
v6Header, err := newIpv6Header(req.srcIP, req.dstIP)
|
||||||
if (n.rawPacket[78] != 0x02) || (n.rawPacket[79] != 0x01) {
|
if err != nil {
|
||||||
if GlobalDebug {
|
|
||||||
fmt.Println("Dropping Advertisement packet without target Source address set")
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if n.rawPacket[58] == 0x0 {
|
if !checkPacketChecksum(v6Header, req.payload) {
|
||||||
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)
|
||||||
@ -118,11 +82,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
autoifaceaddrs, err := autoiface.Addrs()
|
autoifaceaddrs, err := autoiface.Addrs()
|
||||||
|
|
||||||
for _, l := range autoifaceaddrs {
|
for _, l := range autoifaceaddrs {
|
||||||
_, anet, err := net.ParseCIDR(l.String())
|
testIP, anet, err := net.ParseCIDR(l.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if isIpv6(anet.String()) {
|
if isIpv6(testIP.String()) {
|
||||||
filter = append(filter, anet)
|
filter = append(filter, anet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,9 +95,9 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
if filter != nil {
|
if filter != nil {
|
||||||
ok := false
|
ok := false
|
||||||
for _, i := range filter {
|
for _, i := range filter {
|
||||||
if i.Contains(n.answeringForIP) {
|
if i.Contains(req.answeringForIP) {
|
||||||
if GlobalDebug {
|
if GlobalDebug {
|
||||||
fmt.Println("Responded for whitelisted IP", n.answeringForIP)
|
fmt.Println("Responded for whitelisted IP", req.answeringForIP)
|
||||||
}
|
}
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
@ -148,12 +112,12 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
fmt.Println("Getting ready to send packet of type", respondType, "out on interface", iface)
|
fmt.Println("Getting ready to send packet of type", respondType, "out on interface", iface)
|
||||||
}
|
}
|
||||||
|
|
||||||
if n.sourceIface == iface {
|
if req.sourceIface == iface {
|
||||||
pkt(fd, result, n.srcIP, n.answeringForIP, nIface.HardwareAddr, respondType)
|
pkt(fd, result, req.srcIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
|
||||||
} else {
|
} else {
|
||||||
if respondType == ndp_ADV {
|
if respondType == ndp_ADV {
|
||||||
success := false
|
success := false
|
||||||
n.dstIP, success = getAddressFromQuestionListRetry(n.answeringForIP, ndpQuestionChan, ndpQuestionsList)
|
req.dstIP, success = getAddressFromQuestionListRetry(req.answeringForIP, ndpQuestionChan, ndpQuestionsList)
|
||||||
if !success {
|
if !success {
|
||||||
if GlobalDebug {
|
if GlobalDebug {
|
||||||
fmt.Println("Nobody has asked for this IP")
|
fmt.Println("Nobody has asked for this IP")
|
||||||
@ -162,11 +126,11 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, ndpQu
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ndpQuestionChan <- &ndpQuestion{
|
ndpQuestionChan <- &ndpQuestion{
|
||||||
targetIP: n.answeringForIP,
|
targetIP: req.answeringForIP,
|
||||||
askedBy: n.srcIP,
|
askedBy: req.srcIP,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pkt(fd, result, n.dstIP, n.answeringForIP, nIface.HardwareAddr, respondType)
|
pkt(fd, result, req.dstIP, req.answeringForIP, respondIface.HardwareAddr, respondType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,18 +171,20 @@ func getAddressFromQuestionListRetry(targetIP []byte, ndpQuestionChan chan *ndpQ
|
|||||||
if success {
|
if success {
|
||||||
return result, true
|
return result, true
|
||||||
}
|
}
|
||||||
forloop:
|
|
||||||
for {
|
gotBuffered := false
|
||||||
select {
|
select {
|
||||||
case q := <-ndpQuestionChan:
|
case q := <-ndpQuestionChan:
|
||||||
ndpQuestionsList = append(ndpQuestionsList, q)
|
ndpQuestionsList = append(ndpQuestionsList, q)
|
||||||
|
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) {
|
||||||
@ -242,3 +208,33 @@ func cleanupQuestionList(s []*ndpQuestion) []*ndpQuestion {
|
|||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func selectSourceIP(iface *net.Interface) []byte {
|
||||||
|
var _, ulaSpace, _ = net.ParseCIDR("fc00::/7")
|
||||||
|
var result = emptyIpv6
|
||||||
|
ifaceaddrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, n := range ifaceaddrs {
|
||||||
|
tip, _, err := net.ParseCIDR(n.String())
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
var haveUla = false
|
||||||
|
if isIpv6(tip.String()) {
|
||||||
|
if tip.IsGlobalUnicast() {
|
||||||
|
haveUla = true
|
||||||
|
result = tip
|
||||||
|
|
||||||
|
if !ulaSpace.Contains(tip) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if tip.IsLinkLocalUnicast() && !haveUla {
|
||||||
|
result = tip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
debug off
|
debug off
|
||||||
|
|
||||||
// Responder example
|
// Responder example
|
||||||
|
// Create an NDP responder that answers on interface "eth0"
|
||||||
responder {
|
responder {
|
||||||
iface eth0
|
iface eth0
|
||||||
filter fd01::/64
|
filter fd01::/64
|
||||||
@ -12,6 +13,7 @@ responder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Proxy example
|
// Proxy example
|
||||||
|
// Create an NDP proxy for proxying NDP between iface1 ("eth0") and iface2 ("eth1")
|
||||||
// The whitelist is applied on iface2
|
// The whitelist is applied on iface2
|
||||||
proxy {
|
proxy {
|
||||||
iface1 eth0
|
iface1 eth0
|
||||||
|
@ -7,11 +7,11 @@ After=network.target network-online.target
|
|||||||
Type=simple
|
Type=simple
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5s
|
RestartSec=5s
|
||||||
ExecStart=/usr/bin/pndpd config /etc/pndpd/pndpd.conf
|
ExecStart=/usr/local/bin/pndpd config /etc/pndpd/pndpd.conf
|
||||||
|
|
||||||
DynamicUser=yes
|
DynamicUser=yes
|
||||||
AmbientCapabilities=CAP_NET_RAW
|
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
|
||||||
CapabilityBoundingSet=
|
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
|
||||||
ProtectHome=yes
|
ProtectHome=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user