Files
weave/router/fastdp.go
T
2015-06-12 21:24:10 +01:00

594 lines
13 KiB
Go

package router
import (
"encoding/binary"
"fmt"
"log"
"net"
"sync"
"github.com/weaveworks/go-odp/odp"
)
type thunk func() FlowOp
// A bridgePortID is either an ODP vport or the router. We express it
// like this to use it as a map key.
type bridgePortID struct {
vport odp.VportID
router bool
}
var routerPortID = bridgePortID{router: true}
type FastDatapath struct {
vxlanPort int
// The lock guards rthe FastDatapath state, and also
// synchronises use of the dpif
lock sync.Mutex
dpif *odp.Dpif
dp odp.DatapathHandle
consumingMisses bool
missHandlers map[odp.VportID]func(odp.FlowKeys) thunk
vxlanVportID odp.VportID
localPeer *Peer
peers *Peers
interHostConsumer InterHostConsumer
// Bridge state: A bridge port is represented as a function
// that says how to send out a packet on that port.
bridgePorts map[bridgePortID]func(PacketKey) thunk
bridgeMACs map[MAC]func(PacketKey) thunk
// Only accessed from Miss, so not locked
dec *EthernetDecoder
}
func NewFastDatapath(dpname string, vxlanPort int) (*FastDatapath, error) {
dpif, err := odp.NewDpif()
if err != nil {
return nil, err
}
success := false
defer func() {
if !success {
dpif.Close()
}
}()
dp, err := dpif.LookupDatapath(dpname)
if err != nil {
return nil, err
}
fastdp := &FastDatapath{
vxlanPort: vxlanPort,
dpif: dpif,
dp: dp,
missHandlers: make(map[odp.VportID]func(odp.FlowKeys) thunk),
bridgePorts: make(map[bridgePortID]func(PacketKey) thunk),
bridgeMACs: make(map[MAC]func(PacketKey) thunk),
dec: NewEthernetDecoder(),
}
if err := fastdp.deleteVxlanVports(); err != nil {
return nil, err
}
if err := fastdp.clearFlows(); err != nil {
return nil, err
}
fastdp.vxlanVportID, err = fastdp.dp.CreateVport(
odp.NewVxlanVportSpec("vxlan", uint16(vxlanPort)))
if err != nil {
return nil, err
}
success = true
return fastdp, nil
}
func (fastdp *FastDatapath) consumeMisses() error {
if !fastdp.consumingMisses {
if err := fastdp.dp.ConsumeMisses(fastdp); err != nil {
return err
}
fastdp.consumingMisses = true
}
return nil
}
func (fastdp *FastDatapath) Close() error {
err := fastdp.dpif.Close()
fastdp.dpif = nil
return err
}
func (fastdp *FastDatapath) clearFlows() error {
flows, err := fastdp.dp.EnumerateFlows()
if err != nil {
return err
}
for _, flow := range flows {
err = fastdp.dp.DeleteFlow(flow.FlowSpec)
if err != nil && !odp.IsNoSuchFlowError(err) {
return err
}
}
return nil
}
func (fastdp *FastDatapath) deleteVxlanVports() error {
vports, err := fastdp.dp.EnumerateVports()
if err != nil {
return err
}
for _, vport := range vports {
if vport.Spec.TypeName() != "vxlan" {
continue
}
err = fastdp.dp.DeleteVport(vport.ID)
if err != nil && !odp.IsNoSuchVportError(err) {
return err
}
}
return nil
}
func (fastdp *FastDatapath) Error(err error, stopped bool) {
// XXX fatal if stopped
log.Println("Error while listening on datapath:", err)
}
func (fastdp *FastDatapath) Miss(packet []byte, flowKeys odp.FlowKeys) error {
thunk := fastdp.handleMiss(flowKeys)
if thunk != nil {
fastdp.send(thunk(), packet)
}
return nil
}
func (fastdp *FastDatapath) handleMiss(fks odp.FlowKeys) thunk {
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
ingress := fks[odp.OVS_KEY_ATTR_IN_PORT].(odp.InPortFlowKey).VportID()
log.Println("Got miss", fks, "on port", ingress)
handler := fastdp.missHandlers[ingress]
if handler == nil {
handler = fastdp.makeMissHandler(ingress)
if handler == nil {
return simpleThunk(vetoFlowCreation)
}
fastdp.missHandlers[ingress] = handler
}
return handler(fks)
}
func (fastdp *FastDatapath) makeMissHandler(
ingress odp.VportID) func(odp.FlowKeys) thunk {
// Auto-add netdev and internal vports to the bridge (i.e. not
// vxlan vports)
vport, err := fastdp.dp.LookupVport(ingress)
if err != nil {
log.Println(err)
return nil
}
typ := vport.Spec.TypeName()
if typ != "netdev" && typ != "internal" {
return nil
}
// Construct a bridge port for the netdev, which simply
// outputs packets
bridgePort := func(key PacketKey) thunk {
return simpleThunk(fastdp.odpActions(
odp.NewOutputAction(ingress)))
}
fastdp.bridgePorts[bridgePortID{vport: ingress}] = bridgePort
// Clear flows, in order to recalculate flows for broadcasts
// on the bridge
checkWarn(fastdp.clearFlows())
// Packets from the netdev are processed by the bridge
return func(fks odp.FlowKeys) thunk {
return multiThunk(fastdp.bridge(bridgePortID{vport: ingress},
flowKeysToPacketKey(fks)))
}
}
func flowKeysToPacketKey(fks odp.FlowKeys) PacketKey {
eth := fks[odp.OVS_KEY_ATTR_ETHERNET].(odp.EthernetFlowKey).Key()
return PacketKey{SrcMAC: eth.EthSrc, DstMAC: eth.EthDst}
}
// Send a packet, creating a corresponding ODP flow rule if possible
func (fastdp *FastDatapath) send(fops FlowOp, frame []byte) {
// Gather the actions from actionFlowOps, execute any others
var dec *EthernetDecoder
flow := odp.NewFlowSpec()
createFlow := true
for _, xfop := range FlattenFlowOp(fops) {
switch fop := xfop.(type) {
case interface {
updateFlowSpec(*odp.FlowSpec)
}:
fop.updateFlowSpec(&flow)
case vetoFlowCreationFlowOp:
createFlow = false
default:
// A foreign flow op, so send the packet the
// normal way, decoding the packet lazily.
if dec == nil {
dec = fastdp.dec
dec.DecodeLayers(frame)
createFlow = false
}
if len(dec.decoded) != 0 {
fop.Send(frame, dec, false)
}
}
}
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
if len(flow.Actions) != 0 {
checkWarn(fastdp.dp.Execute(frame, nil, flow.Actions))
}
if createFlow {
log.Println("Creating flow", flow)
checkWarn(fastdp.dp.CreateFlow(flow))
}
}
type odpActionsFlowOp struct {
fastdp *FastDatapath
actions []odp.Action
}
func (fastdp *FastDatapath) odpActions(actions ...odp.Action) FlowOp {
return odpActionsFlowOp{
fastdp: fastdp,
actions: actions,
}
}
func (fop odpActionsFlowOp) updateFlowSpec(flow *odp.FlowSpec) {
flow.AddActions(fop.actions)
}
func (fop odpActionsFlowOp) Send(frame []byte, dec *EthernetDecoder, bc bool) {
fastdp := fop.fastdp
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
checkWarn(fastdp.dp.Execute(frame, nil, fop.actions))
}
type nopFlowOp struct{}
func (nopFlowOp) Send([]byte, *EthernetDecoder, bool) {
// A nopFlowOp just provides a hint about flow creation, it
// doesn't send anything
}
type vetoFlowCreationFlowOp struct {
nopFlowOp
}
var vetoFlowCreation = vetoFlowCreationFlowOp{}
type odpFlowKeyFlowOp struct {
key odp.FlowKey
nopFlowOp
}
func odpFlowKey(key odp.FlowKey) FlowOp {
return odpFlowKeyFlowOp{key: key}
}
func (fop odpFlowKeyFlowOp) updateFlowSpec(flow *odp.FlowSpec) {
flow.AddKey(fop.key)
}
func odpEthernetFlowKey(key PacketKey) FlowOp {
fk := odp.NewEthernetFlowKey()
fk.SetEthSrc(key.SrcMAC)
fk.SetEthDst(key.DstMAC)
return odpFlowKeyFlowOp{key: fk}
}
func simpleThunk(fop FlowOp) thunk {
return func() FlowOp { return fop }
}
func callThunks(thunks []thunk) FlowOp {
if len(thunks) == 1 {
return thunks[0]()
}
mfop := NewMultiFlowOp(false)
for _, thunk := range thunks {
mfop.Add(thunk())
}
return mfop
}
func multiThunk(thunks []thunk) thunk {
return func() FlowOp { return callThunks(thunks) }
}
// IntraHost
func (fastdp *FastDatapath) ConsumeIntraHostPackets(
consumer IntraHostConsumer) error {
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
if fastdp.bridgePorts[routerPortID] != nil {
panic("FastDatapath already has an IntraHostConsumer")
}
fastdp.bridgePorts[routerPortID] = func(key PacketKey) thunk {
return func() FlowOp {
return consumer.CapturedPacket(key)
}
}
return fastdp.consumeMisses()
}
func (fastdp *FastDatapath) InjectPacket(key PacketKey) FlowOp {
return callThunks(func() []thunk {
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
return fastdp.bridge(routerPortID, key)
}())
}
// A trivial bridge implementation
func (fastdp *FastDatapath) bridge(ingress bridgePortID,
key PacketKey) []thunk {
if fastdp.bridgeMACs[key.SrcMAC] == nil {
// Learn the source MAC
fastdp.bridgeMACs[key.SrcMAC] = fastdp.bridgePorts[ingress]
}
// If we know about the destination MAC, deliver it to the
// associated port.
dst := fastdp.bridgeMACs[key.DstMAC]
if dst != nil {
return []thunk{simpleThunk(odpEthernetFlowKey(key)), dst(key)}
}
// Otherwise, it might be a real broadcast, or it might just
// be for a MAC we don't know about yet. Either way, we'll
// broadcast it.
var fop FlowOp
thunks := make([]thunk, 1, len(fastdp.bridgePorts)+2)
if (key.DstMAC[0] & 1) == 0 {
// Not a real broadcast, so doon't create a flow rule.
// If we did, we'd need to clear the flows every time
// we learned a new MAC address, or have a more
// complicated selective invalidation scheme.
fop = vetoFlowCreation
} else {
// A real broadcast
fop = odpEthernetFlowKey(key)
if !ingress.router {
// The rule depends on the ingress vport.
thunks = thunks[:2]
thunks[1] = simpleThunk(odpFlowKey(odp.NewInPortFlowKey(
ingress.vport)))
}
}
thunks[0] = simpleThunk(fop)
// Send to all ports except the one it came in on.
for id, dst := range fastdp.bridgePorts {
if id != ingress {
thunks = append(thunks, dst(key))
}
}
return thunks
}
// InterHost
func (fastdp *FastDatapath) ConsumeInterHostPackets(
localPeer *Peer, peers *Peers, consumer InterHostConsumer) error {
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
if fastdp.missHandlers[fastdp.vxlanVportID] != nil {
panic("FastDatapath already has an InterHostConsumer")
}
fastdp.localPeer = localPeer
fastdp.peers = peers
fastdp.missHandlers[fastdp.vxlanVportID] = func(fks odp.FlowKeys) thunk {
tunnel := fks[odp.OVS_KEY_ATTR_TUNNEL].(odp.TunnelFlowKey).Key()
srcPeer, dstPeer := fastdp.extractPeers(tunnel.TunnelId)
if srcPeer == nil || dstPeer == nil {
return simpleThunk(vetoFlowCreation)
}
key := ForwardPacketKey{
SrcPeer: srcPeer,
DstPeer: dstPeer,
PacketKey: flowKeysToPacketKey(fks),
}
// The resulting flow rule should be restricted to
// packets with the same tunnelID
var tunnelFlowKey odp.TunnelFlowKey
tunnelFlowKey.SetTunnelId(tunnel.TunnelId)
tunnelFlowKey.SetIpv4Src(tunnel.Ipv4Src)
tunnelFlowKey.SetIpv4Dst(tunnel.Ipv4Dst)
return func() FlowOp {
fop := NewMultiFlowOp(false)
fop.Add(odpFlowKey(tunnelFlowKey))
fop.Add(consumer(key))
return fop
}
}
return fastdp.consumeMisses()
}
func (fastdp *FastDatapath) InvalidateRoutes() {
fmt.Println("InvalidateRoutes")
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
checkWarn(fastdp.clearFlows())
}
func (fastdp *FastDatapath) InvalidateShortIDs() {
fmt.Println("InvalidateShortIDs")
fastdp.lock.Lock()
defer fastdp.lock.Unlock()
checkWarn(fastdp.clearFlows())
}
func (fastdp *FastDatapath) extractPeers(tunnelID [8]byte) (*Peer, *Peer) {
if fastdp.peers == nil {
return nil, nil
}
vni := binary.BigEndian.Uint64(tunnelID[:])
srcPeer := fastdp.peers.FetchByShortID(PeerShortID(vni & 0xfff))
dstPeer := fastdp.peers.FetchByShortID(PeerShortID((vni >> 12) & 0xfff))
return srcPeer, dstPeer
}
type FastDatapathForwarder struct {
fastdp *FastDatapath
remotePeer *Peer
localIP [4]byte
sendControlMsg func([]byte) error
lock sync.Mutex
listener InterHostForwarderListener
remoteIP [4]byte
established bool
}
func (fastdp *FastDatapath) MakeForwarder(remotePeer *Peer, localIP net.IP,
remote *net.UDPAddr, connUID uint64, crypto InterHostCrypto,
sendControlMsg func([]byte) error) (InterHostForwarder, error) {
if len(localIP) != 4 {
return nil, fmt.Errorf("local IP address %s is not IPv4", localIP)
}
fwd := &FastDatapathForwarder{
fastdp: fastdp,
remotePeer: remotePeer,
sendControlMsg: sendControlMsg,
}
copy(fwd.localIP[:], localIP)
return fwd, nil
}
func (fwd *FastDatapathForwarder) SetListener(
listener InterHostForwarderListener) {
fwd.lock.Lock()
defer fwd.lock.Unlock()
if listener == nil {
panic("nil listener")
}
fwd.listener = listener
if fwd.established {
listener.Established()
}
fwd.sendControlMsg(fwd.localIP[:])
}
func (fwd *FastDatapathForwarder) ControlMessage(msg []byte) {
fwd.lock.Lock()
defer fwd.lock.Unlock()
if len(msg) != 4 {
if fwd.listener != nil {
fwd.listener.Error(fmt.Errorf("FastDatapath control message wrong length %d", len(msg)))
fwd.listener = nil
}
return
}
if !fwd.established {
copy(fwd.remoteIP[:], msg)
fwd.established = true
if fwd.listener != nil {
fwd.listener.Established()
}
}
}
func (fwd *FastDatapathForwarder) Forward(key ForwardPacketKey) FlowOp {
fwd.lock.Lock()
defer fwd.lock.Unlock()
if !fwd.established {
// Ideally we could just return nil. But then we
// would have to invalidate the resulting flows when
// we learn the remote IP. So for now, just prevent
// flows.
return vetoFlowCreation
}
var sta odp.SetTunnelAction
sta.SetTunnelId(tunnelIDFor(key))
sta.SetIpv4Src(fwd.localIP)
sta.SetIpv4Dst(fwd.remoteIP)
sta.SetTos(0)
sta.SetTtl(64)
sta.SetDf(true)
sta.SetCsum(false)
return fwd.fastdp.odpActions(sta,
odp.NewOutputAction(fwd.fastdp.vxlanVportID))
}
func tunnelIDFor(key ForwardPacketKey) (tunnelID [8]byte) {
src := uint64(key.SrcPeer.ShortID)
dst := uint64(key.DstPeer.ShortID)
binary.BigEndian.PutUint64(tunnelID[:], src|dst<<12)
return
}
func (fwd *FastDatapathForwarder) Close() {
// Ideally we would delete all the relevant flows here. But
// until we do that, it's probably not worth clearing all
// flows.
}