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

packetbeat: Enable setting promiscuous mode automatically #11366

Merged
merged 36 commits into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ffd96e6
Enable setting of promiscuous mode
michalpristas Mar 20, 2019
46db28a
seccomp disabled
michalpristas Mar 20, 2019
1c2146a
Merge branch 'master' of https://github.com/elastic/beats into fix-700
michalpristas Mar 21, 2019
637eaab
update faq
michalpristas Mar 21, 2019
1ff4818
changelog
michalpristas Mar 21, 2019
7fc011e
fix
michalpristas Mar 21, 2019
4e140f3
mage update
michalpristas Mar 21, 2019
9ef3f56
Merge branch 'master' into fix-700
michalpristas Mar 21, 2019
70dc0cd
syscall
michalpristas Mar 21, 2019
fbe7398
syscall
michalpristas Mar 21, 2019
8c87687
whitespaces
michalpristas Mar 21, 2019
c00eb6f
typo
michalpristas Mar 21, 2019
24db049
review round
michalpristas Mar 21, 2019
1d2f681
not breaking
michalpristas Mar 22, 2019
185fbff
Merge branch 'master' into fix-700
michalpristas Mar 22, 2019
8e83262
first attemp for system test
michalpristas Mar 22, 2019
22048ec
it's autopep8 style :notes:
michalpristas Mar 22, 2019
49cd7a6
more strict for devices count
michalpristas Mar 22, 2019
fa877f5
import unit test
michalpristas Mar 22, 2019
2065731
fixed tests :party:
michalpristas Mar 22, 2019
cdd6069
Merge branch 'master' into fix-700
michalpristas Mar 26, 2019
24109ff
use test environment
michalpristas Mar 26, 2019
4bafbee
Merge branch 'master' into fix-700
michalpristas Mar 26, 2019
ae6bf1c
Merge branch 'master' into fix-700
michalpristas Mar 29, 2019
498c230
Merge branch 'master' into fix-700
michalpristas Mar 29, 2019
8a99e60
Merge branch 'master' into fix-700
michalpristas Apr 1, 2019
93e48ac
Merge branch 'master' into fix-700
michalpristas Apr 8, 2019
23d5948
configurable promisc mode
michalpristas Apr 8, 2019
9111121
meta reference
michalpristas Apr 8, 2019
52ce33e
invalid condition, shame on me
michalpristas Apr 8, 2019
70d02b0
Merge branch 'master' into fix-700
michalpristas Apr 12, 2019
ef0ae7d
Merge branch 'master' into fix-700
michalpristas Feb 5, 2020
7bb2402
changelog merge fix
michalpristas Feb 5, 2020
a00e370
updated dockerfile for packetbeat
michalpristas Feb 5, 2020
941f534
added comment
michalpristas Feb 5, 2020
bbcc013
updated config file
michalpristas Feb 5, 2020
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 @@ -228,6 +228,7 @@ https://github.com/elastic/beats/compare/v7.0.0-alpha2...master[Check the HEAD d
- Fixed a crash when using af_packet capture {pull}10477[10477]
- Prevent duplicate packet loss error messages in HTTP events. {pull}10709[10709]
- Avoid reporting unknown MongoDB opcodes more than once. {pull}10878[10878]
- 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.11.5

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
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
66 changes: 63 additions & 3 deletions packetbeat/sniffer/afpacket_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,42 @@
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) {

h := &afpacketHandle{}
var err error
promiscEnabled, err := isPromiscEnabled(device)
if err != nil {
logp.Err("Failed to get promiscuous mode for device '%s': %v", device, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it fails here then when closing we don't really know what the previous state was so we should not try to restore it based on this value.

}

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

if err := setPromiscMode(device, true); err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why change mode if it's already enabled?

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)
}

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

func (h *afpacketHandle) Close() {
h.TPacket.Close()
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
}

func setPromiscMode(device string, enabled bool) error {
if device == "any" {
logp.Warn("Cannot set promiscuous mode to device 'any'")
return nil
}

return syscall.SetLsfPromisc(device, enabled)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function says its deprecated. Have you checked out what it recommends to use?

Copy link
Contributor Author

@michalpristas michalpristas Mar 21, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it says x/net where I did not find anything useful. so, for now, I went with this with a possible rewrite to direct syscalls

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's at least add a code comment here explaining the issue + follow up issue in repo.

}
5 changes: 5 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,11 @@
# 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
{% 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"])