-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Changes from 25 commits
ffd96e6
46db28a
1c2146a
637eaab
1ff4818
7fc011e
4e140f3
9ef3f56
70dc0cd
fbe7398
8c87687
c00eb6f
24db049
1d2f681
185fbff
8e83262
22048ec
49cd7a6
fa877f5
2065731
cdd6069
24109ff
4bafbee
ae6bf1c
498c230
8a99e60
93e48ac
23d5948
9111121
52ce33e
70d02b0
ef0ae7d
7bb2402
a00e370
941f534
bbcc013
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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) | ||
} | ||
|
||
h := &afpacketHandle{ | ||
promiscPreviousState: promiscEnabled, | ||
device: device, | ||
promiscPreviousStateDetected: err == nil, | ||
} | ||
|
||
if err := setPromiscMode(device, true); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||
|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it says There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
} |
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"]) |
There was a problem hiding this comment.
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.