1176 lines
31 KiB
Go
1176 lines
31 KiB
Go
// This contains the Overlay implementation for weave's own UDP
|
|
// encapsulation protocol ("sleeve" because a sleeve encapsulates
|
|
// something, it's often woven, it rhymes with "weave", make up your
|
|
// own cheesy reason).
|
|
|
|
package router
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
"github.com/google/gopacket/layers"
|
|
"github.com/weaveworks/mesh"
|
|
)
|
|
|
|
// This diagram explains the various arithmetic and variables related
|
|
// to packet offsets and lengths below:
|
|
//
|
|
// +----+-----+--------+--------+----------+--------------------------+
|
|
// | IP | UDP | Sleeve | Sleeve | Overlay | Overlay Layer 3 Payload |
|
|
// | | | Packet | Frame | Ethernet | |
|
|
// | | | Header | Header | | |
|
|
// +----+-----+--------+--------+----------+--------------------------+
|
|
//
|
|
// <------------------------------------ msgTooBigError.underlayPMTU ->
|
|
//
|
|
// <-------------------------- sleeveForwarder.maxPayload ->
|
|
//
|
|
// <----------> UDPOverhead
|
|
//
|
|
// <--------> Encryptor.PacketOverhead
|
|
//
|
|
// <--------> Encryptor.FrameOverhead
|
|
//
|
|
// <----------> EthernetOverhead
|
|
//
|
|
// <---------------------------------------> sleeveForwarder.overheadDF
|
|
//
|
|
// sleeveForwarder.mtu <-------------------------->
|
|
|
|
const (
|
|
EthernetOverhead = 14
|
|
UDPOverhead = 28 // 20 bytes for IPv4, 8 bytes for UDP
|
|
DefaultMTU = 65535
|
|
FragTestSize = 60001
|
|
PMTUDiscoverySize = 60000
|
|
FragTestInterval = 5 * time.Minute
|
|
MTUVerifyAttempts = 8
|
|
MTUVerifyTimeout = 10 * time.Millisecond // doubled with each attempt
|
|
|
|
ProtocolConnectionEstablished = mesh.ProtocolReserved1
|
|
ProtocolFragmentationReceived = mesh.ProtocolReserved2
|
|
ProtocolPMTUVerified = mesh.ProtocolReserved3
|
|
)
|
|
|
|
type SleeveOverlay struct {
|
|
host string
|
|
localPort int
|
|
|
|
// These fields are set in StartConsumingPackets, and not
|
|
// subsequently modified
|
|
localPeer *mesh.Peer
|
|
localPeerBin []byte
|
|
consumer OverlayConsumer
|
|
peers *mesh.Peers
|
|
conn *net.UDPConn
|
|
|
|
lock sync.Mutex
|
|
forwarders map[mesh.PeerName]*sleeveForwarder
|
|
}
|
|
|
|
func NewSleeveOverlay(host string, localPort int) NetworkOverlay {
|
|
return &SleeveOverlay{host: host, localPort: localPort}
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) StartConsumingPackets(localPeer *mesh.Peer, peers *mesh.Peers, consumer OverlayConsumer) error {
|
|
localAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprint(sleeve.host, ":", sleeve.localPort))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
conn, err := net.ListenUDP("udp4", localAddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := conn.File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer f.Close()
|
|
fd := int(f.Fd())
|
|
|
|
// This makes sure all packets we send out do not have DF set
|
|
// on them.
|
|
err = syscall.SetsockoptInt(fd, syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DONT)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sleeve.lock.Lock()
|
|
defer sleeve.lock.Unlock()
|
|
|
|
if sleeve.localPeer != nil {
|
|
conn.Close()
|
|
return fmt.Errorf("StartConsumingPackets already called")
|
|
}
|
|
|
|
sleeve.localPeer = localPeer
|
|
sleeve.localPeerBin = localPeer.NameByte
|
|
sleeve.consumer = consumer
|
|
sleeve.peers = peers
|
|
sleeve.conn = conn
|
|
sleeve.forwarders = make(map[mesh.PeerName]*sleeveForwarder)
|
|
go sleeve.readUDP()
|
|
return nil
|
|
}
|
|
|
|
func (*SleeveOverlay) InvalidateRoutes() {
|
|
// no cached information, so nothing to do
|
|
}
|
|
|
|
func (*SleeveOverlay) InvalidateShortIDs() {
|
|
// no cached information, so nothing to do
|
|
}
|
|
|
|
func (*SleeveOverlay) AddFeaturesTo(map[string]string) {
|
|
// No features to be provided, to facilitate compatibility
|
|
}
|
|
|
|
func (*SleeveOverlay) Diagnostics() interface{} {
|
|
return nil
|
|
}
|
|
|
|
func (*SleeveOverlay) Stop() {
|
|
// do nothing
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) lookupForwarder(peer mesh.PeerName) *sleeveForwarder {
|
|
sleeve.lock.Lock()
|
|
defer sleeve.lock.Unlock()
|
|
return sleeve.forwarders[peer]
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) addForwarder(peer mesh.PeerName, fwd *sleeveForwarder) {
|
|
sleeve.lock.Lock()
|
|
defer sleeve.lock.Unlock()
|
|
sleeve.forwarders[peer] = fwd
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) removeForwarder(peer mesh.PeerName, fwd *sleeveForwarder) {
|
|
sleeve.lock.Lock()
|
|
defer sleeve.lock.Unlock()
|
|
if sleeve.forwarders[peer] == fwd {
|
|
delete(sleeve.forwarders, peer)
|
|
}
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) readUDP() {
|
|
defer sleeve.conn.Close()
|
|
dec := NewEthernetDecoder()
|
|
buf := make([]byte, MaxUDPPacketSize)
|
|
|
|
for {
|
|
n, sender, err := sleeve.conn.ReadFromUDP(buf)
|
|
if err == io.EOF {
|
|
return
|
|
} else if err != nil {
|
|
log.Print("ignoring UDP read error ", err)
|
|
continue
|
|
} else if n < NameSize {
|
|
log.Print("ignoring too short UDP packet from ", sender)
|
|
continue
|
|
}
|
|
|
|
fwdName := mesh.PeerNameFromBin(buf[:NameSize])
|
|
fwd := sleeve.lookupForwarder(fwdName)
|
|
if fwd == nil {
|
|
continue
|
|
}
|
|
|
|
packet := make([]byte, n-NameSize)
|
|
copy(packet, buf[NameSize:n])
|
|
|
|
err = fwd.crypto.Dec.IterateFrames(packet,
|
|
func(src []byte, dst []byte, frame []byte) {
|
|
sleeve.handleFrame(sender, fwd, src, dst, frame, dec)
|
|
})
|
|
if err != nil {
|
|
// Errors during UDP packet decoding /
|
|
// processing are non-fatal. One common cause
|
|
// is that we receive and attempt to decrypt a
|
|
// "stray" packet. This can actually happen
|
|
// quite easily if there is some connection
|
|
// churn between two peers. After all, UDP
|
|
// isn't a connection-oriented protocol, yet
|
|
// we pretend it is.
|
|
//
|
|
// If anything really is seriously,
|
|
// unrecoverably amiss with a connection, that
|
|
// will typically result in missed heartbeats
|
|
// and the connection getting shut down
|
|
// because of that.
|
|
log.Print(fwd.logPrefixFor(sender), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) handleFrame(sender *net.UDPAddr, fwd *sleeveForwarder, src []byte, dst []byte, frame []byte, dec *EthernetDecoder) {
|
|
dec.DecodeLayers(frame)
|
|
decodedLen := len(dec.decoded)
|
|
if decodedLen == 0 {
|
|
return
|
|
}
|
|
|
|
srcPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(src))
|
|
dstPeer := sleeve.peers.Fetch(mesh.PeerNameFromBin(dst))
|
|
if srcPeer == nil || dstPeer == nil {
|
|
return
|
|
}
|
|
|
|
// Handle special frames produced internally (rather than
|
|
// captured/forwarded) by the remote router.
|
|
//
|
|
// We really shouldn't be decoding these above, since they are
|
|
// not genuine Ethernet frames. However, it is actually more
|
|
// efficient to do so, as we want to optimise for the common
|
|
// (i.e. non-special) frames. These always need decoding, and
|
|
// detecting special frames is cheaper post decoding than pre.
|
|
if decodedLen == 1 && dec.IsSpecial() {
|
|
if srcPeer == fwd.remotePeer && dstPeer == fwd.sleeve.localPeer {
|
|
select {
|
|
case fwd.specialChan <- specialFrame{sender, frame}:
|
|
case <-fwd.finishedChan:
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
sleeve.sendToConsumer(srcPeer, dstPeer, frame, dec)
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) sendToConsumer(srcPeer, dstPeer *mesh.Peer, frame []byte, dec *EthernetDecoder) {
|
|
if sleeve.consumer == nil {
|
|
return
|
|
}
|
|
|
|
fop := sleeve.consumer(ForwardPacketKey{
|
|
SrcPeer: srcPeer,
|
|
DstPeer: dstPeer,
|
|
PacketKey: dec.PacketKey(),
|
|
})
|
|
if fop != nil {
|
|
fop.Process(frame, dec, false)
|
|
}
|
|
}
|
|
|
|
type udpSender interface {
|
|
send([]byte, *net.UDPAddr) error
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) send(msg []byte, raddr *net.UDPAddr) error {
|
|
sleeve.lock.Lock()
|
|
conn := sleeve.conn
|
|
sleeve.lock.Unlock()
|
|
|
|
if conn == nil {
|
|
// Consume wasn't called yet
|
|
return nil
|
|
}
|
|
|
|
_, err := conn.WriteToUDP(msg, raddr)
|
|
return err
|
|
}
|
|
|
|
type sleeveCrypto struct {
|
|
Dec Decryptor
|
|
Enc Encryptor
|
|
EncDF Encryptor
|
|
}
|
|
|
|
func newSleeveCrypto(name []byte, sessionKey *[32]byte, outbound bool) sleeveCrypto {
|
|
if sessionKey == nil {
|
|
return sleeveCrypto{
|
|
Dec: NewNonDecryptor(),
|
|
Enc: NewNonEncryptor(name),
|
|
EncDF: NewNonEncryptor(name),
|
|
}
|
|
}
|
|
return sleeveCrypto{
|
|
Dec: NewNaClDecryptor(sessionKey, outbound),
|
|
Enc: NewNaClEncryptor(name, sessionKey, outbound, false),
|
|
EncDF: NewNaClEncryptor(name, sessionKey, outbound, true),
|
|
}
|
|
}
|
|
|
|
func (crypto sleeveCrypto) Overhead() int {
|
|
return UDPOverhead + crypto.EncDF.PacketOverhead() + crypto.EncDF.FrameOverhead() + EthernetOverhead
|
|
}
|
|
|
|
type sleeveForwarder struct {
|
|
// Immutable
|
|
sleeve *SleeveOverlay
|
|
remotePeer *mesh.Peer
|
|
remotePeerBin []byte
|
|
sendControlMsg func(byte, []byte) error
|
|
connUID uint64
|
|
|
|
// Channels to communicate with the aggregator goroutine
|
|
aggregatorChan chan<- aggregatorFrame
|
|
aggregatorDFChan chan<- aggregatorFrame
|
|
specialChan chan<- specialFrame
|
|
controlMsgChan chan<- controlMessage
|
|
confirmedChan chan<- struct{}
|
|
finishedChan <-chan struct{}
|
|
|
|
// listener channels
|
|
establishedChan chan struct{}
|
|
errorChan chan error
|
|
healthChan chan bool
|
|
|
|
// Explicitly locked state
|
|
lock sync.RWMutex
|
|
remoteAddr *net.UDPAddr
|
|
|
|
// These fields are accessed and updated independently, so no
|
|
// locking needed.
|
|
mtu int // the mtu for this link on the overlay network
|
|
stackFrag bool
|
|
|
|
// State only used within the forwarder goroutine
|
|
crypto sleeveCrypto
|
|
senderDF *udpSenderDF
|
|
maxPayload int
|
|
|
|
// How many bytes of overhead it takes to turn an IP packet on
|
|
// the overlay network into an encapsulated packet on the underlay
|
|
// network
|
|
overheadDF int
|
|
|
|
heartbeatInterval time.Duration
|
|
heartbeatTimer *time.Timer
|
|
heartbeatTimeout *time.Timer
|
|
fragTestTicker *time.Ticker
|
|
ackedHeartbeat bool
|
|
|
|
mtuTestTimeout *time.Timer
|
|
mtuTestsSent uint
|
|
mtuHighestGood int
|
|
mtuLowestBad int
|
|
mtuCandidate int
|
|
}
|
|
|
|
type aggregatorFrame struct {
|
|
src []byte
|
|
dst []byte
|
|
frame []byte
|
|
}
|
|
|
|
// A "special" frame over UDP
|
|
type specialFrame struct {
|
|
sender *net.UDPAddr
|
|
frame []byte
|
|
}
|
|
|
|
// A control message
|
|
type controlMessage struct {
|
|
tag byte
|
|
msg []byte
|
|
}
|
|
|
|
func (sleeve *SleeveOverlay) PrepareConnection(params mesh.OverlayConnectionParams) (mesh.OverlayConnection, error) {
|
|
aggChan := make(chan aggregatorFrame, ChannelSize)
|
|
aggDFChan := make(chan aggregatorFrame, ChannelSize)
|
|
specialChan := make(chan specialFrame, 1)
|
|
controlMsgChan := make(chan controlMessage, 1)
|
|
confirmedChan := make(chan struct{})
|
|
finishedChan := make(chan struct{})
|
|
|
|
var remoteAddr *net.UDPAddr
|
|
if params.Outbound {
|
|
remoteAddr = makeUDPAddr(params.RemoteAddr)
|
|
}
|
|
|
|
crypto := newSleeveCrypto(sleeve.localPeer.NameByte, params.SessionKey, params.Outbound)
|
|
|
|
fwd := &sleeveForwarder{
|
|
sleeve: sleeve,
|
|
remotePeer: params.RemotePeer,
|
|
remotePeerBin: params.RemotePeer.NameByte,
|
|
sendControlMsg: params.SendControlMessage,
|
|
connUID: params.ConnUID,
|
|
aggregatorChan: aggChan,
|
|
aggregatorDFChan: aggDFChan,
|
|
specialChan: specialChan,
|
|
controlMsgChan: controlMsgChan,
|
|
confirmedChan: confirmedChan,
|
|
finishedChan: finishedChan,
|
|
establishedChan: make(chan struct{}),
|
|
errorChan: make(chan error, 1),
|
|
remoteAddr: remoteAddr,
|
|
mtu: DefaultMTU,
|
|
crypto: crypto,
|
|
maxPayload: DefaultMTU - UDPOverhead,
|
|
overheadDF: crypto.Overhead(),
|
|
senderDF: newUDPSenderDF(params.LocalAddr.IP, sleeve.localPort),
|
|
}
|
|
|
|
go fwd.run(aggChan, aggDFChan, specialChan, controlMsgChan, confirmedChan, finishedChan)
|
|
return fwd, nil
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) logPrefixFor(sender *net.UDPAddr) string {
|
|
return fmt.Sprintf("sleeve ->[%s|%s]: ", sender, fwd.remotePeer)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) logPrefix() string {
|
|
fwd.lock.RLock()
|
|
remoteAddr := fwd.remoteAddr
|
|
fwd.lock.RUnlock()
|
|
return fwd.logPrefixFor(remoteAddr)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) Confirm() {
|
|
log.Debug(fwd.logPrefix(), "Confirm")
|
|
select {
|
|
case fwd.confirmedChan <- struct{}{}:
|
|
case <-fwd.finishedChan:
|
|
}
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) EstablishedChannel() <-chan struct{} {
|
|
return fwd.establishedChan
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) ErrorChannel() <-chan error {
|
|
return fwd.errorChan
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) HealthChannel() <-chan bool {
|
|
return fwd.healthChan
|
|
}
|
|
|
|
type curriedForward struct {
|
|
NonDiscardingFlowOp
|
|
fwd *sleeveForwarder
|
|
key ForwardPacketKey
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) Forward(key ForwardPacketKey) FlowOp {
|
|
return curriedForward{fwd: fwd, key: key}
|
|
}
|
|
|
|
func (f curriedForward) Process(frame []byte, dec *EthernetDecoder, broadcast bool) {
|
|
fwd := f.fwd
|
|
fwd.lock.RLock()
|
|
haveContact := (fwd.remoteAddr != nil)
|
|
mtu := fwd.mtu
|
|
stackFrag := fwd.stackFrag
|
|
fwd.lock.RUnlock()
|
|
|
|
if !haveContact {
|
|
log.Print(fwd.logPrefix(), "Cannot forward frame yet - awaiting contact")
|
|
return
|
|
}
|
|
|
|
srcName := f.key.SrcPeer.NameByte
|
|
dstName := f.key.DstPeer.NameByte
|
|
|
|
// We could use non-blocking channel sends here, i.e. drop frames
|
|
// on the floor when the forwarder is busy. This would allow our
|
|
// caller - the capturing loop in the router - to read frames more
|
|
// quickly when under load, i.e. we'd drop fewer frames on the
|
|
// floor during capture. And we could maximise CPU utilisation
|
|
// since we aren't stalling a thread. However, a lot of work has
|
|
// already been done by the time we get here. Since any packet we
|
|
// drop will likely get re-transmitted we end up paying that cost
|
|
// multiple times. So it's better to drop things at the beginning
|
|
// of our pipeline.
|
|
if dec.DF() {
|
|
if !frameTooBig(frame, mtu) {
|
|
fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, frame)
|
|
return
|
|
}
|
|
|
|
// Why do we need an explicit broadcast hint here,
|
|
// rather than just checking the frame for a broadcast
|
|
// destination MAC address? Because even
|
|
// non-broadcast frames can be broadcast, if the
|
|
// destination MAC was not in our MAC cache.
|
|
if broadcast {
|
|
log.Print(fwd.logPrefix(), "dropping too big DF broadcast frame: len ", len(frame), " (", dec.IP.SrcIP, " -> ", dec.IP.DstIP, "): MTU=", mtu)
|
|
return
|
|
}
|
|
|
|
// Send an ICMP back to where the frame came from
|
|
fragNeededPacket, err := dec.makeICMPFragNeeded(mtu)
|
|
if err != nil {
|
|
log.Print(fwd.logPrefix(), err)
|
|
return
|
|
}
|
|
|
|
dec.DecodeLayers(fragNeededPacket)
|
|
|
|
// The frag-needed packet does not have DF set, so the
|
|
// potential recursion here is bounded.
|
|
fwd.sleeve.sendToConsumer(f.key.DstPeer, f.key.SrcPeer, fragNeededPacket, dec)
|
|
return
|
|
}
|
|
|
|
if stackFrag || len(dec.decoded) < 2 {
|
|
fwd.aggregate(fwd.aggregatorChan, srcName, dstName, frame)
|
|
return
|
|
}
|
|
|
|
// Don't have trustworthy stack, so we're going to have to
|
|
// send it DF in any case.
|
|
if !frameTooBig(frame, mtu) {
|
|
fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, frame)
|
|
return
|
|
}
|
|
|
|
// We can't trust the stack to fragment, we have IP, and we
|
|
// have a frame that's too big for the MTU, so we have to
|
|
// fragment it ourself.
|
|
checkWarn(fragment(dec.Eth, dec.IP, mtu,
|
|
func(segFrame []byte) {
|
|
fwd.aggregate(fwd.aggregatorDFChan, srcName, dstName, segFrame)
|
|
}))
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) aggregate(ch chan<- aggregatorFrame, src []byte, dst []byte, frame []byte) {
|
|
select {
|
|
case ch <- aggregatorFrame{src, dst, frame}:
|
|
case <-fwd.finishedChan:
|
|
}
|
|
}
|
|
|
|
func fragment(eth layers.Ethernet, ip layers.IPv4, mtu int, forward func([]byte)) error {
|
|
// We are not doing any sort of NAT, so we don't need to worry
|
|
// about checksums of IP payload (eg UDP checksum).
|
|
headerSize := int(ip.IHL) * 4
|
|
// &^ is bit clear (AND NOT). So here we're clearing the lowest 3
|
|
// bits.
|
|
maxSegmentSize := (mtu - headerSize) &^ 7
|
|
opts := gopacket.SerializeOptions{
|
|
FixLengths: false,
|
|
ComputeChecksums: true}
|
|
payloadSize := int(ip.Length) - headerSize
|
|
payload := ip.BaseLayer.Payload[:payloadSize]
|
|
offsetBase := int(ip.FragOffset) << 3
|
|
origFlags := ip.Flags
|
|
ip.Flags = ip.Flags | layers.IPv4MoreFragments
|
|
ip.Length = uint16(headerSize + maxSegmentSize)
|
|
if eth.EthernetType == layers.EthernetTypeLLC {
|
|
// using LLC, so must set eth length correctly. eth length
|
|
// is just the length of the payload
|
|
eth.Length = ip.Length
|
|
} else {
|
|
eth.Length = 0
|
|
}
|
|
for offset := 0; offset < payloadSize; offset += maxSegmentSize {
|
|
var segmentPayload []byte
|
|
if len(payload) <= maxSegmentSize {
|
|
// last one
|
|
segmentPayload = payload
|
|
ip.Length = uint16(len(payload) + headerSize)
|
|
ip.Flags = origFlags
|
|
if eth.EthernetType == layers.EthernetTypeLLC {
|
|
eth.Length = ip.Length
|
|
} else {
|
|
eth.Length = 0
|
|
}
|
|
} else {
|
|
segmentPayload = payload[:maxSegmentSize]
|
|
payload = payload[maxSegmentSize:]
|
|
}
|
|
ip.FragOffset = uint16((offset + offsetBase) >> 3)
|
|
buf := gopacket.NewSerializeBuffer()
|
|
segPayload := gopacket.Payload(segmentPayload)
|
|
err := gopacket.SerializeLayers(buf, opts, ð, &ip, &segPayload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
forward(buf.Bytes())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func frameTooBig(frame []byte, mtu int) bool {
|
|
// We capture/forward complete ethernet frames. Therefore the
|
|
// frame length includes the ethernet header. However, MTUs
|
|
// operate at the IP layer and thus do not include the ethernet
|
|
// header. To put it another way, when a sender that was told an
|
|
// MTU of M sends an IP packet of exactly that length, we will
|
|
// capture/forward M + EthernetOverhead bytes of data.
|
|
return len(frame) > mtu+EthernetOverhead
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) ControlMessage(tag byte, msg []byte) {
|
|
select {
|
|
case fwd.controlMsgChan <- controlMessage{tag, msg}:
|
|
case <-fwd.finishedChan:
|
|
}
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) Attrs() map[string]interface{} {
|
|
return map[string]interface{}{"name": "sleeve", "mtu": fwd.mtu}
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) Stop() {
|
|
fwd.sleeve.removeForwarder(fwd.remotePeer.Name, fwd)
|
|
|
|
// Tell the forwarder goroutine to finish. We don't need to
|
|
// wait for it.
|
|
close(fwd.confirmedChan)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) run(aggChan <-chan aggregatorFrame,
|
|
aggDFChan <-chan aggregatorFrame,
|
|
specialChan <-chan specialFrame,
|
|
controlMsgChan <-chan controlMessage,
|
|
confirmedChan <-chan struct{},
|
|
finishedChan chan<- struct{}) {
|
|
defer close(finishedChan)
|
|
|
|
var err error
|
|
loop:
|
|
for err == nil {
|
|
select {
|
|
case frame := <-aggChan:
|
|
err = fwd.aggregateAndSend(frame, aggChan, fwd.crypto.Enc, fwd.sleeve, MaxUDPPacketSize-UDPOverhead)
|
|
|
|
case frame := <-aggDFChan:
|
|
err = fwd.aggregateAndSend(frame, aggDFChan, fwd.crypto.EncDF, fwd.senderDF, fwd.maxPayload)
|
|
|
|
case sf := <-specialChan:
|
|
err = fwd.handleSpecialFrame(sf)
|
|
|
|
case cm := <-controlMsgChan:
|
|
err = fwd.handleControlMessage(cm)
|
|
|
|
case _, ok := <-confirmedChan:
|
|
if !ok {
|
|
// confirmedChan is closed to indicate
|
|
// the forwarder is being closed
|
|
break loop
|
|
}
|
|
|
|
err = fwd.confirmed()
|
|
|
|
case <-timerChan(fwd.heartbeatTimer):
|
|
err = fwd.sendHeartbeat()
|
|
|
|
case <-timerChan(fwd.heartbeatTimeout):
|
|
err = fmt.Errorf("timed out waiting for UDP heartbeat")
|
|
|
|
case <-tickerChan(fwd.fragTestTicker):
|
|
err = fwd.sendFragTest()
|
|
|
|
case <-timerChan(fwd.mtuTestTimeout):
|
|
err = fwd.handleMTUTestFailure()
|
|
}
|
|
}
|
|
|
|
if fwd.heartbeatTimer != nil {
|
|
fwd.heartbeatTimer.Stop()
|
|
}
|
|
if fwd.heartbeatTimeout != nil {
|
|
fwd.heartbeatTimeout.Stop()
|
|
}
|
|
if fwd.fragTestTicker != nil {
|
|
fwd.fragTestTicker.Stop()
|
|
}
|
|
if fwd.mtuTestTimeout != nil {
|
|
fwd.mtuTestTimeout.Stop()
|
|
}
|
|
|
|
checkWarn(fwd.senderDF.close())
|
|
|
|
fwd.lock.RLock()
|
|
defer fwd.lock.RUnlock()
|
|
|
|
// this is the only place we send an error to errorChan
|
|
fwd.errorChan <- err
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) aggregateAndSend(frame aggregatorFrame, aggChan <-chan aggregatorFrame, enc Encryptor, sender udpSender, limit int) error {
|
|
// Give up after processing N frames, to avoid starving the
|
|
// other activities of the forwarder goroutine.
|
|
i := 0
|
|
|
|
for {
|
|
// Adding the first frame to an empty buffer
|
|
if !fits(frame, enc, limit) {
|
|
log.Print(fwd.logPrefix(), "Dropping too big frame during forwarding: frame len ", len(frame.frame), ", limit ", limit)
|
|
return nil
|
|
}
|
|
|
|
for {
|
|
enc.AppendFrame(frame.src, frame.dst, frame.frame)
|
|
i++
|
|
|
|
gotOne := false
|
|
if i < 100 {
|
|
select {
|
|
case frame = <-aggChan:
|
|
gotOne = true
|
|
default:
|
|
}
|
|
}
|
|
|
|
if !gotOne {
|
|
return fwd.flushEncryptor(enc, sender)
|
|
}
|
|
|
|
// Accumulate frames until doing so would
|
|
// exceed the MTU. Even in the non-DF case,
|
|
// it doesn't seem worth adding a frame where
|
|
// that would lead to fragmentation,
|
|
// potentially delaying or risking other
|
|
// frames.
|
|
if !fits(frame, enc, fwd.maxPayload) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if err := fwd.flushEncryptor(enc, sender); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
func fits(frame aggregatorFrame, enc Encryptor, limit int) bool {
|
|
return enc.TotalLen()+enc.FrameOverhead()+len(frame.frame) <= limit
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) flushEncryptor(enc Encryptor, sender udpSender) error {
|
|
msg, err := enc.Bytes()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return fwd.processSendError(sender.send(msg, fwd.remoteAddr))
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) sendSpecial(enc Encryptor, sender udpSender, data []byte) error {
|
|
enc.AppendFrame(fwd.sleeve.localPeerBin, fwd.remotePeerBin, data)
|
|
return fwd.flushEncryptor(enc, sender)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleSpecialFrame(special specialFrame) error {
|
|
// The special frame types are distinguished by length
|
|
switch len(special.frame) {
|
|
case EthernetOverhead + 8:
|
|
return fwd.handleHeartbeat(special)
|
|
|
|
case FragTestSize:
|
|
return fwd.handleFragTest(special.frame)
|
|
|
|
default:
|
|
return fwd.handleMTUTest(special.frame)
|
|
}
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleControlMessage(cm controlMessage) error {
|
|
switch cm.tag {
|
|
case ProtocolConnectionEstablished:
|
|
return fwd.handleHeartbeatAck()
|
|
|
|
case ProtocolFragmentationReceived:
|
|
return fwd.handleFragTestAck()
|
|
|
|
case ProtocolPMTUVerified:
|
|
return fwd.handleMTUTestAck(cm.msg)
|
|
|
|
default:
|
|
log.Print(fwd.logPrefix(), "Ignoring unknown control message tag: ", cm.tag)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) confirmed() error {
|
|
log.Debug(fwd.logPrefix(), "confirmed")
|
|
|
|
if fwd.heartbeatInterval != 0 {
|
|
// already confirmed
|
|
return nil
|
|
}
|
|
|
|
// when the connection is confirmed, this should be the only
|
|
// forwarder to the peer.
|
|
fwd.sleeve.addForwarder(fwd.remotePeer.Name, fwd)
|
|
|
|
// heartbeatInterval flags that we want to send heartbeats,
|
|
// even if we don't do sendHeartbeat() yet due to lacking the
|
|
// remote address.
|
|
fwd.heartbeatInterval = FastHeartbeat
|
|
if fwd.remoteAddr != nil {
|
|
if err := fwd.sendHeartbeat(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
fwd.heartbeatTimeout = time.NewTimer(HeartbeatTimeout)
|
|
return nil
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) sendHeartbeat() error {
|
|
log.Debug(fwd.logPrefix(), "sendHeartbeat")
|
|
|
|
// Prime the timer for the next heartbeat. We don't use a
|
|
// ticker because the interval is not constant.
|
|
fwd.heartbeatTimer = setTimer(fwd.heartbeatTimer, fwd.heartbeatInterval)
|
|
|
|
buf := make([]byte, EthernetOverhead+8)
|
|
binary.BigEndian.PutUint64(buf[EthernetOverhead:], fwd.connUID)
|
|
return fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, buf)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleHeartbeat(special specialFrame) error {
|
|
uid := binary.BigEndian.Uint64(special.frame[EthernetOverhead:])
|
|
if uid != fwd.connUID {
|
|
return nil
|
|
}
|
|
|
|
log.Debug(fwd.logPrefix(), "handleHeartbeat")
|
|
|
|
if fwd.remoteAddr == nil {
|
|
fwd.setRemoteAddr(special.sender)
|
|
if fwd.heartbeatInterval != 0 {
|
|
if err := fwd.sendHeartbeat(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else if !udpAddrsEqual(fwd.remoteAddr, special.sender) {
|
|
log.Print(fwd.logPrefix(), "Peer UDP address changed to ", special.sender)
|
|
fwd.setRemoteAddr(special.sender)
|
|
}
|
|
|
|
if !fwd.ackedHeartbeat {
|
|
fwd.ackedHeartbeat = true
|
|
if err := fwd.sendControlMsg(ProtocolConnectionEstablished, nil); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// we can receive a heartbeat before confirmed() has set up
|
|
// heartbeatTimeout
|
|
if fwd.heartbeatTimeout != nil {
|
|
fwd.heartbeatTimeout.Reset(HeartbeatTimeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) setRemoteAddr(addr *net.UDPAddr) {
|
|
// remoteAddr is only modified here, so we don't need to hold
|
|
// the lock when reading it from the forwarder goroutine. But
|
|
// other threads may read it while holding the read lock, so
|
|
// when we modify it, we need to hold the write lock.
|
|
fwd.lock.Lock()
|
|
fwd.remoteAddr = addr
|
|
fwd.lock.Unlock()
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleHeartbeatAck() error {
|
|
log.Debug(fwd.logPrefix(), "handleHeartbeatAck")
|
|
|
|
if fwd.heartbeatInterval != SlowHeartbeat {
|
|
fwd.heartbeatInterval = SlowHeartbeat
|
|
if fwd.heartbeatTimer != nil {
|
|
fwd.heartbeatTimer.Reset(fwd.heartbeatInterval)
|
|
}
|
|
|
|
// The connection is now regarded as established
|
|
close(fwd.establishedChan)
|
|
}
|
|
|
|
fwd.fragTestTicker = time.NewTicker(FragTestInterval)
|
|
if err := fwd.sendFragTest(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Send a large frame down the DF channel. An EMSGSIZE will
|
|
// result, which is handled in processSendError, prompting
|
|
// PMTU discovery to start.
|
|
return fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, make([]byte, PMTUDiscoverySize))
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) sendFragTest() error {
|
|
log.Debug(fwd.logPrefix(), "sendFragTest")
|
|
fwd.stackFrag = false
|
|
return fwd.sendSpecial(fwd.crypto.Enc, fwd.sleeve, make([]byte, FragTestSize))
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleFragTest(frame []byte) error {
|
|
if !allZeros(frame) {
|
|
return nil
|
|
}
|
|
|
|
return fwd.sendControlMsg(ProtocolFragmentationReceived, nil)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleFragTestAck() error {
|
|
log.Debug(fwd.logPrefix(), "handleFragTestAck")
|
|
fwd.stackFrag = true
|
|
return nil
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) processSendError(err error) error {
|
|
if mtbe, ok := err.(msgTooBigError); ok {
|
|
mtu := mtbe.underlayPMTU - fwd.overheadDF
|
|
if fwd.mtuCandidate != 0 && mtu >= fwd.mtuCandidate {
|
|
return nil
|
|
}
|
|
|
|
fwd.mtuHighestGood = 552
|
|
fwd.mtuLowestBad = mtu + 1
|
|
fwd.mtuCandidate = mtu
|
|
fwd.mtuTestsSent = 0
|
|
fwd.maxPayload = mtbe.underlayPMTU - UDPOverhead
|
|
fwd.mtu = mtu
|
|
return fwd.sendMTUTest()
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) sendMTUTest() error {
|
|
log.Debug(fwd.logPrefix(), "sendMTUTest: mtu candidate ", fwd.mtuCandidate)
|
|
|
|
err := fwd.sendSpecial(fwd.crypto.EncDF, fwd.senderDF, make([]byte, fwd.mtuCandidate+EthernetOverhead))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
fwd.mtuTestTimeout = setTimer(fwd.mtuTestTimeout, MTUVerifyTimeout<<fwd.mtuTestsSent)
|
|
fwd.mtuTestsSent++
|
|
return nil
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleMTUTest(frame []byte) error {
|
|
buf := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(buf, uint16(len(frame)-EthernetOverhead))
|
|
return fwd.sendControlMsg(ProtocolPMTUVerified, buf)
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleMTUTestAck(msg []byte) error {
|
|
if len(msg) < 2 {
|
|
log.Print(fwd.logPrefix(), "Received truncated MTUTestAck")
|
|
return nil
|
|
}
|
|
|
|
mtu := int(binary.BigEndian.Uint16(msg))
|
|
log.Debug(fwd.logPrefix(), "handleMTUTestAck: for mtu candidate ", mtu)
|
|
if mtu != fwd.mtuCandidate {
|
|
return nil
|
|
}
|
|
|
|
fwd.mtuHighestGood = mtu
|
|
return fwd.searchMTU()
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) handleMTUTestFailure() error {
|
|
if fwd.mtuTestsSent < MTUVerifyAttempts {
|
|
return fwd.sendMTUTest()
|
|
}
|
|
|
|
log.Debug(fwd.logPrefix(), "handleMTUTestFailure")
|
|
fwd.mtuLowestBad = fwd.mtuCandidate
|
|
return fwd.searchMTU()
|
|
}
|
|
|
|
func (fwd *sleeveForwarder) searchMTU() error {
|
|
log.Debug(fwd.logPrefix(), "searchMTU: ", fwd.mtuHighestGood, fwd.mtuLowestBad)
|
|
|
|
if fwd.mtuHighestGood+1 >= fwd.mtuLowestBad {
|
|
mtu := fwd.mtuHighestGood
|
|
log.Print(fwd.logPrefix(), "Effective MTU verified at ", mtu)
|
|
|
|
if fwd.mtuTestTimeout != nil {
|
|
fwd.mtuTestTimeout.Stop()
|
|
fwd.mtuTestTimeout = nil
|
|
}
|
|
|
|
fwd.mtuCandidate = 0
|
|
fwd.maxPayload = mtu + fwd.overheadDF - UDPOverhead
|
|
fwd.mtu = mtu
|
|
return nil
|
|
}
|
|
|
|
fwd.mtuCandidate = (fwd.mtuHighestGood + fwd.mtuLowestBad) / 2
|
|
fwd.mtuTestsSent = 0
|
|
return fwd.sendMTUTest()
|
|
}
|
|
|
|
type udpSenderDF struct {
|
|
ipBuf gopacket.SerializeBuffer
|
|
opts gopacket.SerializeOptions
|
|
udpHeader *layers.UDP
|
|
localIP net.IP
|
|
remoteIP net.IP
|
|
socket *net.IPConn
|
|
}
|
|
|
|
func newUDPSenderDF(localIP net.IP, localPort int) *udpSenderDF {
|
|
return &udpSenderDF{
|
|
ipBuf: gopacket.NewSerializeBuffer(),
|
|
opts: gopacket.SerializeOptions{
|
|
FixLengths: true,
|
|
// UDP header is calculated with a phantom IP
|
|
// header. Yes, it's totally nuts. Thankfully,
|
|
// for UDP over IPv4, the checksum is
|
|
// optional. It's not optional for IPv6, but
|
|
// we'll ignore that for now. TODO
|
|
ComputeChecksums: false,
|
|
},
|
|
udpHeader: &layers.UDP{SrcPort: layers.UDPPort(localPort)},
|
|
localIP: localIP,
|
|
}
|
|
}
|
|
|
|
func (sender *udpSenderDF) dial() error {
|
|
if sender.socket != nil {
|
|
if err := sender.socket.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
sender.socket = nil
|
|
}
|
|
|
|
laddr := &net.IPAddr{IP: sender.localIP}
|
|
raddr := &net.IPAddr{IP: sender.remoteIP}
|
|
s, err := net.DialIP("ip4:UDP", laddr, raddr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f, err := s.File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
// This makes sure all packets we send out have DF set on them.
|
|
err = syscall.SetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_MTU_DISCOVER, syscall.IP_PMTUDISC_DO)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sender.socket = s
|
|
return nil
|
|
}
|
|
|
|
func (sender *udpSenderDF) send(msg []byte, raddr *net.UDPAddr) error {
|
|
// Ensure we have a socket sending to the right IP address
|
|
if sender.socket == nil || !sender.remoteIP.Equal(raddr.IP) {
|
|
sender.remoteIP = raddr.IP
|
|
if err := sender.dial(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
sender.udpHeader.DstPort = layers.UDPPort(raddr.Port)
|
|
payload := gopacket.Payload(msg)
|
|
err := gopacket.SerializeLayers(sender.ipBuf, sender.opts, sender.udpHeader, &payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
packet := sender.ipBuf.Bytes()
|
|
_, err = sender.socket.Write(packet)
|
|
if err == nil || PosixError(err) != syscall.EMSGSIZE {
|
|
return err
|
|
}
|
|
|
|
f, err := sender.socket.File()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
log.Debug("sleeve ->[", sender, "] expecting PMTU update (IP packet was ", len(packet), " bytes, payload was ", len(msg), " bytes)")
|
|
pmtu, err := syscall.GetsockoptInt(int(f.Fd()), syscall.IPPROTO_IP, syscall.IP_MTU)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return msgTooBigError{underlayPMTU: pmtu}
|
|
}
|
|
|
|
type msgTooBigError struct {
|
|
underlayPMTU int // actual pmtu, i.e. what the kernel told us
|
|
}
|
|
|
|
func (mtbe msgTooBigError) Error() string {
|
|
return fmt.Sprint("Msg too big error. PMTU is ", mtbe.underlayPMTU)
|
|
}
|
|
|
|
func (sender *udpSenderDF) close() error {
|
|
if sender.socket == nil {
|
|
return nil
|
|
}
|
|
|
|
return sender.socket.Close()
|
|
}
|
|
|
|
func udpAddrsEqual(a *net.UDPAddr, b *net.UDPAddr) bool {
|
|
return a.IP.Equal(b.IP) && a.Port == b.Port && a.Zone == b.Zone
|
|
}
|
|
|
|
func allZeros(s []byte) bool {
|
|
for _, b := range s {
|
|
if b != byte(0) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func setTimer(timer *time.Timer, d time.Duration) *time.Timer {
|
|
if timer == nil {
|
|
return time.NewTimer(d)
|
|
}
|
|
|
|
timer.Reset(d)
|
|
return timer
|
|
|
|
}
|
|
|
|
func timerChan(timer *time.Timer) <-chan time.Time {
|
|
if timer != nil {
|
|
return timer.C
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func tickerChan(ticker *time.Ticker) <-chan time.Time {
|
|
if ticker != nil {
|
|
return ticker.C
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func makeUDPAddr(addr *net.TCPAddr) *net.UDPAddr {
|
|
return &net.UDPAddr{IP: addr.IP, Port: addr.Port, Zone: addr.Zone}
|
|
}
|
|
|
|
// Look inside an error produced by the net package to get to the
|
|
// syscall.Errno at the root of the problem.
|
|
func PosixError(err error) error {
|
|
if operr, ok := err.(*net.OpError); ok {
|
|
err = operr.Err
|
|
}
|
|
|
|
// go1.5 wraps an Errno inside a SyscallError inside an OpError
|
|
if scerr, ok := err.(*os.SyscallError); ok {
|
|
err = scerr.Err
|
|
}
|
|
|
|
return err
|
|
}
|