Skip to content
Open
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
10 changes: 9 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ Docs <http://chains.rtfd.org>`__

Install/Run Stuff
-----------------
Want to see what's happening on your network right now? Just install chains and run 'netwatch'.
Install the base package first. Packet capture support is optional because
it needs native libpcap/Npcap build prerequisites on some platforms.

::

$ pip install chains
$ pip install "chains[capture]"

Want to see what's happening on your network right now? Install the capture
extra and run 'netwatch'.
::

$ netwatch -s
2015-09-07 19:08:34 - UDP IP 192.168.1.9(internal)--> 224.0.0.251(multicast_dns)
2015-09-07 19:08:34 - UDP IP6 fe80::6e40:8ff:fe89:fc08(internal) --> ff02::fb(multicast_dns)
Expand Down
31 changes: 25 additions & 6 deletions chains/sources/packet_streamer.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
"""PacketStreamer: Stream packets from a network interface"""
from __future__ import print_function
import os
import pcapy

try:
import pcapy
except ImportError:
pcapy = None

# Local imports
from chains.sources import source
from chains.utils import file_utils, log_utils
logger = log_utils.get_logger()


def _get_pcapy():
"""Return the optional packet capture module or raise an install hint."""
if pcapy is None:
raise ImportError(
'Packet capture support requires pcapy-ng. Install it with '
'`pip install chains[capture]`. On Windows, native build tools '
'and Npcap may also be required.'
)
return pcapy


class PacketStreamer(source.Source):
"""Stream out the packets from the given network interface

Expand All @@ -25,9 +40,11 @@ def __init__(self, iface_name=None, bpf=None, max_packets=None):
# Call super class init
super(PacketStreamer, self).__init__()

pcapy_module = _get_pcapy()

# Check if the interface name was specified, if not set it to the first device
if not iface_name:
devices = pcapy.findalldevs()
devices = pcapy_module.findalldevs()
iface_name = devices[0]
print('Auto Setting Interface to: {:s}'.format(iface_name))

Expand Down Expand Up @@ -65,20 +82,22 @@ def _iface_is_file(self):
def read_interface(self):
"""Read Packets from the packet capture interface"""

pcapy_module = _get_pcapy()

# Spin up the packet capture
if self._iface_is_file():
self.pcap = pcapy.open_offline(self.iface_name)
self.pcap = pcapy_module.open_offline(self.iface_name)
else:
try:
# self.pcap = pcap.pcap(name=self.iface_name, promisc=True, immediate=True)
# snaplen (maximum number of bytes to capture _per_packet_)
# promiscious mode (1 for true)
# timeout (in milliseconds)
self.pcap = pcapy.open_live(self.iface_name, 65536, 1, 0)
self.pcap = pcapy_module.open_live(self.iface_name, 65536, 1, 0)
except OSError:
try:
logger.warning('Could not get promisc mode, turning flag off')
self.pcap = pcapy.open_live(self.iface_name, 65536, 0, 0)
self.pcap = pcapy_module.open_live(self.iface_name, 65536, 0, 0)
except OSError:
log_utils.panic('Could no open interface with any options (may need to be sudo)')

Expand Down Expand Up @@ -110,7 +129,7 @@ def read_interface(self):
# All done so print out a small report
try:
print('Packet stats: %d received, %d dropped, %d dropped by interface' % self.pcap.stats())
except pcapy.PcapError:
except pcapy_module.PcapError:
print('No stats available...')


Expand Down
49 changes: 39 additions & 10 deletions chains/utils/net_utils.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,52 @@
"""Network utilities that might be useful"""
from __future__ import print_function

import sys
import socket
import json
import binascii
import netifaces

try:
import netifaces
except ImportError:
netifaces = None

# Local imports
from chains.utils import file_utils, compat
from chains.utils import compat


def _get_netifaces():
"""Return the optional network interface module or raise an install hint."""
if netifaces is None:
raise ImportError(
'Network interface lookup requires netifaces. Install it with '
'`pip install chains[capture]`. On Windows, native build tools '
'may also be required.'
)
return netifaces


def get_default_interface():
"""Grab the name of the local default network interface"""
return netifaces.gateways()['default'][netifaces.AF_INET][1]
netifaces_module = _get_netifaces()
return netifaces_module.gateways()['default'][netifaces_module.AF_INET][1]


def get_ip_address(iface_name):
"""Get the ip address of the named network interface"""
return netifaces.ifaddresses(iface_name)[netifaces.AF_INET][0]['addr']
netifaces_module = _get_netifaces()
return netifaces_module.ifaddresses(iface_name)[netifaces_module.AF_INET][0]['addr']


def get_mac_address(iface_name):
"""Get the mac address of the named network interface"""
return netifaces.ifaddresses(iface_name)[netifaces.AF_LINK][0]['addr']
netifaces_module = _get_netifaces()
return netifaces_module.ifaddresses(iface_name)[netifaces_module.AF_LINK][0]['addr']


def get_broadcast_address(iface_name):
"""Get the broadcast address of the named network interface"""
return netifaces.ifaddresses(iface_name)[netifaces.AF_INET][0]['broadcast']
netifaces_module = _get_netifaces()
return netifaces_module.ifaddresses(iface_name)[netifaces_module.AF_INET][0]['broadcast']


def mac_to_str(address):
"""Convert a MAC address to a readable/printable string
Expand All @@ -36,6 +58,7 @@ def mac_to_str(address):
"""
return ':'.join('%02x' % compat.ord(b) for b in address)


def str_to_mac(mac_string):
"""Convert a readable string to a MAC address

Expand All @@ -48,6 +71,7 @@ def str_to_mac(mac_string):
mac_string = ''.join(sp)
return binascii.unhexlify(mac_string)


def inet_to_str(inet):
"""Convert inet object to a string

Expand All @@ -62,6 +86,7 @@ def inet_to_str(inet):
except ValueError:
return socket.inet_ntop(socket.AF_INET6, inet)


def str_to_inet(address):
"""Convert an a string IP address to a inet struct

Expand All @@ -76,6 +101,7 @@ def str_to_inet(address):
except socket.error:
return socket.inet_pton(socket.AF_INET6, address)


def is_internal(ip_address):
"""Determine if the address is an internal ip address
Note: This is super bad, improve it
Expand All @@ -84,6 +110,7 @@ def is_internal(ip_address):
local_nets = '10.', '172.16.', '192.168.', '169.254', 'fd', 'fe80::'
return any([ip_address.startswith(local) for local in local_nets])


def is_special(ip_address):
"""Determine if the address is SPECIAL
Note: This is super bad, improve it
Expand All @@ -92,6 +119,7 @@ def is_special(ip_address):
'ff02::fb': 'multicast_dns'}
return special[ip_address] if ip_address in special else False


def test_utils():
"""Test the utility methods"""

Expand All @@ -105,13 +133,14 @@ def test_utils():
assert inet_to_str(b'\x91\xfe\xa0\xed') == '145.254.160.237'
assert str_to_inet('145.254.160.237') == b'\x91\xfe\xa0\xed'
assert is_internal('10.0.0.1')
assert is_internal('222.2.2.2') == False
assert is_internal('222.2.2.2') is False
assert is_special('224.0.0.251')
assert is_special('224.0.0.252') == False
assert is_special('224.0.0.252') is False

my_iface = get_default_interface()
print(get_mac_address(my_iface))
print('Success!')


if __name__ == '__main__':
test_utils()
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ release = sdist bdist_wheel upload -r pypi
test_release = sdist bdist_wheel upload -r pypitest

[metadata]
description-file = README.md
description_file = README.rst

[flake8]
max-line-length = 140
Expand Down
15 changes: 7 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import os
import sys

from setuptools import setup

# Project Information
# Project Information
dist_name = 'chains'
package_name = 'chains'
description = 'Exploratory Python Chained Generator Project'
Expand All @@ -13,8 +10,8 @@
scripts = ['scripts/netwatch', 'scripts/urlwatch', 'scripts/weird_dns']
packages = ['chains', 'chains.links', 'chains.sinks', 'chains.sources', 'chains.utils']

requirements = ['pcapy', 'dpkt', 'netifaces']
test_requirements = []
requirements = ['dpkt']
capture_requirements = ['pcapy-ng', 'netifaces']

# Pull in the version from the package
package = __import__(package_name)
Expand All @@ -31,6 +28,9 @@
scripts=scripts,
packages=packages,
install_requires=requirements,
extras_require={
'capture': capture_requirements,
},
license='MIT',
classifiers=[
'Development Status :: 2 - Pre-Alpha',
Expand All @@ -42,6 +42,5 @@
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
],
tests_require=test_requirements
]
)