271 lines
7.9 KiB
Go
271 lines
7.9 KiB
Go
package weave
|
|
|
|
import (
|
|
"bytes"
|
|
"code.google.com/p/gopacket/layers"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"net"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
const macMaxAge = 10 * time.Minute
|
|
|
|
func NewRouter(iface *net.Interface, name PeerName, password []byte, connLimit int, bufSz int, logFrame func(string, []byte, *layers.Ethernet)) *Router {
|
|
router := &Router{
|
|
Iface: iface,
|
|
Macs: NewMacCache(macMaxAge),
|
|
Peers: NewPeerCache(),
|
|
ConnLimit: connLimit,
|
|
BufSz: bufSz,
|
|
LogFrame: logFrame}
|
|
if len(password) > 0 {
|
|
router.Password = &password
|
|
}
|
|
ourself := NewPeer(name, 0, 0, router)
|
|
router.Ourself = router.Peers.FetchWithDefault(ourself)
|
|
router.Ourself.StartLocalPeer()
|
|
log.Println("Local identity 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.ConnectionMaker = StartConnectionMaker(router)
|
|
router.Topology = StartTopology(router)
|
|
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("Local identity 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("Topology:\n%s", router.Topology))
|
|
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()
|
|
checkFrameTooBig := dec.CheckFrameTooBigFunc(pio)
|
|
mac := router.Iface.HardwareAddr
|
|
if router.Macs.Enter(mac, router.Ourself) {
|
|
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 {
|
|
return nil
|
|
}
|
|
if router.Macs.Enter(srcMac, router.Ourself) {
|
|
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 {
|
|
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 || dec.BroadcastFrame() {
|
|
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.
|
|
connRemote := NewRemoteConnection(router.Ourself, nil, tcpConn.RemoteAddr().String())
|
|
NewLocalConnection(connRemote, UnknownPeerName, 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 {
|
|
continue // TODO something different?
|
|
} else {
|
|
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
|
|
}
|
|
checkWarn(relayConn.Decryptor.IterateFrames(handleUDPPacket, udpPacket))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (router *Router) handleUDPPacketFunc(dec *EthernetDecoder, po PacketSink) FrameConsumer {
|
|
checkFrameTooBig := dec.CheckFrameTooBigFunc(po)
|
|
|
|
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)
|
|
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
|
|
srcMac := dec.eth.SrcMAC
|
|
|
|
if dstPeer != router.Ourself {
|
|
// it's not for us, we're just relaying it
|
|
if decodedLen == 0 {
|
|
return nil
|
|
}
|
|
if router.Macs.Enter(srcMac, srcPeer) {
|
|
log.Println("Discovered remote MAC", srcMac, "at", srcPeer.Name)
|
|
}
|
|
return checkFrameTooBig(router.Ourself.Relay(srcPeer, dstPeer, df, frame, dec))
|
|
}
|
|
|
|
if relayConn.Remote().Name == srcPeer.Name {
|
|
if frameLen == 0 {
|
|
relayConn.SetRemoteUDPAddr(sender)
|
|
return nil
|
|
} else if frameLen == FragTestSize && bytes.Equal(frame, FragTest) {
|
|
relayConn.SendTCP(ProtocolFragmentationReceivedByte)
|
|
return nil
|
|
} else if frameLen == PMTUDiscoverySize && bytes.Equal(frame, PMTUDiscovery) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
if decodedLen == 0 {
|
|
return nil
|
|
}
|
|
|
|
if dec.IsPMTUVerify() && relayConn.Remote().Name == srcPeer.Name {
|
|
frameLenBytes := []byte{0, 0}
|
|
binary.BigEndian.PutUint16(frameLenBytes, uint16(frameLen-EthernetOverhead))
|
|
relayConn.SendTCP(Concat(ProtocolPMTUVerifiedByte, frameLenBytes))
|
|
return nil
|
|
}
|
|
|
|
if router.Macs.Enter(srcMac, srcPeer) {
|
|
log.Println("Discovered remote MAC", srcMac, "at", srcPeer.Name)
|
|
}
|
|
router.LogFrame("Injecting", frame, &dec.eth)
|
|
checkWarn(po.WritePacket(frame))
|
|
dstPeer, found = router.Macs.Lookup(dec.eth.DstMAC)
|
|
if !found || dec.BroadcastFrame() || dstPeer != router.Ourself {
|
|
df := decodedLen == 2 && (dec.ip.Flags&layers.IPv4DontFragment != 0)
|
|
checkFrameTooBig(router.Ourself.RelayBroadcast(srcPeer, df, frame, dec))
|
|
}
|
|
return nil
|
|
}
|
|
}
|