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 {
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:
tun, err = newTunFromFd(
*tunFd,

View File

@ -1,26 +1,43 @@
package nebula
import (
"encoding/binary"
"fmt"
"io"
"net"
"strings"
"github.com/rcrowley/go-metrics"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
)
type disabledTun struct {
block chan struct{}
read chan []byte
cidr *net.IPNet
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 {
return &disabledTun{
func newDisabledTun(cidr *net.IPNet, queueLen int, metricsEnabled bool, l *log.Logger) *disabledTun {
tun := &disabledTun{
cidr: cidr,
block: make(chan struct{}),
read: make(chan []byte, queueLen),
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 {
@ -36,12 +53,73 @@ func (*disabledTun) DeviceName() string {
}
func (t *disabledTun) Read(b []byte) (int, error) {
<-t.block
return 0, io.EOF
r, ok := <-t.read
if !ok {
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) {
t.logger.WithField("raw", prettyPacket(b)).Debugf("Disabled tun received unexpected payload")
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")
}
return len(b), nil
}
@ -55,9 +133,9 @@ func (t *disabledTun) NewMultiQueueReader() (io.ReadWriteCloser, error) {
}
func (t *disabledTun) Close() error {
if t.block != nil {
close(t.block)
t.block = nil
if t.read != nil {
close(t.read)
t.read = nil
}
return nil
}
@ -76,3 +154,22 @@ func (p prettyPacket) String() 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)
}