Add autosense, add config, overhaul code
This commit is contained in:
parent
04c3526c8b
commit
282205f743
19
README.md
19
README.md
@ -3,15 +3,17 @@
|
|||||||
- 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
|
- Proxy NDP between interfaces with an optional whitelist for neighbor solicitations
|
||||||
|
- Optionally automatically determine whitelist based on the IPs assigned to the interfaces
|
||||||
- Permissions required: root or CAP_NET_RAW
|
- Permissions required: root or CAP_NET_RAW
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
````
|
````
|
||||||
pndpd readconfig <path to file>
|
pndpd config <path to file>")
|
||||||
pndpd respond <interface> <optional whitelist of CIDRs separated with a semicolon>
|
pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>")
|
||||||
pndpd proxy <interface1> <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).
|
||||||
|
|
||||||
### Developing
|
### Developing
|
||||||
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:
|
||||||
@ -19,11 +21,14 @@ It is easy to add functionality to PNDPD. For additions outside the core functio
|
|||||||
package main
|
package main
|
||||||
import "pndpd/pndp"
|
import "pndpd/pndp"
|
||||||
|
|
||||||
pndp.SimpleRespond(iface string, filter []*net.IPNet)
|
|
||||||
pndp.ParseFilter(f string) []*net.IPNet
|
pndp.ParseFilter(f string) []*net.IPNet
|
||||||
|
|
||||||
pndp.Proxy(iface1, iface2 string)
|
responderInstance := pndp.NewResponder(iface string, filter []*net.IPNet, autosenseInterface string)
|
||||||
|
responderInstance.Start()
|
||||||
|
responderInstance.Stop()
|
||||||
|
|
||||||
pndp.WaitForSignal()
|
proxyInstance := pndp.NewProxy(iface1 string, iface2 string, filter []*net.IPNet, autosenseInterface string)
|
||||||
|
proxyInstance.Start()
|
||||||
|
proxyInstance.Stop()
|
||||||
````
|
````
|
||||||
Pull request are welcome for any functionality you add.
|
Pull request are welcome for any functionality you add.
|
72
config.go
72
config.go
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"pndpd/pndp"
|
"pndpd/pndp"
|
||||||
@ -11,13 +12,21 @@ import (
|
|||||||
type configResponder struct {
|
type configResponder struct {
|
||||||
Iface string
|
Iface string
|
||||||
Filter string
|
Filter string
|
||||||
|
autosense string
|
||||||
|
instance *pndp.ResponderObj
|
||||||
}
|
}
|
||||||
|
|
||||||
type configProxy struct {
|
type configProxy struct {
|
||||||
Iface1 string
|
Iface1 string
|
||||||
Iface2 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 {
|
||||||
@ -33,50 +42,95 @@ func readConfig(dest string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "debug") {
|
if strings.HasPrefix(line, "debug") {
|
||||||
if strings.Contains(line, "off") {
|
if strings.Contains(line, "on") {
|
||||||
pndp.GlobalDebug = false
|
pndp.GlobalDebug = true
|
||||||
|
fmt.Println("DEBUG ON")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "responder") {
|
if strings.HasPrefix(line, "responder") && strings.Contains(line, "{") {
|
||||||
obj := configResponder{}
|
obj := configResponder{}
|
||||||
filter := ""
|
filter := ""
|
||||||
for {
|
for {
|
||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
line = scanner.Text()
|
line = strings.TrimSpace(scanner.Text())
|
||||||
if strings.HasPrefix(line, "iface") {
|
if strings.HasPrefix(line, "iface") {
|
||||||
obj.Iface = strings.TrimSpace(strings.TrimPrefix(line, "iface"))
|
obj.Iface = strings.TrimSpace(strings.TrimPrefix(line, "iface"))
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "filter") {
|
if strings.HasPrefix(line, "filter") {
|
||||||
filter += strings.TrimSpace(strings.TrimPrefix(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, "}") {
|
if strings.HasPrefix(line, "}") {
|
||||||
obj.Filter = filter
|
obj.Filter = strings.TrimSuffix(filter, ";")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pndp.SimpleRespond(obj.Iface, pndp.ParseFilter(obj.Filter))
|
|
||||||
|
allResponders = append(allResponders, &obj)
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "proxy") {
|
if strings.HasPrefix(line, "proxy") && strings.Contains(line, "{") {
|
||||||
obj := configProxy{}
|
obj := configProxy{}
|
||||||
|
filter := ""
|
||||||
for {
|
for {
|
||||||
scanner.Scan()
|
scanner.Scan()
|
||||||
line = scanner.Text()
|
line = strings.TrimSpace(scanner.Text())
|
||||||
if strings.HasPrefix(line, "iface1") {
|
if strings.HasPrefix(line, "iface1") {
|
||||||
obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(line, "iface1"))
|
obj.Iface1 = strings.TrimSpace(strings.TrimPrefix(line, "iface1"))
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(line, "iface2") {
|
if strings.HasPrefix(line, "iface2") {
|
||||||
obj.Iface2 = strings.TrimSpace(strings.TrimPrefix(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, "}") {
|
if strings.HasPrefix(line, "}") {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pndp.Proxy(obj.Iface1, obj.Iface2)
|
allProxies = append(allProxies, &obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
if err := scanner.Err(); err != nil {
|
||||||
log.Fatal(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()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
45
main.go
45
main.go
@ -3,38 +3,61 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
"pndpd/pndp"
|
"pndpd/pndp"
|
||||||
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 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 0.4 by Kioubit")
|
fmt.Println("PNDPD Version 0.5 - Kioubit 2021")
|
||||||
|
|
||||||
if len(os.Args) <= 2 {
|
if len(os.Args) <= 2 {
|
||||||
printUsage()
|
printUsage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch os.Args[1] {
|
switch os.Args[1] {
|
||||||
case "respond":
|
case "respond":
|
||||||
|
var r *pndp.ResponderObj
|
||||||
if len(os.Args) == 4 {
|
if len(os.Args) == 4 {
|
||||||
pndp.SimpleRespond(os.Args[2], pndp.ParseFilter(os.Args[3]))
|
r = pndp.NewResponder(os.Args[2], pndp.ParseFilter(os.Args[3]), "")
|
||||||
|
r.Start()
|
||||||
} else {
|
} else {
|
||||||
pndp.SimpleRespond(os.Args[2], nil)
|
r = pndp.NewResponder(os.Args[2], nil, "")
|
||||||
|
fmt.Println("WARNING: You should use a whitelist unless you know what you are doing")
|
||||||
|
r.Start()
|
||||||
}
|
}
|
||||||
|
WaitForSignal()
|
||||||
|
r.Stop()
|
||||||
case "proxy":
|
case "proxy":
|
||||||
pndp.Proxy(os.Args[2], os.Args[3])
|
var p *pndp.ProxyObj
|
||||||
case "readconfig":
|
if len(os.Args) == 5 {
|
||||||
|
p = pndp.NewProxy(os.Args[2], os.Args[3], pndp.ParseFilter(os.Args[4]), "")
|
||||||
|
} else {
|
||||||
|
p = pndp.NewProxy(os.Args[2], os.Args[3], nil, "")
|
||||||
|
}
|
||||||
|
WaitForSignal()
|
||||||
|
p.Stop()
|
||||||
|
case "config":
|
||||||
readConfig(os.Args[2])
|
readConfig(os.Args[2])
|
||||||
default:
|
default:
|
||||||
printUsage()
|
printUsage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pndp.WaitForSignal()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func printUsage() {
|
func printUsage() {
|
||||||
fmt.Println("Specify command")
|
fmt.Println("Usage:")
|
||||||
fmt.Println("Usage: pndpd readconfig <path to file>")
|
fmt.Println("pndpd config <path to file>")
|
||||||
fmt.Println("Usage: pndpd respond <interface> <optional whitelist of CIDRs separated with a semicolon>")
|
fmt.Println("pndpd respond <interface> <optional whitelist of CIDRs separated by a semicolon>")
|
||||||
fmt.Println("Usage: pndpd proxy <interface1> <interface2>")
|
fmt.Println("pndpd proxy <interface1> <interface2> <optional whitelist of CIDRs separated by a semicolon applied to interface2>")
|
||||||
|
fmt.Println("More options and additional documentation in the example config file")
|
||||||
}
|
}
|
||||||
|
231
pndp/process.go
231
pndp/process.go
@ -3,43 +3,157 @@ package pndp
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"runtime/pprof"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var GlobalDebug = false
|
var GlobalDebug = false
|
||||||
|
|
||||||
// Items needed for graceful shutdown
|
type ResponderObj struct {
|
||||||
var stop = make(chan struct{})
|
stopChan chan struct{}
|
||||||
var stopWg sync.WaitGroup
|
stopWG *sync.WaitGroup
|
||||||
var sigCh = make(chan os.Signal)
|
iface string
|
||||||
|
filter []*net.IPNet
|
||||||
// WaitForSignal Waits (blocking) for the program to be interrupted by the OS and then gracefully shuts down releasing all resources
|
autosense string
|
||||||
func WaitForSignal() {
|
}
|
||||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
type ProxyObj struct {
|
||||||
<-sigCh
|
stopChan chan struct{}
|
||||||
Shutdown()
|
stopWG *sync.WaitGroup
|
||||||
|
iface1 string
|
||||||
|
iface2 string
|
||||||
|
filter []*net.IPNet
|
||||||
|
autosense string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown Exits the program gracefully and releases all resources
|
// NewResponder
|
||||||
//
|
//
|
||||||
//Do not use with WaitForSignal
|
// iface - The interface to listen to and respond from
|
||||||
func Shutdown() {
|
//
|
||||||
fmt.Println("Shutting down...")
|
// filter - Optional (can be nil) list of CIDRs to whitelist. Must be IPV6! ParseFilter verifies ipv6
|
||||||
close(stop)
|
//
|
||||||
if wgWaitTimout(&stopWg, 10*time.Second) {
|
// 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.
|
||||||
fmt.Println("Done")
|
// Start() must be called on the object to actually start responding
|
||||||
} else {
|
func NewResponder(iface string, filter []*net.IPNet, autosenseInterface string) *ResponderObj {
|
||||||
fmt.Println("Aborting shutdown, since it is taking too long")
|
var s sync.WaitGroup
|
||||||
pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
|
return &ResponderObj{
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
stopWG: &s,
|
||||||
|
iface: iface,
|
||||||
|
filter: filter,
|
||||||
|
autosense: autosenseInterface,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
func (obj *ResponderObj) Start() {
|
||||||
|
go obj.start()
|
||||||
|
}
|
||||||
|
func (obj *ResponderObj) start() {
|
||||||
|
obj.stopWG.Add(1)
|
||||||
|
requests := make(chan *ndpRequest, 100)
|
||||||
|
defer func() {
|
||||||
|
close(requests)
|
||||||
|
obj.stopWG.Done()
|
||||||
|
}()
|
||||||
|
go respond(obj.iface, requests, ndp_ADV, obj.filter, obj.autosense, obj.stopWG, obj.stopChan)
|
||||||
|
go listen(obj.iface, requests, ndp_SOL, obj.stopWG, obj.stopChan)
|
||||||
|
fmt.Println("Started responder instance")
|
||||||
|
<-obj.stopChan
|
||||||
|
}
|
||||||
|
|
||||||
os.Exit(0)
|
//Stop a running Responder instance
|
||||||
|
// Returns false on success
|
||||||
|
func (obj *ResponderObj) Stop() bool {
|
||||||
|
close(obj.stopChan)
|
||||||
|
fmt.Println("Shutting down responder instance..")
|
||||||
|
if wgWaitTimout(obj.stopWG, 10*time.Second) {
|
||||||
|
fmt.Println("Done")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error shutting down instance")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProxy Proxy NDP between interfaces iface1 and iface2 with an optional filter (whitelist)
|
||||||
|
//
|
||||||
|
// filter - Optional (can be nil) list of CIDRs to whitelist. Must be IPV6! ParseFilter verifies ipv6
|
||||||
|
//
|
||||||
|
// 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 proxying
|
||||||
|
func NewProxy(iface1 string, iface2 string, filter []*net.IPNet, autosenseInterface string) *ProxyObj {
|
||||||
|
var s sync.WaitGroup
|
||||||
|
return &ProxyObj{
|
||||||
|
stopChan: make(chan struct{}),
|
||||||
|
stopWG: &s,
|
||||||
|
iface1: iface1,
|
||||||
|
iface2: iface2,
|
||||||
|
filter: filter,
|
||||||
|
autosense: autosenseInterface,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *ProxyObj) Start() {
|
||||||
|
go obj.start()
|
||||||
|
}
|
||||||
|
func (obj *ProxyObj) start() {
|
||||||
|
obj.stopWG.Add(1)
|
||||||
|
defer func() {
|
||||||
|
obj.stopWG.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
req_iface1_sol_iface2 := make(chan *ndpRequest, 100)
|
||||||
|
defer close(req_iface1_sol_iface2)
|
||||||
|
go listen(obj.iface1, req_iface1_sol_iface2, ndp_SOL, obj.stopWG, obj.stopChan)
|
||||||
|
go respond(obj.iface2, req_iface1_sol_iface2, ndp_SOL, obj.filter, obj.autosense, obj.stopWG, obj.stopChan)
|
||||||
|
|
||||||
|
req_iface2_sol_iface1 := make(chan *ndpRequest, 100)
|
||||||
|
defer close(req_iface2_sol_iface1)
|
||||||
|
go listen(obj.iface2, req_iface2_sol_iface1, ndp_SOL, obj.stopWG, obj.stopChan)
|
||||||
|
go respond(obj.iface1, req_iface2_sol_iface1, ndp_SOL, nil, "", obj.stopWG, obj.stopChan)
|
||||||
|
|
||||||
|
req_iface1_adv_iface2 := make(chan *ndpRequest, 100)
|
||||||
|
defer close(req_iface1_adv_iface2)
|
||||||
|
go listen(obj.iface1, req_iface1_adv_iface2, ndp_ADV, obj.stopWG, obj.stopChan)
|
||||||
|
go respond(obj.iface2, req_iface1_adv_iface2, ndp_ADV, nil, "", obj.stopWG, obj.stopChan)
|
||||||
|
|
||||||
|
req_iface2_adv_iface1 := make(chan *ndpRequest, 100)
|
||||||
|
defer close(req_iface2_adv_iface1)
|
||||||
|
go listen(obj.iface2, req_iface2_adv_iface1, ndp_ADV, obj.stopWG, obj.stopChan)
|
||||||
|
go respond(obj.iface1, req_iface2_adv_iface1, ndp_ADV, nil, "", obj.stopWG, obj.stopChan)
|
||||||
|
|
||||||
|
<-obj.stopChan
|
||||||
|
}
|
||||||
|
|
||||||
|
//Stop a running Proxy instance
|
||||||
|
// Returns false on success
|
||||||
|
func (obj *ProxyObj) Stop() bool {
|
||||||
|
close(obj.stopChan)
|
||||||
|
fmt.Println("Shutting down proxy instance..")
|
||||||
|
if wgWaitTimout(obj.stopWG, 10*time.Second) {
|
||||||
|
fmt.Println("Done")
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
fmt.Println("Error shutting down instance")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist for SimpleRespond
|
||||||
|
func ParseFilter(f string) []*net.IPNet {
|
||||||
|
if f == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := strings.Split(f, ";")
|
||||||
|
result := make([]*net.IPNet, len(s))
|
||||||
|
for i, n := range s {
|
||||||
|
_, cidr, err := net.ParseCIDR(n)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
result[i] = cidr
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func wgWaitTimout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
func wgWaitTimout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
||||||
@ -55,70 +169,3 @@ func wgWaitTimout(wg *sync.WaitGroup, timeout time.Duration) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SimpleRespond (Non blocking)
|
|
||||||
//
|
|
||||||
// iface - The interface to listen to and respond from
|
|
||||||
//
|
|
||||||
// filter - Optional (can be nil) list of CIDRs to whitelist. Must be IPV6!
|
|
||||||
// ParseFilter verifies ipv6
|
|
||||||
func SimpleRespond(iface string, filter []*net.IPNet) {
|
|
||||||
go simpleRespond(iface, filter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func simpleRespond(iface string, filter []*net.IPNet) {
|
|
||||||
defer stopWg.Done()
|
|
||||||
stopWg.Add(3) // This function, 2x goroutines
|
|
||||||
requests := make(chan *ndpRequest, 100)
|
|
||||||
defer close(requests)
|
|
||||||
go respond(iface, requests, ndp_ADV, filter)
|
|
||||||
go listen(iface, requests, ndp_SOL)
|
|
||||||
<-stop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxy NDP between interfaces iface1 and iface2
|
|
||||||
//
|
|
||||||
// Non blocking
|
|
||||||
func Proxy(iface1, iface2 string) {
|
|
||||||
go proxy(iface1, iface2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func proxy(iface1, iface2 string) {
|
|
||||||
defer stopWg.Done()
|
|
||||||
stopWg.Add(9) // This function, 8x goroutines
|
|
||||||
|
|
||||||
req_iface1_sol_iface2 := make(chan *ndpRequest, 100)
|
|
||||||
defer close(req_iface1_sol_iface2)
|
|
||||||
go listen(iface1, req_iface1_sol_iface2, ndp_SOL)
|
|
||||||
go respond(iface2, req_iface1_sol_iface2, ndp_SOL, nil)
|
|
||||||
|
|
||||||
req_iface2_sol_iface1 := make(chan *ndpRequest, 100)
|
|
||||||
defer close(req_iface2_sol_iface1)
|
|
||||||
go listen(iface2, req_iface2_sol_iface1, ndp_SOL)
|
|
||||||
go respond(iface1, req_iface2_sol_iface1, ndp_SOL, nil)
|
|
||||||
|
|
||||||
req_iface1_adv_iface2 := make(chan *ndpRequest, 100)
|
|
||||||
defer close(req_iface1_adv_iface2)
|
|
||||||
go listen(iface1, req_iface1_adv_iface2, ndp_ADV)
|
|
||||||
go respond(iface2, req_iface1_adv_iface2, ndp_ADV, nil)
|
|
||||||
|
|
||||||
req_iface2_adv_iface1 := make(chan *ndpRequest, 100)
|
|
||||||
defer close(req_iface2_adv_iface1)
|
|
||||||
go listen(iface2, req_iface2_adv_iface1, ndp_ADV)
|
|
||||||
go respond(iface1, req_iface2_adv_iface1, ndp_ADV, nil)
|
|
||||||
<-stop
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseFilter Helper Function to Parse a string of CIDRs separated by a semicolon as a Whitelist for SimpleRespond
|
|
||||||
func ParseFilter(f string) []*net.IPNet {
|
|
||||||
s := strings.Split(f, ";")
|
|
||||||
result := make([]*net.IPNet, len(s))
|
|
||||||
for i, n := range s {
|
|
||||||
_, cidr, err := net.ParseCIDR(n)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
result[i] = cidr
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"golang.org/x/net/bpf"
|
"golang.org/x/net/bpf"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
@ -40,7 +41,9 @@ func htons(v uint16) int {
|
|||||||
}
|
}
|
||||||
func htons16(v uint16) uint16 { return v<<8 | v>>8 }
|
func htons16(v uint16) uint16 { return v<<8 | v>>8 }
|
||||||
|
|
||||||
func listen(iface string, responder chan *ndpRequest, requestType ndpType) {
|
func listen(iface string, responder chan *ndpRequest, requestType ndpType, stopWG *sync.WaitGroup, stopChan chan struct{}) {
|
||||||
|
stopWG.Add(1)
|
||||||
|
defer stopWG.Done()
|
||||||
niface, err := net.InterfaceByName(iface)
|
niface, err := net.InterfaceByName(iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err.Error())
|
panic(err.Error())
|
||||||
@ -55,9 +58,9 @@ func listen(iface string, responder chan *ndpRequest, requestType ndpType) {
|
|||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
<-stop
|
<-stopChan
|
||||||
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
|
||||||
}()
|
}()
|
||||||
fmt.Println("Obtained fd ", fd)
|
fmt.Println("Obtained fd ", fd)
|
||||||
|
|
||||||
|
@ -4,19 +4,18 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
var globalFd int
|
func respond(iface string, requests chan *ndpRequest, respondType ndpType, filter []*net.IPNet, autoSense string, stopWG *sync.WaitGroup, stopChan chan struct{}) {
|
||||||
|
stopWG.Add(1)
|
||||||
func respond(iface string, requests chan *ndpRequest, respondType ndpType, filter []*net.IPNet) {
|
defer stopWG.Done()
|
||||||
defer stopWg.Done()
|
|
||||||
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
fd, err := syscall.Socket(syscall.AF_INET6, syscall.SOCK_RAW, syscall.IPPROTO_RAW)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer syscall.Close(globalFd)
|
defer syscall.Close(fd)
|
||||||
globalFd = fd
|
|
||||||
err = syscall.BindToDevice(fd, iface)
|
err = syscall.BindToDevice(fd, iface)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -49,16 +48,36 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, filte
|
|||||||
for {
|
for {
|
||||||
var n *ndpRequest
|
var n *ndpRequest
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-stopChan:
|
||||||
return
|
return
|
||||||
case n = <-requests:
|
case n = <-requests:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if autoSense != "" {
|
||||||
|
autoiface, err := net.InterfaceByName(autoSense)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
autoifaceaddrs, err := autoiface.Addrs()
|
||||||
|
|
||||||
|
for _, n := range autoifaceaddrs {
|
||||||
|
_, anet, err := net.ParseCIDR(n.String())
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if isIpv6(anet.String()) {
|
||||||
|
filter = append(filter, anet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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(n.answeringForIP) {
|
||||||
fmt.Println("filter allowed IP", n.answeringForIP)
|
if GlobalDebug {
|
||||||
|
fmt.Println("Responded for whitelisted IP", n.answeringForIP)
|
||||||
|
}
|
||||||
ok = true
|
ok = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -69,16 +88,16 @@ func respond(iface string, requests chan *ndpRequest, respondType ndpType, filte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if n.sourceIface == iface {
|
if n.sourceIface == iface {
|
||||||
pkt(result, n.srcIP, n.answeringForIP, niface.HardwareAddr, respondType)
|
pkt(fd, result, n.srcIP, n.answeringForIP, niface.HardwareAddr, respondType)
|
||||||
} else {
|
} else {
|
||||||
if !bytes.Equal(n.mac, n.receivedIfaceMac) {
|
if !bytes.Equal(n.mac, n.receivedIfaceMac) {
|
||||||
pkt(n.srcIP, n.dstIP, n.answeringForIP, niface.HardwareAddr, respondType)
|
pkt(fd, n.srcIP, n.dstIP, n.answeringForIP, niface.HardwareAddr, respondType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func pkt(ownIP []byte, dstIP []byte, tgtip []byte, mac []byte, respondType ndpType) {
|
func pkt(fd int, ownIP []byte, dstIP []byte, tgtip []byte, mac []byte, respondType ndpType) {
|
||||||
v6, err := newIpv6Header(ownIP, dstIP)
|
v6, err := newIpv6Header(ownIP, dstIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
@ -101,7 +120,7 @@ func pkt(ownIP []byte, dstIP []byte, tgtip []byte, mac []byte, respondType ndpTy
|
|||||||
fmt.Println("Sending packet of type", respondType, "to")
|
fmt.Println("Sending packet of type", respondType, "to")
|
||||||
fmt.Printf("% X\n", t)
|
fmt.Printf("% X\n", t)
|
||||||
}
|
}
|
||||||
err = syscall.Sendto(globalFd, response, 0, &d)
|
err = syscall.Sendto(fd, response, 0, &d)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err.Error())
|
fmt.Println(err.Error())
|
||||||
}
|
}
|
||||||
|
37
pndpd.conf
Normal file
37
pndpd.conf
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Example config file for PNDPD
|
||||||
|
|
||||||
|
// Enable or disable debug
|
||||||
|
// If enabled, this option can fill up your logfiles very quickly
|
||||||
|
debug off
|
||||||
|
|
||||||
|
// Responder example
|
||||||
|
responder {
|
||||||
|
iface eth0
|
||||||
|
filter fd01::/64
|
||||||
|
filter fd02::/64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy example
|
||||||
|
// The whitelist is applied on iface2
|
||||||
|
proxy {
|
||||||
|
iface1 eth0
|
||||||
|
iface2 eth1
|
||||||
|
filter fd01::/64
|
||||||
|
filter fd02::/64
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Responder example with autoconfigured whitelist
|
||||||
|
// The whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently.
|
||||||
|
responder {
|
||||||
|
iface eth0
|
||||||
|
autosense eth0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxy example with autoconfigured whitelist
|
||||||
|
// The whitelist is configured based on the addresses assigned to the interface specified. This works even if the IP addresses change frequently.
|
||||||
|
proxy {
|
||||||
|
iface1 eth0
|
||||||
|
iface2 eth1
|
||||||
|
autosense eth1
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user