Initial commit

This commit is contained in:
Matt Fox 2011-05-12 13:45:14 -07:00
commit 09a000ac50
9 changed files with 5057 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.so
build/

1
CHANGES.txt Normal file
View File

@ -0,0 +1 @@
v0.1, 12 May 2011 -- Initial release.

24
LICENSE.txt Normal file
View File

@ -0,0 +1,24 @@
Copyright (c) 2011, Kerkhoff Technologies, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Kerkhoff Technologies, Inc. nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

5
MANIFEST Normal file
View File

@ -0,0 +1,5 @@
CHANGES.txt
LICENSE.txt
README.txt
netfilterqueue.pyx
setup.py

69
README.txt Normal file
View File

@ -0,0 +1,69 @@
==============
NetfilterQueue
==============
NetfilterQueue provides access to packets matched by an iptables rule in
Linux. Packets so matched can be accepted, dropped, altered, or given a mark.
Libnetfilter_queue (the netfilter library, not this module) is part of the `Netfilter project <http://netfilter.org/projects/libnetfilter_queue/>`_.
Example
=======
Coming soon...
Installation
============
NetfilterQueue is a C extention module that links against libnetfilter_queue. Before installing, ensure you have:
1. A C compiler
2. Python development files
3. Libnetfilter_queue development files and associated dependencies
On Debian or Ubuntu, these files are install with::
sudo apt-get install build-essential python-dev libnetfilter-queue-dev
From PyPI
---------
To install from PyPI by pip::
pip install netfilterqueue
From source
-----------
To install from source::
wget http://pypi.python.org/packages/source/n/netfilterqueue/netfilterqueue-0.1.tar.gz
tar -xvzf netfilterqueue-0.1.tar.gz
cd netfilterqueue-0.1
python setup.py install
Setup will use Cython if it is installed, regenerating the .c source from the .pyx before compiling the .so.
Usage
=====
Coming soon...
Now route packets to the queue::
# iptables -I INPUT -p tcp --dport 80 -j NFQUEUE --queue-num 1
The only special part of the rule is the target. Rules can have any match and
can be added to any table or chain.
Valid queue numbers are integers from 0 to 65,536 inclusive.
Limitations
===========
TODO: fix this up
* compiled to max 2048-byte packets, so won't work on LO?
* full API not implemented: omits set_payload(), interface methods, and what else?

4541
netfilterqueue.c Normal file

File diff suppressed because it is too large Load Diff

191
netfilterqueue.pxd Normal file
View File

@ -0,0 +1,191 @@
cdef extern from "sys/types.h":
ctypedef unsigned char u_int8_t
ctypedef unsigned short int u_int16_t
ctypedef unsigned int u_int32_t
cdef extern from "netinet/ip.h":
struct iphdr:
u_int8_t tos
u_int16_t tot_len
u_int16_t id
u_int16_t frag_off
u_int8_t ttl
u_int8_t protocol
u_int16_t check
u_int32_t saddr
u_int32_t daddr
# Dummy defines from netinet/in.h:
cdef enum:
IPPROTO_IP = 0 # Dummy protocol for TCP.
IPPROTO_HOPOPTS = 0 # IPv6 Hop-by-Hop options.
IPPROTO_ICMP = 1 # Internet Control Message Protocol.
IPPROTO_IGMP = 2 # Internet Group Management Protocol. */
IPPROTO_IPIP = 4 # IPIP tunnels (older KA9Q tunnels use 94).
IPPROTO_TCP = 6 # Transmission Control Protocol.
IPPROTO_EGP = 8 # Exterior Gateway Protocol.
IPPROTO_PUP = 12 # PUP protocol.
IPPROTO_UDP = 17 # User Datagram Protocol.
IPPROTO_IDP = 22 # XNS IDP protocol.
IPPROTO_TP = 29 # SO Transport Protocol Class 4.
IPPROTO_IPV6 = 41 # IPv6 header.
IPPROTO_ROUTING = 43 # IPv6 routing header.
IPPROTO_FRAGMENT = 44 # IPv6 fragmentation header.
IPPROTO_RSVP = 46 # Reservation Protocol.
IPPROTO_GRE = 47 # General Routing Encapsulation.
IPPROTO_ESP = 50 # encapsulating security payload.
IPPROTO_AH = 51 # authentication header.
IPPROTO_ICMPV6 = 58 # ICMPv6.
IPPROTO_NONE = 59 # IPv6 no next header.
IPPROTO_DSTOPTS = 60 # IPv6 destination options.
IPPROTO_MTP = 92 # Multicast Transport Protocol.
IPPROTO_ENCAP = 98 # Encapsulation Header.
IPPROTO_PIM = 103 # Protocol Independent Multicast.
IPPROTO_COMP = 108 # Compression Header Protocol.
IPPROTO_SCTP = 132 # Stream Control Transmission Protocol.
IPPROTO_RAW = 255 # Raw IP packets.
IPPROTO_MAX
cdef extern from "Python.h":
object PyString_FromStringAndSize(char *s, Py_ssize_t len)
cdef extern from "sys/time.h":
ctypedef long time_t
struct timeval:
time_t tv_sec
time_t tv_usec
struct timezone:
pass
cdef extern from "netinet/in.h":
u_int32_t ntohl (u_int32_t __netlong) nogil
u_int16_t ntohs (u_int16_t __netshort) nogil
u_int32_t htonl (u_int32_t __hostlong) nogil
u_int16_t htons (u_int16_t __hostshort) nogil
cdef extern from "libnfnetlink/linux_nfnetlink.h":
struct nfgenmsg:
u_int8_t nfgen_family
u_int8_t version
u_int16_t res_id
cdef extern from "libnetfilter_queue/linux_nfnetlink_queue.h":
enum nfqnl_config_mode:
NFQNL_COPY_NONE
NFQNL_COPY_META
NFQNL_COPY_PACKET
struct nfqnl_msg_packet_hdr:
u_int32_t packet_id
u_int16_t hw_protocol
u_int8_t hook
cdef extern from "libnetfilter_queue/libnetfilter_queue.h":
struct nfq_handle:
pass
struct nfq_q_handle:
pass
struct nfq_data:
pass
struct nfqnl_msg_packet_hw:
u_int8_t hw_addr[8]
nfq_handle *nfq_open()
int nfq_close(nfq_handle *h)
int nfq_bind_pf(nfq_handle *h, u_int16_t pf)
int nfq_unbind_pf(nfq_handle *h, u_int16_t pf)
ctypedef int *nfq_callback(nfq_q_handle *gh, nfgenmsg *nfmsg,
nfq_data *nfad, void *data)
nfq_q_handle *nfq_create_queue(nfq_handle *h,
u_int16_t num,
nfq_callback *cb,
void *data)
int nfq_destroy_queue(nfq_q_handle *qh)
int nfq_handle_packet(nfq_handle *h, char *buf, int len)
int nfq_set_mode(nfq_q_handle *qh,
u_int8_t mode, unsigned int len)
q_set_queue_maxlen(nfq_q_handle *qh,
u_int32_t queuelen)
int nfq_set_verdict(nfq_q_handle *qh,
u_int32_t id,
u_int32_t verdict,
u_int32_t data_len,
unsigned char *buf) nogil
int nfq_set_verdict_mark(nfq_q_handle *qh,
u_int32_t id,
u_int32_t verdict,
u_int32_t mark,
u_int32_t datalen,
unsigned char *buf) nogil
int nfq_set_queue_maxlen(nfq_q_handle *qh, u_int32_t queuelen)
int nfq_fd(nfq_handle *h)
nfqnl_msg_packet_hdr *nfq_get_msg_packet_hdr(nfq_data *nfad)
int nfq_get_payload(nfq_data *nfad, char **data)
int nfq_get_timestamp(nfq_data *nfad, timeval *tv)
nfqnl_msg_packet_hw *nfq_get_packet_hw(nfq_data *nfad)
# Dummy defines from linux/socket.h:
cdef enum: # Protocol families, same as address families.
PF_INET = 2
PF_INET6 = 10
cdef extern from "sys/socket.h":
ssize_t recv(int __fd, void *__buf, size_t __n, int __flags) nogil
# Dummy defines from linux/netfilter.h
cdef enum:
NF_DROP
NF_ACCEPT
NF_STOLEN
NF_QUEUE
NF_REPEAT
NF_STOP
NF_MAX_VERDICT = NF_STOP
cdef class NFPacket:
cdef nfq_q_handle *_qh
cdef nfq_data *_nfa
cdef nfqnl_msg_packet_hdr *_hdr
cdef bint _verdict_is_set # True if verdict has been issued,
# false otherwise
cdef bint _mark_is_set # True if a mark has been given, false otherwise
cdef u_int32_t _given_mark # Mark given to packet
cdef unsigned char *_given_payload # New payload of packet, or null
# From NFQ packet header:
cdef readonly u_int32_t id
cdef readonly u_int16_t hw_protocol
cdef readonly u_int8_t hook
# Packet details:
cdef Py_ssize_t payload_len
cdef readonly char *payload
cdef timeval timestamp
# TODO: implement these
#cdef u_int8_t hw_addr[8] # A eui64-formatted address?
#cdef readonly u_int32_t nfmark
#cdef readonly u_int32_t indev
#cdef readonly u_int32_t physindev
#cdef readonly u_int32_t outdev
#cdef readonly u_int32_t physoutdev
cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa)
cdef void verdict(self, u_int8_t verdict)
cpdef Py_ssize_t get_payload_len(self)
cpdef double get_timestamp(self)
cpdef set_mark(self, u_int32_t mark)
cpdef accept(self)
cpdef drop(self)
cdef class NFQueue:
cdef nfq_handle *h # Handle to NFQueue library
cdef nfq_q_handle *qh # A handle to the queue
cdef u_int16_t af # Address family
cdef packet_copy_size # Amount of packet metadata + data copied to buffer

179
netfilterqueue.pyx Normal file
View File

@ -0,0 +1,179 @@
## cython: profile=True
import socket
# Constants for module users
COPY_NONE = 1
COPY_META = 2
COPY_PACKET = 3
# Packet copying defaults
DEF MaxPacketSize = 0xFFFF
DEF BufferSize = 4096
DEF MetadataSize = 80
DEF MaxCopySize = BufferSize - MetadataSize
cdef int global_callback(nfq_q_handle *qh, nfgenmsg *nfmsg,
nfq_data *nfa, void *data) with gil:
"""
Create an NFPacket and pass it to appropriate Python/Cython callback.
Working in a Python or Cython callback, rather than directly in this
callback, results in a ~7% performance hit.
"""
# cdef nfqnl_msg_packet_hdr *_hdr = nfq_get_msg_packet_hdr(nfa)
# cdef u_int32_t id = ntohl(_hdr.packet_id)
# nfq_set_verdict(
# qh,
# id,
# NF_ACCEPT,
# 0, # TODO: adapt to use self._given_payload
# NULL # TODO: adapt to use self._given_payload
# )
packet = NFPacket()
packet.set_nfq_data(qh, nfa)
(<NFQueue>data).handle(packet)
return 1
cdef class NFPacket:
"""A packet received from NFQueue."""
def __cinit__(self):
self._verdict_is_set = False
self._mark_is_set = False
self._given_payload = NULL
def __str__(self):
cdef iphdr *hdr = <iphdr*>self.payload
protocol = "Unknown protocol"
for name in filter(lambda x: x.startswith("IPPROTO"), dir(socket)):
if getattr(socket, name) == hdr.protocol:
protocol = name[8:]
return "%s packet, %s bytes" % (protocol, self.payload_len)
cdef set_nfq_data(self, nfq_q_handle *qh, nfq_data *nfa):
"""
Assign a packet from NFQ to this object. Parse the header and load
local values.
"""
self._qh = qh
self._nfa = nfa
self._hdr = nfq_get_msg_packet_hdr(nfa)
self.id = ntohl(self._hdr.packet_id)
self.hw_protocol = ntohs(self._hdr.hw_protocol)
self.hook = self._hdr.hook
self.payload_len = nfq_get_payload(self._nfa, &self.payload)
if self.payload_len < 0:
raise OSError("Failed to get payload of packet.")
nfq_get_timestamp(self._nfa, &self.timestamp)
cdef void verdict(self, u_int8_t verdict):
"""Call appropriate set_verdict... function on packet."""
#if self._verdict_is_set:
# raise RuntimeWarning("Verdict already given for this packet.")
if self._mark_is_set:
nfq_set_verdict_mark( # TODO: make this use nfq_set_verdict2 if available on system
self._qh,
self.id,
verdict,
htonl(self._given_mark),
0, # TODO: adapt to use self._given_payload
NULL # TODO: adapt to use self._given_payload
)
else:
nfq_set_verdict(
self._qh,
self.id,
verdict,
0, # TODO: adapt to use self._given_payload
NULL # TODO: adapt to use self._given_payload
)
#self._verdict_is_set = True
def get_payload(self):
cdef object py_string = PyString_FromStringAndSize(self.payload, self.payload_len)
return py_string
cpdef Py_ssize_t get_payload_len(self):
return self.payload_len
cpdef double get_timestamp(self):
return self.timestamp.tv_sec + (self.timestamp.tv_usec/1000000.0)
# TODO: implement this
#cpdef set_payload(self, unsigned char *payload):
# """Set the new payload of this packet."""
# self._given_payload = payload
cpdef set_mark(self, u_int32_t mark):
self._given_mark = mark
self._mark_is_set = True
cpdef accept(self):
"""Accept the packet."""
self.verdict(NF_ACCEPT)
cpdef drop(self):
"""Drop the packet."""
self.verdict(NF_DROP)
cdef class NFQueue:
"""Handle a single numbered queue."""
def __cinit__(self, *args, **kwargs):
if "af" in kwargs:
self.af = kwargs["af"]
else:
self.af = socket.AF_INET
self.h = nfq_open()
if self.h == NULL:
raise OSError("Failed to open NFQueue.")
nfq_unbind_pf(self.h, self.af) # This does NOT kick out previous
# running queues
if nfq_bind_pf(self.h, self.af) < 0:
raise OSError("Failed to bind family %s." % self.af)
def __dealloc__(self):
if self.qh != NULL:
nfq_destroy_queue(self.qh)
# Don't call nfq_unbind_pf unless you want to disconnect any other
# processes using this libnetfilter_queue on this protocol family!
nfq_close(self.h)
def bind(self, object handler, int queue_num, u_int32_t maxlen, u_int8_t mode=NFQNL_COPY_PACKET, u_int32_t range=MaxPacketSize):
"""Create a new queue with the given callback function."""
self.qh = nfq_create_queue(self.h, queue_num, <nfq_callback*>global_callback, <void*>handler)
if self.qh == NULL:
raise OSError("Failed to create queue %s." % queue_num)
if range > MaxCopySize:
range = MaxCopySize
if nfq_set_mode(self.qh, mode, range) < 0:
raise OSError("Failed to set packet copy mode.")
nfq_set_queue_maxlen(self.qh, maxlen)
def unbind(self):
"""Destroy the queue."""
if self.qh != NULL:
nfq_destroy_queue(self.qh)
# See warning about nfq_unbind_pf in __dealloc__ above.
def run(self):
"""Begin accepting packets."""
cdef int fd = nfq_fd(self.h)
cdef char buf[BufferSize]
cdef int rv
with nogil:
rv = recv(fd, buf, sizeof(buf), 0)
while rv >= 0:
nfq_handle_packet(self.h, buf, rv)
with nogil:
rv = recv(fd, buf, sizeof(buf), 0)
def handle(self, NFPacket packet):
"""Handle a single packet. User-defined classes should override this."""
packet.accept()

45
setup.py Normal file
View File

@ -0,0 +1,45 @@
from distutils.core import setup, Extension
VERSION = "0.1"
try:
# Use Cython
from Cython.Distutils import build_ext
ext = Extension(
"netfilterqueue",
sources=["netfilterqueue.pyx",],
libraries=["netfilter_queue"],
)
except ImportError:
# No Cython
build_ext = None
ext = Extension(
"netfilterqueue",
sources = ["netfilterqueue.c"],
libraries=["netfilter_queue"],
)
setup(
cmdclass = {"build_ext": build_ext},
ext_modules = [ext],
name="NetfilterQueue",
version=VERSION,
license="BSD",
author="Matthew Fox",
author_email="matthew@kerkhofftech.ca",
url="https://github.com/kti/python-netfilter_queue",
description="Python bindings for libnetfilter_queue",
long_description=open("README.txt").read(),
download_url="http://pypi.python.org/packages/source/N/NetfilterQueue/NetfilterQueue-%s.tar.gz" % VERSION,
classifiers = [
"Development Status :: 4 - Beta",
"License :: OSI Approved :: BSD License",
"Operating System :: POSIX :: Linux",
"Topic :: System :: Networking",
"Topic :: Security",
"Intended Audience :: Developers",
"Intended Audience :: Telecommunications Industry",
"Programming Language :: Cython",
"Programming Language :: Python :: 2",
]
)