From caa74111838fc40e0a7977c2bdd0c8507664d42e Mon Sep 17 00:00:00 2001 From: Kosma Moczek Date: Sun, 20 Jun 2010 15:29:20 +0000 Subject: [PATCH] Merged revisions 5591-5606,5613,5625,5645-5646 via svnmerge from http://svn.umitproject.org/svnroot/umit/umpa/branches/link-layer-integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ........ r5591 | kosma | 2010-06-05 00:38:07 +0200 (So, 05 čen 2010) | 3 lines Initial version of SocketL2 - Linux and FreeBSD supported. With tests. L2 tests fail under BSD. L3 support not touched. ........ r5592 | kosma | 2010-06-05 01:03:33 +0200 (So, 05 čen 2010) | 1 line obliterating re module at getxsick's request :* ........ r5593 | kosma | 2010-06-05 01:15:34 +0200 (So, 05 čen 2010) | 1 line platform check: convert find to startswith ........ r5594 | kosma | 2010-06-05 01:16:59 +0200 (So, 05 čen 2010) | 1 line platform check: detect MS Windows via os.name ........ r5595 | kosma | 2010-06-05 12:39:43 +0200 (So, 05 čen 2010) | 1 line added a _Socket superclass ........ r5596 | kosma | 2010-06-05 13:16:45 +0200 (So, 05 čen 2010) | 2 lines moved 'get packet destination' to class Packet, with tests ........ r5597 | kosma | 2010-06-05 18:23:57 +0200 (So, 05 čen 2010) | 2 lines Port L3 code to new style. Add BSD endianness quirk support w/test. ........ r5598 | kosma | 2010-06-05 19:32:07 +0200 (So, 05 čen 2010) | 2 lines Load fcntl module conditionally - it's not present under Win32. ........ r5599 | kosma | 2010-06-05 21:28:44 +0200 (So, 05 čen 2010) | 1 line fix a typo preventing L3 sockets from working under OpenBSD ........ r5600 | kosma | 2010-06-05 22:15:52 +0200 (So, 05 čen 2010) | 1 line Fixed L2 send (bpf) under OpenBSD. ........ r5601 | kosma | 2010-06-05 22:39:46 +0200 (So, 05 čen 2010) | 1 line minor documentation issue ........ r5602 | kosma | 2010-06-05 23:42:41 +0200 (So, 05 čen 2010) | 1 line example for SocketL2 usage ........ r5603 | kosma | 2010-06-05 23:46:17 +0200 (So, 05 čen 2010) | 2 lines Set svn:executable property on examples, install scripts and setup.py. ........ r5604 | kosma | 2010-06-05 23:58:42 +0200 (So, 05 čen 2010) | 1 line set svn:ignore on generated documentation dirs ........ r5605 | kosma | 2010-06-06 00:09:36 +0200 (Ne, 06 čen 2010) | 6 lines README file update. - New library description from the trac site. - Added myself to Authors section (hope I won't get killed for that ;). - Formatting cleanups, rephrasing, etc. ........ r5606 | kosma | 2010-06-06 00:21:49 +0200 (Ne, 06 čen 2010) | 1 line apply changes to cloned README in docs/ ........ r5613 | getxsick | 2010-06-16 19:41:53 +0200 (St, 16 čen 2010) | 2 lines rename UMPA repository to umpa ........ r5625 | getxsick | 2010-06-18 14:48:59 +0200 (Pá, 18 čen 2010) | 2 lines rename umpa/branch to umpa/branches (even if i prefer branch/) ........ r5645 | kosma | 2010-06-19 18:00:18 +0200 (So, 19 čen 2010) | 1 line revert a too-hasty documentation change ........ r5646 | kosma | 2010-06-19 19:14:44 +0200 (So, 19 čen 2010) | 1 line information on L3 sockets under xp sp2 ........ git-svn-id: http://svn.umitproject.org/svnroot/umit/umpa/trunk@5651 1105ee14-b0fa-0310-85a5-ff3eed429ff7 --- README | 49 ++-- docs/README | 49 ++-- docs/tutorials-src/01-YourFirstPackets.rst | 15 ++ examples/layer2.py | 49 ++++ examples/models.py | 0 examples/nostrict.py | 0 examples/payload.py | 0 examples/sniff.py | 0 examples/tcp.py | 0 examples/udp.py | 0 install_scripts/clean.sh | 0 install_scripts/create_docs.sh | 0 install_scripts/create_packages.sh | 0 setup.py | 0 tests/a_unit/test_packets.py | 18 +- tests/a_unit/test_sockets.py | 79 +++++- tests/utils.py | 12 + umit/umpa/__init__.py | 2 +- umit/umpa/_packets.py | 19 ++ umit/umpa/_sockets.py | 266 +++++++++++++++++---- 20 files changed, 448 insertions(+), 110 deletions(-) create mode 100755 examples/layer2.py mode change 100644 => 100755 examples/models.py mode change 100644 => 100755 examples/nostrict.py mode change 100644 => 100755 examples/payload.py mode change 100644 => 100755 examples/sniff.py mode change 100644 => 100755 examples/tcp.py mode change 100644 => 100755 examples/udp.py mode change 100644 => 100755 install_scripts/clean.sh mode change 100644 => 100755 install_scripts/create_docs.sh mode change 100644 => 100755 install_scripts/create_packages.sh mode change 100644 => 100755 setup.py diff --git a/README b/README index 09e0639..62077e4 100644 --- a/README +++ b/README @@ -2,9 +2,9 @@ UMPA - Umit's Manipulations of Packets Art ============================================ -Copyright (C) 2008 Adriano Monteiro Marques +Copyright (C) 2008-2010 Adriano Monteiro Marques -Author: Bartosz SKOWRON +Authors: Bartosz SKOWRON All rights reserved, see COPYING for details. @@ -26,28 +26,27 @@ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Introduction ============ -UMPA is a packets manipulation library. +UMPA is a packet generation and manipulation library. It aims to provide +an easy-to-use system to manipulate packets on every layer of the OSI model. +UMPA supports many sophisticated features, including: -UMPA is developed in the Python programming language. + * auto-generating packets - defaults are provided for most fields + * high-level editing of header fields - no knowledge of bit-level + representation needed + * auto-filling - checksum/length fields are generated automatically + * interaction system - reacting and responding to network events + * sniffing and sending packets - easy communication with the real world -Packet manipulation library aims to provide easy to use system -to manipulate packets of every OSI model layers. - -Many features are supported like: auto-generating packets, high-level -editing header fields, auto-filling, interaction system, sniffing -and creating objet's of packets. The goal of the project is to get a powerful, easy to use and intelligent -tool for pro and newbies. - -Check docs/* for additional informations. +packet manipulation tool suitable both for beginners and experienced users. +UMPA has a wide range of uses, including network diagnostics, auditing, +stress testing and network stack debugging. Documentation ============= -On the Web: - -http://www.umpa.umitproject.org +On the Web: http://www.umpa.umitproject.org This page also points to support resources and informations about UMPA development status and plans. @@ -63,7 +62,7 @@ Dependencies * [1] Python programming language * [2] libpcap (for sniffing) - * [3] libpcap's python wrapper (see INSTALL for more) + * [3] libpcap's python wrapper (see INSTALL for details) Acknowledgements @@ -72,11 +71,11 @@ Acknowledgements I would like to thank for a several people, who help and support me with the library, sending patches, testing it and giving ideas: - * Adriano Monteiro Marques - * Francesco Piccinno - * Luís António Bastião Silva - * Guilherme Henrique Polo Gonçalves - * João Paulo de Souza Medeiros + * Adriano Monteiro Marques + * Francesco Piccinno + * Luís António Bastião Silva + * Guilherme Henrique Polo Gonçalves + * João Paulo de Souza Medeiros Authors @@ -88,6 +87,6 @@ Authors References ========== - * [0] UMPA website - http://www.umpa.umitproject.org - * [1] Python official website - http://www.python.org - * [2] Umit Project - http://www.umitproject.org + * [0] UMPA website - http://umpa.umitproject.org/ + * [1] Umit Project - http://www.umitproject.org/ + * [2] Python official website - http://www.python.org/ diff --git a/docs/README b/docs/README index 09e0639..62077e4 100644 --- a/docs/README +++ b/docs/README @@ -2,9 +2,9 @@ UMPA - Umit's Manipulations of Packets Art ============================================ -Copyright (C) 2008 Adriano Monteiro Marques +Copyright (C) 2008-2010 Adriano Monteiro Marques -Author: Bartosz SKOWRON +Authors: Bartosz SKOWRON All rights reserved, see COPYING for details. @@ -26,28 +26,27 @@ Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Introduction ============ -UMPA is a packets manipulation library. +UMPA is a packet generation and manipulation library. It aims to provide +an easy-to-use system to manipulate packets on every layer of the OSI model. +UMPA supports many sophisticated features, including: -UMPA is developed in the Python programming language. + * auto-generating packets - defaults are provided for most fields + * high-level editing of header fields - no knowledge of bit-level + representation needed + * auto-filling - checksum/length fields are generated automatically + * interaction system - reacting and responding to network events + * sniffing and sending packets - easy communication with the real world -Packet manipulation library aims to provide easy to use system -to manipulate packets of every OSI model layers. - -Many features are supported like: auto-generating packets, high-level -editing header fields, auto-filling, interaction system, sniffing -and creating objet's of packets. The goal of the project is to get a powerful, easy to use and intelligent -tool for pro and newbies. - -Check docs/* for additional informations. +packet manipulation tool suitable both for beginners and experienced users. +UMPA has a wide range of uses, including network diagnostics, auditing, +stress testing and network stack debugging. Documentation ============= -On the Web: - -http://www.umpa.umitproject.org +On the Web: http://www.umpa.umitproject.org This page also points to support resources and informations about UMPA development status and plans. @@ -63,7 +62,7 @@ Dependencies * [1] Python programming language * [2] libpcap (for sniffing) - * [3] libpcap's python wrapper (see INSTALL for more) + * [3] libpcap's python wrapper (see INSTALL for details) Acknowledgements @@ -72,11 +71,11 @@ Acknowledgements I would like to thank for a several people, who help and support me with the library, sending patches, testing it and giving ideas: - * Adriano Monteiro Marques - * Francesco Piccinno - * Luís António Bastião Silva - * Guilherme Henrique Polo Gonçalves - * João Paulo de Souza Medeiros + * Adriano Monteiro Marques + * Francesco Piccinno + * Luís António Bastião Silva + * Guilherme Henrique Polo Gonçalves + * João Paulo de Souza Medeiros Authors @@ -88,6 +87,6 @@ Authors References ========== - * [0] UMPA website - http://www.umpa.umitproject.org - * [1] Python official website - http://www.python.org - * [2] Umit Project - http://www.umitproject.org + * [0] UMPA website - http://umpa.umitproject.org/ + * [1] Umit Project - http://www.umitproject.org/ + * [2] Python official website - http://www.python.org/ diff --git a/docs/tutorials-src/01-YourFirstPackets.rst b/docs/tutorials-src/01-YourFirstPackets.rst index 4616f78..e87f389 100644 --- a/docs/tutorials-src/01-YourFirstPackets.rst +++ b/docs/tutorials-src/01-YourFirstPackets.rst @@ -345,6 +345,21 @@ Ok, actually we have a socket object, so let's send the packet! ``Socket.send()`` method returns a list with sent bytes of each packets (we can pass more than one packet at the same time). +Raw IP sockets under Windows +---------------------------- + +If you plan to use raw sockets under Windows XP SP2 or later, be aware of +the restrictions imposed by the Windows' networking stack. They generally boil +down to two things: + + 1. TCP data cannot be sent over raw sockets. + 2. The IP source address for any outgoing UDP datagram must exist on a network + interface. + +The detailed description of those restrictions can be found at the following URL: +http://msdn.microsoft.com/en-us/library/ms740548%28VS.85%29.aspx. +If you find them to be too limiting, consider using Layer 2 sockets (``SocketL2`` +class) instead. UDP protocol ------------ diff --git a/examples/layer2.py b/examples/layer2.py new file mode 100755 index 0000000..efcbf25 --- /dev/null +++ b/examples/layer2.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 Adriano Monteiro Marques. +# +# Author: Kosma Moczek +# +# This library is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public +# License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +""" +This example shows how to send raw Ethernet frames using SocketL2 class. +""" + +import umit.umpa +import umit.umpa.utils.security + +from umit.umpa.protocols import Ethernet, IP, UDP, Payload +from umit.umpa import SocketL2, Packet + +# Create a raw Ethernet socket. The 'iface' argument is mandatory. +sock = umit.umpa.SocketL2(iface='eth0') + +# Drop root privileges after socket creation. This improves security. +umit.umpa.utils.security.drop_priviliges() + +# Create protocol objects. We need to specify MAC addresses since we operate +# on the link layer. +ethernet = Ethernet(src='00:11:22:33:44:55', dst='01:23:45:67:89:AB') +ip = IP(src='192.168.1.234', dst='192.168.1.1') +udp = UDP(srcport=1234, dstport=4321) +payload = Payload("UMPA") + +# Create a new Packet object. +packet = Packet(ethernet, ip, udp, payload) + +# Send the packet. Use a network sniffer (e.g. Wireshark) to see it. +sock.send(packet) diff --git a/examples/models.py b/examples/models.py old mode 100644 new mode 100755 diff --git a/examples/nostrict.py b/examples/nostrict.py old mode 100644 new mode 100755 diff --git a/examples/payload.py b/examples/payload.py old mode 100644 new mode 100755 diff --git a/examples/sniff.py b/examples/sniff.py old mode 100644 new mode 100755 diff --git a/examples/tcp.py b/examples/tcp.py old mode 100644 new mode 100755 diff --git a/examples/udp.py b/examples/udp.py old mode 100644 new mode 100755 diff --git a/install_scripts/clean.sh b/install_scripts/clean.sh old mode 100644 new mode 100755 diff --git a/install_scripts/create_docs.sh b/install_scripts/create_docs.sh old mode 100644 new mode 100755 diff --git a/install_scripts/create_packages.sh b/install_scripts/create_packages.sh old mode 100644 new mode 100755 diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 diff --git a/tests/a_unit/test_packets.py b/tests/a_unit/test_packets.py index a3a5478..dbed3f3 100644 --- a/tests/a_unit/test_packets.py +++ b/tests/a_unit/test_packets.py @@ -21,9 +21,10 @@ import sys from umit.umpa import Packet -from umit.umpa.protocols import IP, TCP, UDP, Payload +from umit.umpa.protocols import Ethernet, IP, TCP, UDP, Payload from umit.umpa.protocols import _consts -from umit.umpa.utils.exceptions import UMPAException, UMPAStrictException +from umit.umpa.utils.exceptions import UMPAException, UMPAStrictException, \ + UMPAAttributeException from umit.umpa._packets import StrictWarning import py.test @@ -97,6 +98,19 @@ def test_protos_order(self): for i in xrange(len(order)): assert isinstance(p.protos[i], order[i]) + def test_get_destination(self): + p = Packet(Ethernet(dst='00:11:22:33:44:55'), + IP(dst='1.2.3.4'), + UDP(dstport=1234), + Payload('UMPA')) + + py.test.raises(UMPAException, p._get_destination, 1); + assert p._get_destination(2) == '00:11:22:33:44:55' + assert p._get_destination(3) == '1.2.3.4' + py.test.raises(UMPAAttributeException, p._get_destination, 4); + py.test.raises(UMPAAttributeException, p._get_destination, 5); + py.test.raises(UMPAException, p._get_destination, 6); + class TestUMPAPacketsOutput(object): def test_get_raw__ip(self): p = Packet() diff --git a/tests/a_unit/test_sockets.py b/tests/a_unit/test_sockets.py index 0507075..8356b49 100644 --- a/tests/a_unit/test_sockets.py +++ b/tests/a_unit/test_sockets.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2009 Adriano Monteiro Marques. +# Copyright (C) 2009-2010 Adriano Monteiro Marques. # -# Author: Bartosz SKOWRON +# Authors: Bartosz SKOWRON +# Kosma Moczek # # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published @@ -22,10 +23,11 @@ import os import py.test -from umit.umpa import Socket, Packet -from umit.umpa.protocols import IP, TCP +from umit.umpa import Socket, SocketL2, Packet +import umit.umpa.sniffing +from umit.umpa.protocols import Ethernet, IP, TCP, UDP, Payload from umit.umpa.utils.exceptions import UMPAException, UMPANotPermittedException - +from tests.utils import SendPacket, SendPacketL2 class TestUMPASockets(object): def test_init(self): @@ -34,12 +36,75 @@ def test_init(self): def test_sent_size(self): if os.name == 'posix' and os.geteuid() != 0: - py.test.skip('root-priviliges are needed') + py.test.skip('root-privileges are needed') p1 = Packet(IP(), TCP()) p2 = Packet(IP(), TCP()) s = Socket() size = s.send(p1, p2) - assert size == [40, 40] + + def test_send_size_L2(self): + if os.name == 'posix' and os.geteuid() != 0: + py.test.skip('root-privileges are needed') + + p1 = Packet(Ethernet(src='00:11:22:33:44:55', dst='00:11:22:33:44:55'), + IP(src="127.0.0.1", dst="127.0.0.1"), + TCP(srcport=1234, dstport=4321), + Payload('xyz')) + p2 = Packet(Ethernet(src='00:11:22:33:44:55', dst='00:11:22:33:44:55'), + IP(src="127.0.0.1", dst="127.0.0.1"), + TCP(srcport=1234, dstport=4321), + Payload('xyz')) + + s = SocketL2(iface='lo') + size = s.send(p1, p2) + + assert size == [57, 57] + + def test_send_sniff_tcp(self): + if os.name == 'posix' and os.geteuid() != 0: + py.test.skip('root-privileges are needed') + + p = Packet(Ethernet(src='00:11:22:33:44:55', dst='00:11:22:33:44:55'), + IP(src="127.0.0.1", dst="127.0.0.1"), + UDP(srcport=1234, dstport=4321), + Payload('xyz')) + + th = SendPacketL2(p, iface='lo') + th.start() + result = umit.umpa.sniffing.sniff(1, device='lo', filter="src port 1234") + th.join() + + assert len(result) == 1 + assert result[0].ethernet.src == '00:11:22:33:44:55' + assert result[0].ip.src == '127.0.0.1' + assert result[0].udp.srcport == 1234 + assert result[0].udp.dstport == 4321 + + def test_send_sniff_tcp_L2(self): + if os.name == 'posix' and os.geteuid() != 0: + py.test.skip('root-privileges are needed') + + p = Packet(Ethernet(src='00:11:22:33:44:55', dst='00:11:22:33:44:55'), + IP(src="127.0.0.1", dst="127.0.0.1"), + TCP(srcport=1234, dstport=4321), + Payload('xyz')) + + th = SendPacketL2(p, iface='lo') + th.start() + result = umit.umpa.sniffing.sniff(1, device='lo', filter="src port 1234") + th.join() + + assert len(result) == 1 + assert result[0].ethernet.src == '00:11:22:33:44:55' + assert result[0].ip.src == '127.0.0.1' + assert result[0].tcp.srcport == 1234 + assert result[0].tcp.dstport == 4321 + + def test_ntohs_quirk(self): + raw1 = "ABCDEFGH" + raw2 = umit.umpa._sockets._ntohs_quirk(raw1) + + assert raw2 == "ABDCEFHG" diff --git a/tests/utils.py b/tests/utils.py index 2ee1a83..65c036a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -34,3 +34,15 @@ def run(self): for i in xrange(self._amount): time.sleep(2) s.send(self._packet) + +class SendPacketL2(threading.Thread): + def __init__(self, packet, amount=1, iface=None): + super(SendPacketL2, self).__init__() + self._packet = packet + self._amount = amount + self._iface = iface + def run(self): + s = umit.umpa.SocketL2(iface=self._iface) + for i in xrange(self._amount): + time.sleep(2) + s.send(self._packet) diff --git a/umit/umpa/__init__.py b/umit/umpa/__init__.py index 2cb2a82..6a54e98 100644 --- a/umit/umpa/__init__.py +++ b/umit/umpa/__init__.py @@ -40,7 +40,7 @@ from umit.umpa._config import config from umit.umpa._packets import Packet -from umit.umpa._sockets import Socket +from umit.umpa._sockets import Socket, SocketL2 # UMPA handles with the local directory $HOME/.umpa # especially with the $HOME/.umpa/umpa_plugins diff --git a/umit/umpa/_packets.py b/umit/umpa/_packets.py index 00fe90f..a2fd400 100644 --- a/umit/umpa/_packets.py +++ b/umit/umpa/_packets.py @@ -162,6 +162,25 @@ def _add_new_protocols(self, protos): last_proto.__dict__['payload'] = proto self.protos.append(proto) + def _get_destination(self, layer): + """ + Get packet's destination address. + + @type layer: C{int} + @param layer: Protocol layer. + + @return: destination address from the selected layer. + """ + + for proto in self.protos: + if proto.layer == layer: + break + + if not proto: + raise UMPAException("The packet does not contain a layer %d protocol." % level) + + return proto.dst + def get_raw(self): """ Return raw packet in the bit-mode (big-endian). diff --git a/umit/umpa/_sockets.py b/umit/umpa/_sockets.py index 889644f..b42f9c0 100644 --- a/umit/umpa/_sockets.py +++ b/umit/umpa/_sockets.py @@ -1,9 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# Copyright (C) 2008-2009 Adriano Monteiro Marques. +# Copyright (C) 2008-2010 Adriano Monteiro Marques. # -# Author: Bartosz SKOWRON +# Authors: Bartosz SKOWRON +# Kosma Moczek # # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published @@ -20,80 +21,245 @@ # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA """ -Socket connection management. +Raw sockets support. -It contains Socket class which should be used instead of -socket.socket() directly from the standard library. - -But it's correctly to use socket module directly if needed. +Contains Socket classes which can be used to send raw packets in +a platform-independent manner. The standard Python socket module can be used +instead if advanced functionalities are needed. """ import socket +import struct +import sys +import os +from errno import EBUSY from umit.umpa.utils.exceptions import UMPAException, UMPANotPermittedException -class Socket(object): +# constants from various header files not available under Python +ETH_P_ALL = 3 # from linux/if_ether.h +BIOCSETIF = 2149597804 # from net/bpf.h + +# Detect socket programming model. This greatly simplifies socket code. +if sys.platform == 'linux2': + _l2model = 'AF_PACKET' + _l3model = 'AF_INET' + _l3quirk = None +elif sys.platform.startswith('freebsd') or \ + sys.platform.startswith('netbsd') or \ + sys.platform.startswith('darwin'): + _l2model = 'bpf' + _l3model = 'AF_INET' + _l3quirk = 'ntohs' +elif sys.platform.startswith('openbsd'): + _l2model = 'bpf' + _l3model = 'AF_INET' + _l3quirk = None +elif os.name == 'nt': + _l2model = 'NDIS' + _l3model = 'AF_INET' + _l3quirk = 'windows' +else: + _l2model = None + _l3model = None + _l3quirk = None + +# load additional modules conditionally depending on programming models +if _l2model == 'bpf': + from fcntl import ioctl + +def send(*packets, **kwargs): """ - This class handles with sockets. + Send arbitrary packets. - To send built packets your need to create a socket. - You can use socket module from Python Standard Library directly - but it's recommended to use this class instead. + The function creates sockets of proper level as needed. The 'iface' + named argument must be supplied for L2 (link-layer) sockets. - That is because there are some other features, - and for some security issues. + @type packets: C{Packet} + @param packets: list of umit.umpa.Packet objects to send. + + @returns: List of return values (byte counts) from the send() function. """ - def __init__(self): + sent_bytes = [] + for packet in packets: + # create appropriate socket based on packet's lowermost layer + if packet.protos[0].layer == 2: + sock = SocketL2(iface=kwargs.get('iface')) + else: + sock = SocketL3() + sent_bytes.extend(sock.send(packet)) + return sent_bytes + +class _Socket(object): + """ + Raw socket superclass. + + This is an abstract class. + """ + + def __init__(self, **kwargs): + raise NotImplementedError("this is an abstract class") + +class SocketL2(_Socket): + """ + Level 2 (link-layer) socket class. + + Supported platforms: Linux (AF_PACKET), BSD (bpf). + """ + + def __init__(self, iface=None): """ - Create a new Socket(). + Create a new SocketL2 instance. + + Requires root/administrator rights and/or CAP_NET_RAW capability. + + @type iface: C{str} + @param iface: Interface to use for sending the packets. """ + if iface is None: + # TODO: port interface detection from the link-layer branch + raise NotImplementedError("You need to specify iface") + + if _l2model == 'AF_PACKET': + try: + self._sock = socket.socket(socket.AF_PACKET, + socket.SOCK_RAW, + ETH_P_ALL) + except socket.error, msg: + raise UMPANotPermittedException(msg) - # to create socket object we need root priviligies. - # if non-root EUID, then exception is raised - # use umit.umpa.utils.security.super_priviliges() to avoid exception - # when a new Socket object is created - try: - self._sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, - socket.IPPROTO_RAW) - except socket.error, msg: - raise UMPANotPermittedException(msg) + self._sock.bind((iface, ETH_P_ALL)) + self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**20) + elif _l2model == 'bpf': + # Loop over /dev/bpf* devices, looking for a free one. + self._sock = None + suffix = 0 + while self._sock is None: + try: + self._sock = open('/dev/bpf'+str(suffix), 'wb') + except IOError, error: + if error.errno == EBUSY: + suffix = suffix + 1 + else: + raise UMPAException(str(error)) - # to build own headers of IP - self._sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + ioctl(self._sock, BIOCSETIF, iface) + else: + raise NotImplementedError("L2 sockets unsupported on your platform") def send(self, *packets): """ - Send packets in to the network. + Send packets through the socket. @type packets: C{Packet} - @param packets: packets which were built by umit.umpa.Packet objects. + @param packets: List of umit.umpa.Packet objects to send. + + @returns: List of return values (byte counts) from the send() function. """ - sent_bits = [] + sent_bytes = [] for packet in packets: - # XXX try to use similar mechanism as is for SocketL2 - # to pick up correct interface, using bind() and send() - # then there is no need to pick out a destination address - dst_addr = self._get_address(packet) - # if dst_addr is a tuple, convert it to a string; works only for IPv4 - if type(dst_addr) is tuple: - dst_addr = ".".join(str(y) for y in dst_addr) - sent_bits.append(self._sock.sendto(packet.get_raw(), - (dst_addr, 0))) - return sent_bits - - def _get_address(self, packet): + if _l2model == 'AF_PACKET': + sent_bytes.append(self._sock.send(packet.get_raw())) + elif _l2model == 'bpf': + sent_bytes.append(self._sock.write(packet.get_raw())) + else: + raise NotImplementedError("L2 send unsupported on your platform") + return sent_bytes + +class SocketL3(_Socket): + """ + Level 3 (network layer) socket class. + + Supported platforms: Linux, BSD, Windows (AF_INET). + + Note: If you plan to use raw sockets under Windows XP SP2 or later, be aware + of the restrictions imposed by the Windows’ networking stack. They generally + boil down to two things: + + 1. TCP data cannot be sent over raw sockets. + 2. The IP source address for any outgoing UDP datagram must exist on + a network interface. + + The detailed description of those restrictions can be found at the following URL: + http://msdn.microsoft.com/en-us/library/ms740548%28VS.85%29.aspx. If you find + them to be too limiting, consider using Layer 2 sockets (SocketL2 class) instead. + """ + + def __init__(self): """ - Pick out the destination address from 3rd layer. + Create a new SocketL3 instance. - @return: destination address from 3rd layer of OSI model. + Requires root/administrator rights and/or CAP_NET_RAW capability. """ - for proto in packet.protos: - if proto.layer == 3: # XXX: if we included more than one protocol - break # of layer 3 we got IP from the first one - if not proto: - raise UMPAException("There is not prototocol from 3rd layer.") + if _l3model == 'AF_INET': + try: + self._sock = socket.socket(socket.AF_INET, + socket.SOCK_RAW, + socket.IPPROTO_RAW) + except socket.error, msg: + raise UMPANotPermittedException(msg) + + # tell the kernel we are including our own IP header + self._sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) + else: + raise NotImplementedError('L3 sockets unsupported on your platform') + + def send(self, *packets): + """ + Send packets through the socket. + + @type packets: C{Packet} + @param packets: List of umit.umpa.Packet objects to send. + + @returns: List of return values (byte counts) from the send() function. + """ + + sent_bytes = [] + for packet in packets: + if _l3model == 'AF_INET': + # get destination address and convert it to IPv4 notation + # TODO: move this to utils.net + dst_addr = packet._get_destination(layer=3) + if type(dst_addr) is tuple: + dst_addr = ".".join(str(y) for y in dst_addr) + + raw = packet.get_raw() + + if _l3quirk == 'ntohs': + raw = _ntohs_quirk(raw) + + sent_bytes.append(self._sock.sendto(raw, (dst_addr, 0))) + else: + raise NotImplementedError("L3 send unsupported on your platform") + return sent_bytes + +def _ntohs_quirk(raw): + """ + FreeBSD raw socket endianness quirk support. + + Some BSD flavors, notably FreeBSD, have a weirdness in their raw IP sockets + in that the 'total length' and 'fragment offset' fields must be supplied in + host byte order. See ip(4) manpage on affected systems. + + This function corrects the endianness of a packet, making it suitable to be + sent on such systems. + + @type raw: C{str} + @param raw: Raw IP header + + @returns: Raw IP header data with 'total length' and 'fragment offset' fields + converted to host byte order. + """ + + vhds, tl, id, fo = struct.unpack("!HHHH", raw[0:8]) + tl = socket.ntohs(tl) + fo = socket.ntohs(fo) + raw = struct.pack("!HHHH", vhds, tl, id, fo) + raw[8:] + + return raw - return proto.dst +# XXX API compatibility hack for Milestone 0.3 release +Socket = SocketL3