| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | # Copyright (C) 2008-2010 Adriano Monteiro Marques. |
|---|
| 5 | # |
|---|
| 6 | # Authors: Bartosz SKOWRON <getxsick at gmail dot com> |
|---|
| 7 | # Kosma Moczek <kosma at kosma dot pl> |
|---|
| 8 | # |
|---|
| 9 | # This library is free software; you can redistribute it and/or modify |
|---|
| 10 | # it under the terms of the GNU Lesser General Public License as published |
|---|
| 11 | # by the Free Software Foundation; either version 2.1 of the License, or |
|---|
| 12 | # (at your option) any later version. |
|---|
| 13 | # |
|---|
| 14 | # This library is distributed in the hope that it will be useful, but |
|---|
| 15 | # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
|---|
| 16 | # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public |
|---|
| 17 | # License for more details. |
|---|
| 18 | # |
|---|
| 19 | # You should have received a copy of the GNU Lesser General Public License |
|---|
| 20 | # along with this library; if not, write to the Free Software Foundation, |
|---|
| 21 | # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
|---|
| 22 | |
|---|
| 23 | """ |
|---|
| 24 | Raw sockets support. |
|---|
| 25 | |
|---|
| 26 | Contains Socket classes which can be used to send raw packets in |
|---|
| 27 | a platform-independent manner. The standard Python socket module can be used |
|---|
| 28 | instead if advanced functionalities are needed. |
|---|
| 29 | """ |
|---|
| 30 | |
|---|
| 31 | import socket |
|---|
| 32 | import sys |
|---|
| 33 | import os |
|---|
| 34 | from fcntl import ioctl |
|---|
| 35 | |
|---|
| 36 | from umit.umpa.utils.exceptions import UMPAException, UMPANotPermittedException |
|---|
| 37 | |
|---|
| 38 | # constants from various header files not available under Python |
|---|
| 39 | ETH_P_ALL = 3 # from linux/if_ether.h |
|---|
| 40 | BIOCSETIF = 2149597804 # from net/bpf.h |
|---|
| 41 | |
|---|
| 42 | # Detect socket programming model. This greatly simplifies socket code. |
|---|
| 43 | if sys.platform == 'linux2': |
|---|
| 44 | _l2model = 'AF_PACKET' |
|---|
| 45 | _l3model = 'AF_INET' |
|---|
| 46 | _l3quirk = None |
|---|
| 47 | elif sys.platform.startswith('freebsd') or \ |
|---|
| 48 | sys.platform.startswith('netbsd') or \ |
|---|
| 49 | sys.platform.startswith('darwin'): |
|---|
| 50 | _l2model = 'bpf' |
|---|
| 51 | _l3model = 'AF_INET' |
|---|
| 52 | _l3quirk = 'ntohs' |
|---|
| 53 | elif sys.platform.startswith('openbsd'): |
|---|
| 54 | _l2model = 'bpf' |
|---|
| 55 | _l3model = 'AF_INET', |
|---|
| 56 | _l3quirk = None |
|---|
| 57 | elif os.name == 'nt': |
|---|
| 58 | _l2model = 'NDIS' |
|---|
| 59 | _l3model = 'AF_INET' |
|---|
| 60 | _l3quirk = 'windows' |
|---|
| 61 | else: |
|---|
| 62 | _l2model = None |
|---|
| 63 | _l3model = None |
|---|
| 64 | _l3quirk = None |
|---|
| 65 | |
|---|
| 66 | def send(*packets, **kwargs): |
|---|
| 67 | """ |
|---|
| 68 | Send arbitrary packets. |
|---|
| 69 | |
|---|
| 70 | The function creates sockets of proper level as needed. The 'iface' |
|---|
| 71 | named argument must be supplied for L2 (link-layer) sockets. |
|---|
| 72 | |
|---|
| 73 | @type packets: C{Packet} |
|---|
| 74 | @param packets: list of umit.umpa.Packet objects to send. |
|---|
| 75 | |
|---|
| 76 | @returns: List of return values (byte counts) from the send() function. |
|---|
| 77 | """ |
|---|
| 78 | |
|---|
| 79 | sent_bytes = [] |
|---|
| 80 | for packet in packets: |
|---|
| 81 | # create appropriate socket based on packet's lowermost layer |
|---|
| 82 | if packet.protos[0].layer == 2: |
|---|
| 83 | sock = SocketL2(iface=kwargs.get('iface')) |
|---|
| 84 | else: |
|---|
| 85 | sock = SocketL3() |
|---|
| 86 | sent_bytes.extend(sock.send(packet)) |
|---|
| 87 | return sent_bytes |
|---|
| 88 | |
|---|
| 89 | class SocketL2(object): |
|---|
| 90 | """ |
|---|
| 91 | Level 2 (link-layer) socket class. |
|---|
| 92 | |
|---|
| 93 | Supported platforms: Linux (AF_PACKET), BSD (bpf). |
|---|
| 94 | """ |
|---|
| 95 | |
|---|
| 96 | def __init__(self, iface=None): |
|---|
| 97 | """ |
|---|
| 98 | Create a new SocketL2. |
|---|
| 99 | |
|---|
| 100 | @type iface: C{str} |
|---|
| 101 | @param iface: Interface to use for sending the packets. |
|---|
| 102 | """ |
|---|
| 103 | if iface is None: |
|---|
| 104 | # TODO: port interface detection from the link-layer branch |
|---|
| 105 | raise NotImplementedError("You need to specify iface") |
|---|
| 106 | |
|---|
| 107 | if _l2model == 'AF_PACKET': |
|---|
| 108 | try: |
|---|
| 109 | self._sock = socket.socket(socket.AF_PACKET, |
|---|
| 110 | socket.SOCK_RAW, |
|---|
| 111 | ETH_P_ALL) |
|---|
| 112 | except socket.error, msg: |
|---|
| 113 | raise UMPANotPermittedException(msg) |
|---|
| 114 | |
|---|
| 115 | self._sock.bind((iface, ETH_P_ALL)) |
|---|
| 116 | self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2**20) |
|---|
| 117 | elif _l2model == 'bpf': |
|---|
| 118 | try: |
|---|
| 119 | self._sock = open("/dev/bpf", 'w') |
|---|
| 120 | except IOError, msg: |
|---|
| 121 | raise UMPAException('cannot open /dev/bpf: ' + msg) |
|---|
| 122 | |
|---|
| 123 | ioctl(self._sock, BIOCSETIF, iface) |
|---|
| 124 | else: |
|---|
| 125 | raise NotImplementedError("L2 sockets unsupported on your platform") |
|---|
| 126 | |
|---|
| 127 | def send(self, *packets): |
|---|
| 128 | """ |
|---|
| 129 | Send packets through the socket. |
|---|
| 130 | |
|---|
| 131 | @type packets: C{Packet} |
|---|
| 132 | @param packets: List of umit.umpa.Packet objects to send. |
|---|
| 133 | |
|---|
| 134 | @returns: List of return values (byte counts) from the send() function. |
|---|
| 135 | """ |
|---|
| 136 | |
|---|
| 137 | sent_bytes = [] |
|---|
| 138 | for packet in packets: |
|---|
| 139 | if _l2model == 'AF_PACKET': |
|---|
| 140 | sent_bytes.append(self._sock.send(packet.get_raw())) |
|---|
| 141 | elif _l2model == 'bpf': |
|---|
| 142 | sent_bytes.append(self._sock.write(packet.get_raw())) |
|---|
| 143 | else: |
|---|
| 144 | raise NotImplementedError("L2 send unsupported on your platform") |
|---|
| 145 | return sent_bytes |
|---|
| 146 | |
|---|
| 147 | # TODO: legacy L3 code below, not integrated yet |
|---|
| 148 | |
|---|
| 149 | class Socket(object): |
|---|
| 150 | """ |
|---|
| 151 | This class handles with sockets. |
|---|
| 152 | |
|---|
| 153 | To send built packets your need to create a socket. |
|---|
| 154 | You can use socket module from Python Standard Library directly |
|---|
| 155 | but it's recommended to use this class instead. |
|---|
| 156 | |
|---|
| 157 | That is because there are some other features, |
|---|
| 158 | and for some security issues. |
|---|
| 159 | """ |
|---|
| 160 | |
|---|
| 161 | def __init__(self): |
|---|
| 162 | """ |
|---|
| 163 | Create a new Socket(). |
|---|
| 164 | """ |
|---|
| 165 | |
|---|
| 166 | # to create socket object we need root priviligies. |
|---|
| 167 | # if non-root EUID, then exception is raised |
|---|
| 168 | # use umit.umpa.utils.security.super_priviliges() to avoid exception |
|---|
| 169 | # when a new Socket object is created |
|---|
| 170 | try: |
|---|
| 171 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, |
|---|
| 172 | socket.IPPROTO_RAW) |
|---|
| 173 | except socket.error, msg: |
|---|
| 174 | raise UMPANotPermittedException(msg) |
|---|
| 175 | |
|---|
| 176 | # to build own headers of IP |
|---|
| 177 | self._sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) |
|---|
| 178 | |
|---|
| 179 | def send(self, *packets): |
|---|
| 180 | """ |
|---|
| 181 | Send packets in to the network. |
|---|
| 182 | |
|---|
| 183 | @type packets: C{Packet} |
|---|
| 184 | @param packets: packets which were built by umit.umpa.Packet objects. |
|---|
| 185 | """ |
|---|
| 186 | |
|---|
| 187 | sent_bits = [] |
|---|
| 188 | for packet in packets: |
|---|
| 189 | # XXX try to use similar mechanism as is for SocketL2 |
|---|
| 190 | # to pick up correct interface, using bind() and send() |
|---|
| 191 | # then there is no need to pick out a destination address |
|---|
| 192 | dst_addr = self._get_address(packet) |
|---|
| 193 | # if dst_addr is a tuple, convert it to a string; works only for IPv4 |
|---|
| 194 | if type(dst_addr) is tuple: |
|---|
| 195 | dst_addr = ".".join(str(y) for y in dst_addr) |
|---|
| 196 | sent_bits.append(self._sock.sendto(packet.get_raw(), |
|---|
| 197 | (dst_addr, 0))) |
|---|
| 198 | return sent_bits |
|---|
| 199 | |
|---|
| 200 | def _get_address(self, packet): |
|---|
| 201 | """ |
|---|
| 202 | Pick out the destination address from 3rd layer. |
|---|
| 203 | |
|---|
| 204 | @return: destination address from 3rd layer of OSI model. |
|---|
| 205 | """ |
|---|
| 206 | for proto in packet.protos: |
|---|
| 207 | if proto.layer == 3: # XXX: if we included more than one protocol |
|---|
| 208 | break # of layer 3 we got IP from the first one |
|---|
| 209 | |
|---|
| 210 | if not proto: |
|---|
| 211 | raise UMPAException("There is not prototocol from 3rd layer.") |
|---|
| 212 | |
|---|
| 213 | return proto.dst |
|---|