tun_disabled: reply to ICMP Echo Request (#342)

This change allows a server running with `tun.disabled: true` (usually
a lighthouse) to still reply to ICMP EchoRequest packets. This allows
you to "ping" the lighthouse Nebula IP as a quick check to make sure the
tunnel is up, even when running with tun.disabled.

This is still gated by allowing `icmp` packets in the inbound firewall
rules.
This commit is contained in:
Wade Simmons 2021-03-01 11:09:41 -05:00 committed by GitHub
parent 27d9a67dda
commit a0583ebdca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 11 deletions

View File

@ -123,7 +123,7 @@ func Main(config *Config, configTest bool, buildVersion string, logger *logrus.L
switch { switch {
case config.GetBool("tun.disabled", false): case config.GetBool("tun.disabled", false):
tun = newDisabledTun(tunCidr, l) tun = newDisabledTun(tunCidr, config.GetInt("tun.tx_queue", 500), config.GetBool("stats.message_metrics", false), l)
case tunFd != nil: case tunFd != nil:
tun, err = newTunFromFd( tun, err = newTunFromFd(
*tunFd, *tunFd,

View File

@ -1,26 +1,43 @@
package nebula package nebula
import ( import (
"encoding/binary"
"fmt" "fmt"
"io" "io"
"net" "net"
"strings" "strings"
"github.com/rcrowley/go-metrics"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
type disabledTun struct { type disabledTun struct {
block chan struct{} read chan []byte
cidr *net.IPNet cidr *net.IPNet
logger *log.Logger logger *log.Logger
// Track these metrics since we don't have the tun device to do it for us
tx metrics.Counter
rx metrics.Counter
} }
func newDisabledTun(cidr *net.IPNet, l *log.Logger) *disabledTun { func newDisabledTun(cidr *net.IPNet, queueLen int, metricsEnabled bool, l *log.Logger) *disabledTun {
return &disabledTun{ tun := &disabledTun{
cidr: cidr, cidr: cidr,
block: make(chan struct{}), read: make(chan []byte, queueLen),
logger: l, logger: l,
} }
if metricsEnabled {
tun.tx = metrics.GetOrRegisterCounter("messages.tx.message", nil)
tun.rx = metrics.GetOrRegisterCounter("messages.rx.message", nil)
} else {
tun.tx = &metrics.NilCounter{}
tun.rx = &metrics.NilCounter{}
}
return tun
} }
func (*disabledTun) Activate() error { func (*disabledTun) Activate() error {
@ -36,12 +53,73 @@ func (*disabledTun) DeviceName() string {
} }
func (t *disabledTun) Read(b []byte) (int, error) { func (t *disabledTun) Read(b []byte) (int, error) {
<-t.block r, ok := <-t.read
if !ok {
return 0, io.EOF return 0, io.EOF
}
if len(r) > len(b) {
return 0, fmt.Errorf("packet larger than mtu: %d > %d bytes", len(r), len(b))
}
t.tx.Inc(1)
if l.Level >= logrus.DebugLevel {
t.logger.WithField("raw", prettyPacket(r)).Debugf("Write payload")
}
return copy(b, r), nil
}
func (t *disabledTun) handleICMPEchoRequest(b []byte) bool {
// Return early if this is not a simple ICMP Echo Request
if !(len(b) >= 28 && len(b) <= mtu && b[0] == 0x45 && b[9] == 0x01 && b[20] == 0x08) {
return false
}
// We don't support fragmented packets
if b[7] != 0 || (b[6]&0x2F != 0) {
return false
}
buf := make([]byte, len(b))
copy(buf, b)
// Swap dest / src IPs and recalculate checksum
ipv4 := buf[0:20]
copy(ipv4[12:16], b[16:20])
copy(ipv4[16:20], b[12:16])
ipv4[10] = 0
ipv4[11] = 0
binary.BigEndian.PutUint16(ipv4[10:], ipChecksum(ipv4))
// Change type to ICMP Echo Reply and recalculate checksum
icmp := buf[20:]
icmp[0] = 0
icmp[2] = 0
icmp[3] = 0
binary.BigEndian.PutUint16(icmp[2:], ipChecksum(icmp))
// attempt to write it, but don't block
select {
case t.read <- buf:
default:
t.logger.Debugf("tun_disabled: dropped ICMP Echo Reply response")
}
return true
} }
func (t *disabledTun) Write(b []byte) (int, error) { func (t *disabledTun) Write(b []byte) (int, error) {
t.rx.Inc(1)
// Check for ICMP Echo Request before spending time doing the full parsing
if t.handleICMPEchoRequest(b) {
if l.Level >= logrus.DebugLevel {
t.logger.WithField("raw", prettyPacket(b)).Debugf("Disabled tun responded to ICMP Echo Request")
}
} else if l.Level >= logrus.DebugLevel {
t.logger.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload") t.logger.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
}
return len(b), nil return len(b), nil
} }
@ -55,9 +133,9 @@ func (t *disabledTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
} }
func (t *disabledTun) Close() error { func (t *disabledTun) Close() error {
if t.block != nil { if t.read != nil {
close(t.block) close(t.read)
t.block = nil t.read = nil
} }
return nil return nil
} }
@ -76,3 +154,22 @@ func (p prettyPacket) String() string {
return s.String() return s.String()
} }
func ipChecksum(b []byte) uint16 {
var c uint32
sz := len(b) - 1
for i := 0; i < sz; i += 2 {
c += uint32(b[i]) << 8
c += uint32(b[i+1])
}
if sz%2 == 0 {
c += uint32(b[sz]) << 8
}
for (c >> 16) > 0 {
c = (c & 0xffff) + (c >> 16)
}
return ^uint16(c)
}