Files
weave/router/router.go
T
Matthias Radestock 247ebaaf19 ignore heartbeats from the 'wrong' connection
The router may receive heartbeat packets that originated from (the
other end of) a different connection with the remote peer than the
current one. If we don't detect that, we may erroneously tell the
remote peer to mark the connection as 'established'. Which can lead to
a crash.

To enable detection, we include the connection UID - which is the same
at both ends - in the heartbeat frame. That way we can simply ignore
heartbeats with a different connection UID than the current connection
to the remote peer.

Previously heartbeat frames were zero length. In order to include the
64 bit connection UID we could just send 8 octets instead. However,
that would potentially trip up the Ethernet decoder at the other end,
and in any case we might as well make heartbeats fit the format of
other special frames, namely having an all-zero Ethernet header. This
in fact results in a simplification of the special frame handling,
since heartbeats are no longer *extra* special.

Fixes #352.
2015-01-17 18:45:19 +00:00

310 lines
9.3 KiB
Go

package router
import (
"bytes"
"code.google.com/p/gopacket/layers"
"encoding/binary"
"fmt"
"io"
"log"
"net"
"syscall"
"time"
)
const macMaxAge = 10 * time.Minute // [1]
// [1] should be greater than typical ARP cache expiries, i.e. > 3/2 *
// /proc/sys/net/ipv4_neigh/*/base_reachable_time_ms on Linux
func NewRouter(iface *net.Interface, name PeerName, password []byte, connLimit int, bufSz int, logFrame func(string, []byte, *layers.Ethernet)) *Router {
onMacExpiry := func(mac net.HardwareAddr, peer *Peer) {
log.Println("Expired MAC", mac, "at", peer.Name)
}
onPeerGC := func(peer *Peer) {
log.Println("Removing unreachable", peer)
}
router := &Router{
Iface: iface,
Macs: NewMacCache(macMaxAge, onMacExpiry),
ConnLimit: connLimit,
BufSz: bufSz,
LogFrame: logFrame}
if len(password) > 0 {
router.Password = &password
}
router.Ourself = StartLocalPeer(name, router)
router.Peers = NewPeers(router.Ourself.Peer, router.Macs, onPeerGC)
router.Peers.FetchWithDefault(router.Ourself.Peer)
log.Println("Our name is", router.Ourself.Name)
return router
}
func (router *Router) UsingPassword() bool {
return router.Password != nil
}
func (router *Router) Start() {
// we need two pcap handles since they aren't thread-safe
pio, err := NewPcapIO(router.Iface.Name, router.BufSz)
checkFatal(err)
po, err := NewPcapO(router.Iface.Name)
checkFatal(err)
router.Routes = StartRoutes(router.Ourself.Peer, router.Peers)
router.ConnectionMaker = StartConnectionMaker(router.Ourself, router.Peers)
router.UDPListener = router.listenUDP(Port, po)
router.listenTCP(Port)
router.sniff(pio)
}
func (router *Router) Status() string {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintln("Our name is", router.Ourself.Name))
buf.WriteString(fmt.Sprintln("Sniffing traffic on", router.Iface))
buf.WriteString(fmt.Sprintf("MACs:\n%s", router.Macs))
buf.WriteString(fmt.Sprintf("Peers:\n%s", router.Peers))
buf.WriteString(fmt.Sprintf("Routes:\n%s", router.Routes))
buf.WriteString(fmt.Sprintf("Reconnects:\n%s", router.ConnectionMaker))
return buf.String()
}
func (router *Router) sniff(pio PacketSourceSink) {
log.Println("Sniffing traffic on", router.Iface)
dec := NewEthernetDecoder()
injectFrame := func(frame []byte) error { return pio.WritePacket(frame) }
checkFrameTooBig := func(err error) error { return dec.CheckFrameTooBig(err, injectFrame) }
mac := router.Iface.HardwareAddr
if router.Macs.Enter(mac, router.Ourself.Peer) {
log.Println("Discovered our MAC", mac)
}
go func() {
for {
pkt, err := pio.ReadPacket()
checkFatal(err)
router.LogFrame("Sniffed", pkt, nil)
checkWarn(router.handleCapturedPacket(pkt, dec, checkFrameTooBig))
}
}()
}
func (router *Router) handleCapturedPacket(frameData []byte, dec *EthernetDecoder, checkFrameTooBig func(error) error) error {
dec.DecodeLayers(frameData)
decodedLen := len(dec.decoded)
if decodedLen == 0 {
return nil
}
srcMac := dec.eth.SrcMAC
srcPeer, found := router.Macs.Lookup(srcMac)
// We need to filter out frames we injected ourselves. For such
// frames, the srcMAC will have been recorded as associated with a
// different peer.
if found && srcPeer != router.Ourself.Peer {
return nil
}
if router.Macs.Enter(srcMac, router.Ourself.Peer) {
log.Println("Discovered local MAC", srcMac)
}
if dec.DropFrame() {
return nil
}
dstMac := dec.eth.DstMAC
dstPeer, found := router.Macs.Lookup(dstMac)
if found && dstPeer == router.Ourself.Peer {
return nil
}
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
if df {
router.LogFrame("Forwarding DF", frameData, &dec.eth)
} else {
router.LogFrame("Forwarding", frameData, &dec.eth)
}
// at this point we are handing over the frame to forwarders, so
// we need to make a copy of it in order to prevent the next
// capture from overwriting the data
frameLen := len(frameData)
frameCopy := make([]byte, frameLen, frameLen)
copy(frameCopy, frameData)
if !found {
return checkFrameTooBig(router.Ourself.Broadcast(df, frameCopy, dec))
} else {
return checkFrameTooBig(router.Ourself.Forward(dstPeer, df, frameCopy, dec))
}
}
func (router *Router) listenTCP(localPort int) {
localAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprint(":", localPort))
checkFatal(err)
ln, err := net.ListenTCP("tcp4", localAddr)
checkFatal(err)
go func() {
defer ln.Close()
for {
tcpConn, err := ln.AcceptTCP()
if err != nil {
log.Println(err)
continue
}
router.acceptTCP(tcpConn)
}
}()
}
func (router *Router) acceptTCP(tcpConn *net.TCPConn) {
// someone else is dialing us, so our udp sender is the conn
// on Port and we wait for them to send us something on UDP to
// start.
remoteAddrStr := tcpConn.RemoteAddr().String()
log.Printf("->[%s] connection accepted\n", remoteAddrStr)
connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr)
NewLocalConnection(connRemote, true, tcpConn, nil, router)
}
func (router *Router) listenUDP(localPort int, po PacketSink) *net.UDPConn {
localAddr, err := net.ResolveUDPAddr("udp4", fmt.Sprint(":", localPort))
checkFatal(err)
conn, err := net.ListenUDP("udp4", localAddr)
checkFatal(err)
f, err := conn.File()
defer f.Close()
checkFatal(err)
fd := int(f.Fd())
// This one 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)
checkFatal(err)
go router.udpReader(conn, po)
return conn
}
func (router *Router) udpReader(conn *net.UDPConn, po PacketSink) {
defer conn.Close()
dec := NewEthernetDecoder()
handleUDPPacket := router.handleUDPPacketFunc(dec, po)
buf := make([]byte, MaxUDPPacketSize)
for {
n, sender, err := conn.ReadFromUDP(buf)
if err == io.EOF {
return
} else if err != nil {
log.Println("ignoring UDP read error", err)
continue
} else if n < NameSize {
log.Println("ignoring too short UDP packet from", sender)
continue
}
name := PeerNameFromBin(buf[:NameSize])
packet := make([]byte, n-NameSize)
copy(packet, buf[NameSize:n])
udpPacket := &UDPPacket{
Name: name,
Packet: packet,
Sender: sender}
peerConn, found := router.Ourself.ConnectionTo(name)
if !found {
continue
}
relayConn, ok := peerConn.(*LocalConnection)
if !ok {
continue
}
err = relayConn.Decryptor.IterateFrames(handleUDPPacket, udpPacket)
if pde, ok := err.(PacketDecodingError); ok {
if pde.Fatal {
relayConn.CheckFatal(pde)
} else {
relayConn.log(pde.Error())
}
} else {
checkWarn(err)
}
}
}
func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, po PacketSink) FrameConsumer {
checkFrameTooBig := func(err error, srcPeer *Peer) error {
if err == nil { // optimisation: avoid closure creation in common case
return nil
}
return dec.CheckFrameTooBig(err,
func(icmpFrame []byte) error {
return router.Ourself.Forward(srcPeer, false, icmpFrame, nil)
})
}
return func(relayConn *LocalConnection, sender *net.UDPAddr, srcNameByte, dstNameByte []byte, frameLen uint16, frame []byte) error {
srcName := PeerNameFromBin(srcNameByte)
dstName := PeerNameFromBin(dstNameByte)
srcPeer, found := router.Peers.Fetch(srcName)
if !found {
return nil
}
dstPeer, found := router.Peers.Fetch(dstName)
if !found {
return nil
}
dec.DecodeLayers(frame)
decodedLen := len(dec.decoded)
if decodedLen == 0 {
return nil
}
// 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 != relayConn.Remote() || dstPeer != router.Ourself.Peer {
// A special frame not originating from the remote, or
// not for us? How odd; let's just drop it.
return nil
}
switch {
case frameLen == EthernetOverhead + 8:
relayConn.ReceivedHeartbeat(sender, binary.BigEndian.Uint64(frame[EthernetOverhead:]))
case frameLen == FragTestSize && bytes.Equal(frame, FragTest):
relayConn.SendTCP(ProtocolFragmentationReceivedByte)
case frameLen == PMTUDiscoverySize && bytes.Equal(frame, PMTUDiscovery):
default:
frameLenBytes := []byte{0, 0}
binary.BigEndian.PutUint16(frameLenBytes, uint16(frameLen-EthernetOverhead))
relayConn.SendTCP(Concat(ProtocolPMTUVerifiedByte, frameLenBytes))
}
return nil
}
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
if dstPeer != router.Ourself.Peer {
// it's not for us, we're just relaying it
if df {
router.LogFrame("Relaying DF", frame, &dec.eth)
} else {
router.LogFrame("Relaying", frame, &dec.eth)
}
return checkFrameTooBig(router.Ourself.Relay(srcPeer, dstPeer, df, frame, dec), srcPeer)
}
srcMac := dec.eth.SrcMAC
dstMac := dec.eth.DstMAC
if router.Macs.Enter(srcMac, srcPeer) {
log.Println("Discovered remote MAC", srcMac, "at", srcName)
}
router.LogFrame("Injecting", frame, &dec.eth)
checkWarn(po.WritePacket(frame))
dstPeer, found = router.Macs.Lookup(dstMac)
if !found || dstPeer != router.Ourself.Peer {
return checkFrameTooBig(router.Ourself.RelayBroadcast(srcPeer, df, frame, dec), srcPeer)
}
return nil
}
}