Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick #11366 to 7.x: packetbeat: Enable setting promiscuous mode automatically #16104

Merged
merged 1 commit into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d

*Packetbeat*

- Enable setting promiscuous mode automatically. {pull}11366[11366]

*Winlogbeat*

Expand Down
15 changes: 15 additions & 0 deletions packetbeat/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:1.13.7

RUN \
apt-get update \
&& apt-get install -y --no-install-recommends \
python-pip \
virtualenv \
librpm-dev \
netcat \
libpcap-dev \
&& rm -rf /var/lib/apt/lists/*

RUN pip install --upgrade pip
RUN pip install --upgrade setuptools
RUN pip install --upgrade docker-compose==1.23.2
2 changes: 1 addition & 1 deletion packetbeat/Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
BEAT_NAME?=packetbeat
BEAT_TITLE?=Packetbeat
SYSTEM_TESTS?=true
TEST_ENVIRONMENT=false
TEST_ENVIRONMENT?=true
ES_BEATS?=..
EXCLUDE_COMMON_UPDATE_TARGET=true

Expand Down
7 changes: 7 additions & 0 deletions packetbeat/_meta/beat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ packetbeat.interfaces.device: {{ call .device .GOOS }}
# Use this setting to override the automatically generated BPF filter.
#packetbeat.interfaces.bpf_filter:

# With `auto_promisc_mode` Packetbeat puts interface in promiscuous mode automatically on startup.
# This option does not work with `any` interface device.
# The default option is false and requires manual set-up of promiscuous mode.
# Warning: under some circumstances (e.g beat crash) promiscuous mode
# can stay enabled even after beat is shut down.
#packetbeat.interfaces.auto_promisc_mode: true

#================================== Flows =====================================

packetbeat.flows:
Expand Down
23 changes: 12 additions & 11 deletions packetbeat/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,18 @@ type Config struct {
}

type InterfacesConfig struct {
Device string `config:"device"`
Type string `config:"type"`
File string `config:"file"`
WithVlans bool `config:"with_vlans"`
BpfFilter string `config:"bpf_filter"`
Snaplen int `config:"snaplen"`
BufferSizeMb int `config:"buffer_size_mb"`
TopSpeed bool
Dumpfile string
OneAtATime bool
Loop int
Device string `config:"device"`
Type string `config:"type"`
File string `config:"file"`
WithVlans bool `config:"with_vlans"`
BpfFilter string `config:"bpf_filter"`
Snaplen int `config:"snaplen"`
BufferSizeMb int `config:"buffer_size_mb"`
EnableAutoPromiscMode bool `config:"auto_promisc_mode"`
TopSpeed bool
Dumpfile string
OneAtATime bool
Loop int
}

type Flows struct {
Expand Down
37 changes: 37 additions & 0 deletions packetbeat/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
version: '2.3'
services:
beat:
build: ${PWD}/.
depends_on:
- proxy_dep
working_dir: /go/src/github.com/elastic/beats/packetbeat
environment:
- ES_HOST=elasticsearch
- ES_PORT=9200
- ES_USER=beats
- ES_PASS=testing
- KIBANA_HOST=kibana
- KIBANA_PORT=5601
volumes:
- ${PWD}/..:/go/src/github.com/elastic/beats/
command: make
privileged: true
pid: host

# This is a proxy used to block beats until all services are healthy.
# See: https://github.com/docker/compose/issues/4369
proxy_dep:
image: busybox
depends_on:
elasticsearch: { condition: service_healthy }
kibana: { condition: service_healthy }

elasticsearch:
extends:
file: ../testing/environments/${TESTING_ENVIRONMENT}.yml
service: elasticsearch

kibana:
extends:
file: ../testing/environments/${TESTING_ENVIRONMENT}.yml
service: kibana
20 changes: 20 additions & 0 deletions packetbeat/docs/packetbeat-options.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,26 @@ packetbeat.interfaces.type: af_packet
packetbeat.interfaces.buffer_size_mb: 100
------------------------------------------------------------------------------

[float]
==== `auto_promisc_mode`

With `auto_promisc_mode` Packetbeat puts interface in promiscuous mode automatically on startup.
This option does not work with `any` interface device.
The default option is false and requires manual set-up of promiscuous mode.
Warning: under some circumstances (e.g beat crash) promiscuous mode
can stay enabled even after beat is shut down.

Example:

[source,yaml]
------------------------------------------------------------------------------
packetbeat.interfaces.device: eth0
packetbeat.interfaces.type: af_packet
packetbeat.interfaces.buffer_size_mb: 100
packetbeat.interfaces.auto_promisc_mode: true
------------------------------------------------------------------------------


[float]
==== `with_vlans`

Expand Down
7 changes: 7 additions & 0 deletions packetbeat/packetbeat.reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ packetbeat.interfaces.device: any
# Use this setting to override the automatically generated BPF filter.
#packetbeat.interfaces.bpf_filter:

# With `auto_promisc_mode` Packetbeat puts interface in promiscuous mode automatically on startup.
# This option does not work with `any` interface device.
# The default option is false and requires manual set-up of promiscuous mode.
# Warning: under some circumstances (e.g beat crash) promiscuous mode
# can stay enabled even after beat is shut down.
#packetbeat.interfaces.auto_promisc_mode: true

#================================== Flows =====================================

packetbeat.flows:
Expand Down
80 changes: 77 additions & 3 deletions packetbeat/sniffer/afpacket_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,49 @@
package sniffer

import (
"fmt"
"syscall"
"time"
"unsafe"

"github.com/elastic/beats/libbeat/logp"

"github.com/tsg/gopacket"
"github.com/tsg/gopacket/afpacket"
"github.com/tsg/gopacket/layers"
)

type afpacketHandle struct {
TPacket *afpacket.TPacket
TPacket *afpacket.TPacket
promiscPreviousState bool
promiscPreviousStateDetected bool
device string
}

func newAfpacketHandle(device string, snaplen int, block_size int, num_blocks int,
timeout time.Duration) (*afpacketHandle, error) {
timeout time.Duration, autoPromiscMode bool) (*afpacketHandle, error) {

h := &afpacketHandle{}
var err error
var promiscEnabled bool

if autoPromiscMode {
promiscEnabled, err = isPromiscEnabled(device)
if err != nil {
logp.Err("Failed to get promiscuous mode for device '%s': %v", device, err)
}

if !promiscEnabled {
if setPromiscErr := setPromiscMode(device, true); setPromiscErr != nil {
logp.Warn("Failed to set promiscuous mode for device '%s'. Packetbeat may be unable to see any network traffic. Please follow packetbeat FAQ to learn about mitigation: Error: %v", device, err)
}
}
}

h := &afpacketHandle{
promiscPreviousState: promiscEnabled,
device: device,
promiscPreviousStateDetected: autoPromiscMode && err == nil,
}

if device == "any" {
h.TPacket, err = afpacket.NewTPacket(
Expand Down Expand Up @@ -69,4 +96,51 @@ func (h *afpacketHandle) LinkType() layers.LinkType {

func (h *afpacketHandle) Close() {
h.TPacket.Close()
// previous state detected only if auto mode was on
if h.promiscPreviousStateDetected {
if err := setPromiscMode(h.device, h.promiscPreviousState); err != nil {
logp.Warn("Failed to reset promiscuous mode for device '%s'. Your device might be in promiscuous mode.: %v", h.device, err)
}
}
}

func isPromiscEnabled(device string) (bool, error) {
if device == "any" {
return false, nil
}

s, e := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
if e != nil {
return false, e
}

defer syscall.Close(s)

var ifreq struct {
name [syscall.IFNAMSIZ]byte
flags uint16
}

copy(ifreq.name[:], []byte(device))
_, _, ep := syscall.Syscall(syscall.SYS_IOCTL, uintptr(s), syscall.SIOCGIFFLAGS, uintptr(unsafe.Pointer(&ifreq)))
if ep != 0 {
return false, fmt.Errorf("ioctl command SIOCGIFFLAGS failed to get device flags for %v: return code %d", device, ep)
}

return ifreq.flags&uint16(syscall.IFF_PROMISC) != 0, nil
}

// setPromiscMode enables promisc mode if configured.
// this makes maintenance for user simpler without any additional manual steps
// issue [700](https://github.com/elastic/beats/issues/700)
func setPromiscMode(device string, enabled bool) error {
if device == "any" {
logp.Warn("Cannot set promiscuous mode to device 'any'")
return nil
}

// SetLsfPromisc is marked as deprecated but used to improve readability (bpf)
// and avoid Cgo (pcap)
// TODO: replace with x/net/bpf or pcap
return syscall.SetLsfPromisc(device, enabled)
}
2 changes: 1 addition & 1 deletion packetbeat/sniffer/afpacket_nonlinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ type afpacketHandle struct {
}

func newAfpacketHandle(device string, snaplen int, blockSize int, numBlocks int,
timeout time.Duration) (*afpacketHandle, error) {
timeout time.Duration, enableAutoPromiscMode bool) (*afpacketHandle, error) {

return nil, fmt.Errorf("Afpacket MMAP sniffing is only available on Linux")
}
Expand Down
2 changes: 1 addition & 1 deletion packetbeat/sniffer/sniffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func openAFPacket(filter string, cfg *config.InterfacesConfig) (snifferHandle, e
}

timeout := 500 * time.Millisecond
h, err := newAfpacketHandle(cfg.Device, szFrame, szBlock, numBlocks, timeout)
h, err := newAfpacketHandle(cfg.Device, szFrame, szBlock, numBlocks, timeout, cfg.EnableAutoPromiscMode)
if err != nil {
return nil, err
}
Expand Down
6 changes: 6 additions & 0 deletions packetbeat/tests/system/config/packetbeat.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
# keyword to sniff on all connected interfaces.
packetbeat.interfaces.device: {{ iface_device|default("any") }}

{% if af_packet %}
packetbeat.interfaces.type: af_packet
packetbeat.interfaces.buffer_size_mb: 100
packetbeat.interfaces.auto_promisc_mode: true
{% endif %}

{% if bpf_filter %}
packetbeat.interfaces.bpf_filter: {{ bpf_filter }}
{% endif %}
Expand Down
84 changes: 84 additions & 0 deletions packetbeat/tests/system/test_0069_af_packet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import os
import subprocess
import sys
import time
import unittest
from packetbeat import BaseTest

"""
Tests for afpacket.
"""


def is_root():
if 'geteuid' not in dir(os):
return False
euid = os.geteuid()
print("euid is", euid)
return euid == 0


class Test(BaseTest):

@unittest.skipUnless(
sys.platform.startswith("linux"),
"af_packet only on Linux")
@unittest.skipUnless(is_root(), "Requires root")
def test_afpacket_promisc(self):
"""
Should switch to promisc mode and back.
"""

# get device name, leave out loopback device
devices = [f for f in os.listdir(
"/sys/class/net") if f.startswith("lo")]
assert len(devices) > 0

device = devices[0]

ip_proc = subprocess.Popen(
["ip", "link", "show", device], stdout=subprocess.PIPE)
o, e = ip_proc.communicate()
assert e is None

prev_promisc = "PROMISC" in o.decode("utf-8")

# turn off promics if was on
if prev_promisc:
subprocess.call(["ip", "link", "set", device,
"promisc", "off"], stdout=subprocess.PIPE)

self.render_config_template(
af_packet=True,
iface_device=device
)
packetbeat = self.start_packetbeat()

# wait for promisc to be turned on, cap(90s)
for x in range(10):
time.sleep(5)

ip_proc = subprocess.Popen(
["ip", "link", "show", device], stdout=subprocess.PIPE)
o, e = ip_proc.communicate()

is_promisc = "PROMISC" in o.decode("utf-8")
if is_promisc:
break

assert is_promisc

# stop packetbeat and check if promisc is set to previous state
packetbeat.kill_and_wait()

ip_proc = subprocess.Popen(
["ip", "link", "show", device], stdout=subprocess.PIPE)
o, e = ip_proc.communicate()
assert e is None

is_promisc = "PROMISC" in o.decode("utf-8")
assert is_promisc == False

# reset device
if prev_promisc:
subprocess.call(["ip", "link", "set", device, "promisc", "on"])