222 lines
6.1 KiB
Go
222 lines
6.1 KiB
Go
|
// +build e2e_testing
|
||
|
|
||
|
package router
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"strconv"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/slackhq/nebula"
|
||
|
)
|
||
|
|
||
|
type R struct {
|
||
|
// Simple map of the ip:port registered on a control to the control
|
||
|
// Basically a router, right?
|
||
|
controls map[string]*nebula.Control
|
||
|
|
||
|
// A map for inbound packets for a control that doesn't know about this address
|
||
|
inNat map[string]*nebula.Control
|
||
|
|
||
|
// A last used map, if an inbound packet hit the inNat map then
|
||
|
// all return packets should use the same last used inbound address for the outbound sender
|
||
|
// map[from address + ":" + to address] => ip:port to rewrite in the udp packet to receiver
|
||
|
outNat map[string]net.UDPAddr
|
||
|
|
||
|
// All interactions are locked to help serialize behavior
|
||
|
sync.Mutex
|
||
|
}
|
||
|
|
||
|
type exitType int
|
||
|
|
||
|
const (
|
||
|
// Keeps routing, the function will get called again on the next packet
|
||
|
keepRouting exitType = 0
|
||
|
// Does not route this packet and exits immediately
|
||
|
exitNow exitType = 1
|
||
|
// Routes this packet and exits immediately afterwards
|
||
|
routeAndExit exitType = 2
|
||
|
)
|
||
|
|
||
|
type ExitFunc func(packet *nebula.UdpPacket, receiver *nebula.Control) exitType
|
||
|
|
||
|
func NewR(controls ...*nebula.Control) *R {
|
||
|
r := &R{
|
||
|
controls: make(map[string]*nebula.Control),
|
||
|
inNat: make(map[string]*nebula.Control),
|
||
|
outNat: make(map[string]net.UDPAddr),
|
||
|
}
|
||
|
|
||
|
for _, c := range controls {
|
||
|
addr := c.GetUDPAddr()
|
||
|
if _, ok := r.controls[addr]; ok {
|
||
|
panic("Duplicate listen address: " + addr)
|
||
|
}
|
||
|
r.controls[addr] = c
|
||
|
}
|
||
|
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// AddRoute will place the nebula controller at the ip and port specified.
|
||
|
// It does not look at the addr attached to the instance.
|
||
|
// If a route is used, this will behave like a NAT for the return path.
|
||
|
// Rewriting the source ip:port to what was last sent to from the origin
|
||
|
func (r *R) AddRoute(ip net.IP, port uint16, c *nebula.Control) {
|
||
|
r.Lock()
|
||
|
defer r.Unlock()
|
||
|
|
||
|
inAddr := net.JoinHostPort(ip.String(), fmt.Sprintf("%v", port))
|
||
|
if _, ok := r.inNat[inAddr]; ok {
|
||
|
panic("Duplicate listen address inNat: " + inAddr)
|
||
|
}
|
||
|
r.inNat[inAddr] = c
|
||
|
}
|
||
|
|
||
|
// OnceFrom will route a single packet from sender then return
|
||
|
// If the router doesn't have the nebula controller for that address, we panic
|
||
|
func (r *R) OnceFrom(sender *nebula.Control) {
|
||
|
r.RouteExitFunc(sender, func(*nebula.UdpPacket, *nebula.Control) exitType {
|
||
|
return routeAndExit
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// RouteUntilTxTun will route for sender and return when a packet is seen on receivers tun
|
||
|
// If the router doesn't have the nebula controller for that address, we panic
|
||
|
func (r *R) RouteUntilTxTun(sender *nebula.Control, receiver *nebula.Control) []byte {
|
||
|
tunTx := receiver.GetTunTxChan()
|
||
|
udpTx := sender.GetUDPTxChan()
|
||
|
|
||
|
for {
|
||
|
select {
|
||
|
// Maybe we already have something on the tun for us
|
||
|
case b := <-tunTx:
|
||
|
return b
|
||
|
|
||
|
// Nope, lets push the sender along
|
||
|
case p := <-udpTx:
|
||
|
outAddr := sender.GetUDPAddr()
|
||
|
r.Lock()
|
||
|
inAddr := net.JoinHostPort(p.ToIp.String(), fmt.Sprintf("%v", p.ToPort))
|
||
|
c := r.getControl(outAddr, inAddr, p)
|
||
|
if c == nil {
|
||
|
r.Unlock()
|
||
|
panic("No control for udp tx")
|
||
|
}
|
||
|
|
||
|
c.InjectUDPPacket(p)
|
||
|
r.Unlock()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RouteExitFunc will call the whatDo func with each udp packet from sender.
|
||
|
// whatDo can return:
|
||
|
// - exitNow: the packet will not be routed and this call will return immediately
|
||
|
// - routeAndExit: this call will return immediately after routing the last packet from sender
|
||
|
// - keepRouting: the packet will be routed and whatDo will be called again on the next packet from sender
|
||
|
//TODO: is this RouteWhile?
|
||
|
func (r *R) RouteExitFunc(sender *nebula.Control, whatDo ExitFunc) {
|
||
|
h := &nebula.Header{}
|
||
|
for {
|
||
|
p := sender.GetFromUDP(true)
|
||
|
r.Lock()
|
||
|
if err := h.Parse(p.Data); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
outAddr := sender.GetUDPAddr()
|
||
|
inAddr := net.JoinHostPort(p.ToIp.String(), fmt.Sprintf("%v", p.ToPort))
|
||
|
receiver := r.getControl(outAddr, inAddr, p)
|
||
|
if receiver == nil {
|
||
|
r.Unlock()
|
||
|
panic("Can't route for host: " + inAddr)
|
||
|
}
|
||
|
|
||
|
e := whatDo(p, receiver)
|
||
|
switch e {
|
||
|
case exitNow:
|
||
|
r.Unlock()
|
||
|
return
|
||
|
|
||
|
case routeAndExit:
|
||
|
receiver.InjectUDPPacket(p)
|
||
|
r.Unlock()
|
||
|
return
|
||
|
|
||
|
case keepRouting:
|
||
|
receiver.InjectUDPPacket(p)
|
||
|
|
||
|
default:
|
||
|
panic(fmt.Sprintf("Unknown exitFunc return: %v", e))
|
||
|
}
|
||
|
|
||
|
r.Unlock()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// RouteUntilAfterMsgType will route for sender until a message type is seen and sent from sender
|
||
|
// If the router doesn't have the nebula controller for that address, we panic
|
||
|
func (r *R) RouteUntilAfterMsgType(sender *nebula.Control, msgType nebula.NebulaMessageType, subType nebula.NebulaMessageSubType) {
|
||
|
h := &nebula.Header{}
|
||
|
r.RouteExitFunc(sender, func(p *nebula.UdpPacket, r *nebula.Control) exitType {
|
||
|
if err := h.Parse(p.Data); err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
if h.Type == msgType && h.Subtype == subType {
|
||
|
return routeAndExit
|
||
|
}
|
||
|
|
||
|
return keepRouting
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// RouteForUntilAfterToAddr will route for sender and return only after it sees and sends a packet destined for toAddr
|
||
|
// finish can be any of the exitType values except `keepRouting`, the default value is `routeAndExit`
|
||
|
// If the router doesn't have the nebula controller for that address, we panic
|
||
|
func (r *R) RouteForUntilAfterToAddr(sender *nebula.Control, toAddr *net.UDPAddr, finish exitType) {
|
||
|
if finish == keepRouting {
|
||
|
finish = routeAndExit
|
||
|
}
|
||
|
|
||
|
r.RouteExitFunc(sender, func(p *nebula.UdpPacket, r *nebula.Control) exitType {
|
||
|
if p.ToIp.Equal(toAddr.IP) && p.ToPort == uint16(toAddr.Port) {
|
||
|
return finish
|
||
|
}
|
||
|
|
||
|
return keepRouting
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// getControl performs or seeds NAT translation and returns the control for toAddr, p from fields may change
|
||
|
// This is an internal router function, the caller must hold the lock
|
||
|
func (r *R) getControl(fromAddr, toAddr string, p *nebula.UdpPacket) *nebula.Control {
|
||
|
if newAddr, ok := r.outNat[fromAddr+":"+toAddr]; ok {
|
||
|
p.FromIp = newAddr.IP
|
||
|
p.FromPort = uint16(newAddr.Port)
|
||
|
}
|
||
|
|
||
|
c, ok := r.inNat[toAddr]
|
||
|
if ok {
|
||
|
sHost, sPort, err := net.SplitHostPort(toAddr)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
port, err := strconv.Atoi(sPort)
|
||
|
if err != nil {
|
||
|
panic(err)
|
||
|
}
|
||
|
|
||
|
r.outNat[c.GetUDPAddr()+":"+fromAddr] = net.UDPAddr{
|
||
|
IP: net.ParseIP(sHost),
|
||
|
Port: port,
|
||
|
}
|
||
|
return c
|
||
|
}
|
||
|
|
||
|
//TODO: call receive hooks!
|
||
|
return r.controls[toAddr]
|
||
|
}
|