2018-01-02 13:57:39 +00:00
|
|
|
|
# −*− coding: UTF−8 −*−
|
|
|
|
|
#/**
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# * Software Name : pycrate
|
2019-02-25 10:26:10 +00:00
|
|
|
|
# * Version : 0.4
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# *
|
|
|
|
|
# * Copyright © 2013. Benoit Michau. ANSSI.
|
|
|
|
|
# *
|
2018-04-15 19:47:21 +00:00
|
|
|
|
# * 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.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# *
|
2018-04-15 19:47:21 +00:00
|
|
|
|
# * This library is distributed in the hope that it will be useful,
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
2018-04-15 19:47:21 +00:00
|
|
|
|
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
|
# * Lesser General Public License for more details.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# *
|
2018-04-15 19:47:21 +00:00
|
|
|
|
# * 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., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
|
# * MA 02110-1301 USA
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# *
|
|
|
|
|
# *--------------------------------------------------------
|
|
|
|
|
# * File Name : pycrate_corenet/ServerGTPU.py
|
|
|
|
|
# * Created : 2013-11-04
|
|
|
|
|
# * Authors : Benoit Michau
|
|
|
|
|
# *--------------------------------------------------------
|
|
|
|
|
#*/
|
|
|
|
|
|
2018-02-09 21:06:38 +00:00
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
# GTP-U handler works with Linux PF_PACKET RAW socket on the Internet side
|
|
|
|
|
# and with standard GTP-U 3GPP protocol on the RNC / eNB side
|
|
|
|
|
# RNC / eNB <== [IP/UDP/GTPU/IP_mobile] ==> GTPUd <== [RawEthernet/IP_mobile] ==> Internet
|
|
|
|
|
#
|
|
|
|
|
# This way, the complete IP interface of a mobile is exposed through this Gi interface.
|
|
|
|
|
# It requires the GTPUd to resolve ARP request on behalf of mobiles that it handles:
|
|
|
|
|
# this is the role of ARPd
|
|
|
|
|
#------------------------------------------------------------------------------#
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
# filtering exports
|
|
|
|
|
__all__ = ['ARPd', 'GTPUd', 'DPI', 'MOD', 'DNSRESP', 'TCPSYNACK']
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import signal
|
|
|
|
|
#
|
|
|
|
|
if os.name != 'nt':
|
|
|
|
|
from fcntl import ioctl
|
|
|
|
|
from socket import timeout
|
|
|
|
|
from random import _urandom
|
|
|
|
|
else:
|
|
|
|
|
print('[ERR] ServerGTPU : you\'re not on *nix system. It\'s not going to work:\n'\
|
|
|
|
|
'You need PF_PACKET socket')
|
|
|
|
|
|
|
|
|
|
from .utils import *
|
|
|
|
|
from pycrate_core.elt import Envelope
|
|
|
|
|
from pycrate_ether.IP import *
|
|
|
|
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
# setting / unsetting ethernet IF in promiscuous mode #
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
# copied from scapy (scapy/scapy/arch/linux.py)
|
|
|
|
|
|
|
|
|
|
SIOCGIFINDEX = 0x8933 # name -> if_index mapping
|
|
|
|
|
SOL_PACKET = 263
|
|
|
|
|
PACKET_MR_PROMISC = 1
|
|
|
|
|
PACKET_ADD_MEMBERSHIP = 1
|
|
|
|
|
PACKET_DROP_MEMBERSHIP = 2
|
|
|
|
|
|
|
|
|
|
def get_if(iff, cmd):
|
|
|
|
|
"""Ease SIOCGIF* ioctl calls"""
|
|
|
|
|
sk = socket.socket()
|
|
|
|
|
ifreq = ioctl(sk, cmd, pack('16s16x', iff.encode('utf8')))
|
|
|
|
|
sk.close()
|
|
|
|
|
return ifreq
|
|
|
|
|
|
|
|
|
|
def get_if_index(iff):
|
|
|
|
|
return int(unpack('I', get_if(iff, SIOCGIFINDEX)[16:20])[0])
|
|
|
|
|
|
|
|
|
|
def set_promisc(sk, iff, val=1):
|
|
|
|
|
mreq = pack('IHH8s', get_if_index(iff), PACKET_MR_PROMISC, 0, b'')
|
|
|
|
|
if val:
|
|
|
|
|
cmd = PACKET_ADD_MEMBERSHIP
|
|
|
|
|
else:
|
|
|
|
|
cmd = PACKET_DROP_MEMBERSHIP
|
|
|
|
|
sk.setsockopt(SOL_PACKET, cmd, mreq)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
# ARPd #
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
class ARPd(object):
|
|
|
|
|
'''
|
|
|
|
|
ARP resolver
|
2018-02-09 21:06:38 +00:00
|
|
|
|
resolves Ethernet / IPv4 address correspondence on behalf of UE connected over
|
2018-01-02 13:57:39 +00:00
|
|
|
|
GTP-U.
|
|
|
|
|
|
|
|
|
|
The method .resolve(ipaddr) returns the MAC address for the requested IP
|
|
|
|
|
address.
|
|
|
|
|
It runs a background thread too, that answers ARP requests on behalf of
|
|
|
|
|
connected mobiles.
|
|
|
|
|
|
|
|
|
|
When handling mobiles' network interfaces over GTP-U, the following steps
|
|
|
|
|
are followed:
|
|
|
|
|
- for outgoing packets:
|
|
|
|
|
1) for any destination IP outside of our network (e.g. 192.168.1.0/24),
|
|
|
|
|
provide the ROUTER_MAC_ADDR directly
|
|
|
|
|
2) for local destination IP address in our subnet,
|
|
|
|
|
provide the corresponding MAC address after an ARP resolution
|
|
|
|
|
- for incoming packets:
|
|
|
|
|
we must answer the router's or local hosts' ARP requests
|
|
|
|
|
before being able to receive IP packets to be transferred to the mobiles
|
|
|
|
|
|
|
|
|
|
ARPd:
|
|
|
|
|
maintains the ARP_RESOLV_TABLE
|
|
|
|
|
listens on the ethernet interface for:
|
|
|
|
|
- incoming ARP requests, and answer it for IP addresses from our IP_POOL
|
|
|
|
|
- incoming ARP responses (due to the daemon sending ARP requests)
|
|
|
|
|
- incoming IP packets (thanks to promiscous mode) to update the ARP_RESOLV_TABLE
|
|
|
|
|
with new MAC addresses opportunistically
|
|
|
|
|
sends ARP request when needed to be able then to forward IP packets from mobile
|
|
|
|
|
'''
|
|
|
|
|
#
|
|
|
|
|
# verbosity level: list of log types to display when calling
|
|
|
|
|
# self._log(logtype, msg)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# recv() buffer length
|
2018-01-14 13:20:58 +00:00
|
|
|
|
BUFLEN = 2048
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# select() timeout and wait period
|
2018-01-14 13:20:58 +00:00
|
|
|
|
SELECT_TO = 0.1
|
|
|
|
|
SELECT_SLEEP = 0.05
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# all Gi interface parameters
|
|
|
|
|
# Our GGSN ethernet parameters (IF, MAC and IP addresses)
|
|
|
|
|
# (and also the MAC address to be used for any mobiles through our GGSN)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
GGSN_ETH_IF = 'eth0'
|
|
|
|
|
GGSN_MAC_ADDR = '08:00:00:01:02:03'
|
|
|
|
|
GGSN_IP_ADDR = '192.168.1.100'
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# the set of IP address to be used by our mobiles
|
2018-01-14 13:20:58 +00:00
|
|
|
|
IP_POOL = {'192.168.1.201', '192.168.1.202', '192.168.1.203'}
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# network parameters:
|
|
|
|
|
# subnet prefix
|
|
|
|
|
# WNG: we only handle IPv4 /24 subnet
|
|
|
|
|
SUBNET_PREFIX = '192.168.1.0/24'
|
|
|
|
|
# and 1st IP router (MAC and IP addresses)
|
|
|
|
|
# this is to resolve directly any IP outside our subnet
|
|
|
|
|
ROUTER_MAC_ADDR = 'f4:00:00:01:02:03'
|
|
|
|
|
ROUTER_IP_ADDR = '192.168.1.1'
|
|
|
|
|
#
|
|
|
|
|
CATCH_SIGINT = False
|
|
|
|
|
|
|
|
|
|
def __init__(self, opportunist=False):
|
|
|
|
|
#
|
|
|
|
|
self.GGSN_MAC_BUF = mac_aton(self.GGSN_MAC_ADDR)
|
|
|
|
|
self.GGSN_IP_BUF = inet_aton(self.GGSN_IP_ADDR)
|
|
|
|
|
self.ROUTER_MAC_BUF = mac_aton(self.ROUTER_MAC_ADDR)
|
|
|
|
|
self.ROUTER_IP_BUF = inet_aton(self.ROUTER_IP_ADDR)
|
|
|
|
|
# use an uint32 for the subnet prefix
|
|
|
|
|
prefip, prefmask = self.SUBNET_PREFIX.split('/')
|
|
|
|
|
pref = unpack('>I', inet_aton(prefip))[0]
|
|
|
|
|
self.SUBNET_MASK = (1<<32)-(1<<(32-int(prefmask)))
|
|
|
|
|
self.SUBNET_PREFIX = pref & self.SUBNET_MASK
|
|
|
|
|
#
|
|
|
|
|
# init RAW ethernet socket for ARP
|
|
|
|
|
self.sk_arp = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(0x0806))
|
|
|
|
|
self.sk_arp.settimeout(0.1)
|
|
|
|
|
#self.sk_arp.setsockopt(SOL_PACKET, SO_RCVBUF, 0)
|
|
|
|
|
self.sk_arp.bind((self.GGSN_ETH_IF, 0x0806))
|
|
|
|
|
#self.sk_arp.setsockopt(SOL_PACKET, SO_RCVBUF, 2**24)
|
|
|
|
|
#
|
|
|
|
|
# init RAW ethernet socket for IPv4
|
|
|
|
|
self.sk_ip = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(0x0800))
|
|
|
|
|
self.sk_ip.settimeout(0.1)
|
|
|
|
|
#self.sk_ip.setsockopt(SOL_PACKET, SO_RCVBUF, 0)
|
|
|
|
|
self.sk_ip.bind((self.GGSN_ETH_IF, 0x0800))
|
|
|
|
|
#self.sk_ip.setsockopt(SOL_PACKET, SO_RCVBUF, 2**24)
|
|
|
|
|
#
|
|
|
|
|
# ARP resolution table
|
|
|
|
|
self.ARP_RESOLV_TABLE = {
|
|
|
|
|
self.ROUTER_IP_ADDR : self.ROUTER_MAC_BUF,
|
|
|
|
|
self.GGSN_IP_ADDR : self.GGSN_MAC_BUF,
|
|
|
|
|
}
|
|
|
|
|
for ip in self.IP_POOL:
|
|
|
|
|
self.ARP_RESOLV_TABLE[ip] = self.GGSN_MAC_BUF
|
|
|
|
|
#
|
|
|
|
|
# interrupt handler
|
|
|
|
|
if self.CATCH_SIGINT:
|
|
|
|
|
def sigint_handler(signum, frame):
|
|
|
|
|
if self.DEBUG > 1:
|
|
|
|
|
self._log('INF', 'CTRL+C caught')
|
|
|
|
|
self.stop()
|
|
|
|
|
signal.signal(signal.SIGINT, sigint_handler)
|
|
|
|
|
#
|
|
|
|
|
self.set_opportunist(opportunist)
|
|
|
|
|
# starting main listening loop in background
|
|
|
|
|
self._listening = True
|
|
|
|
|
self._listener_t = threadit(self.listen)
|
|
|
|
|
self._log('INF', 'ARP resolver started')
|
|
|
|
|
#
|
|
|
|
|
# .resolve(ip) method is available for ARP resolution by GTPUd
|
|
|
|
|
|
|
|
|
|
def _log(self, logtype='DBG', msg=''):
|
|
|
|
|
# logtype: 'ERR', 'WNG', 'INF', 'DBG'
|
|
|
|
|
if logtype in self.DEBUG:
|
|
|
|
|
log('[%s] [ARPd] %s' % (logtype, msg))
|
|
|
|
|
|
|
|
|
|
def set_opportunist(self, state):
|
|
|
|
|
if state:
|
|
|
|
|
self.sk_list = (self.sk_arp, self.sk_ip)
|
|
|
|
|
else:
|
|
|
|
|
self.sk_list = (self.sk_arp, )
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
if self._listening:
|
|
|
|
|
self._listening = False
|
|
|
|
|
sleep(self.SELECT_TO * 2)
|
|
|
|
|
try:
|
|
|
|
|
self.sk_arp.close()
|
|
|
|
|
self.sk_ip.close()
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'socket error: {0}'.format(err))
|
|
|
|
|
|
|
|
|
|
def listen(self):
|
|
|
|
|
# select() until we receive arp or ip packet
|
|
|
|
|
while self._listening:
|
|
|
|
|
r = []
|
|
|
|
|
r = select(self.sk_list, [], [], self.SELECT_TO)[0]
|
|
|
|
|
for sk in r:
|
|
|
|
|
try:
|
|
|
|
|
buf = sk.recvfrom(self.BUFLEN)[0]
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'external network error (recvfrom): %s' % err)
|
|
|
|
|
buf = b''
|
|
|
|
|
# dipatch ARP request / IP response
|
|
|
|
|
if sk != self.sk_arp:
|
|
|
|
|
# sk == self.sk_ip
|
|
|
|
|
if len(buf) >= 34 and buf[12:14] == b'\x08\x00':
|
|
|
|
|
self._process_ipbuf(buf)
|
|
|
|
|
else:
|
|
|
|
|
# sk == self.sk_arp
|
|
|
|
|
if len(buf) >= 42 and buf[12:14] == b'\x08\x06':
|
|
|
|
|
self._process_arpbuf(buf)
|
|
|
|
|
#
|
|
|
|
|
# if select() timeouts, take a little rest
|
|
|
|
|
if len(r) == 0:
|
|
|
|
|
sleep(self.SELECT_SLEEP)
|
|
|
|
|
self._log('INF', 'ARP resolver stopped')
|
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def _process_arpbuf(self, buf):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# this is an ARP request or response:
|
|
|
|
|
arpop = ord(buf[21:22])
|
|
|
|
|
# 1) check if it requests for one of our IP
|
|
|
|
|
if arpop == 1:
|
|
|
|
|
ipreq = inet_ntoa(buf[38:42])
|
|
|
|
|
if ipreq in self.IP_POOL:
|
|
|
|
|
# reply to it with our MAC ADDR
|
|
|
|
|
try:
|
|
|
|
|
self.sk_arp.sendto(
|
2018-01-14 13:20:58 +00:00
|
|
|
|
b''.join((buf[6:12], self.GGSN_MAC_BUF, # Ethernet hdr
|
|
|
|
|
b'\x08\x06\0\x01\x08\0\x06\x04\0\x02',
|
|
|
|
|
self.GGSN_MAC_BUF, buf[38:42], # ARP sender
|
|
|
|
|
buf[6:12], buf[28:32], # ARP target
|
|
|
|
|
b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')),
|
|
|
|
|
(self.GGSN_ETH_IF, 0x0806))
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('ERR', 'external network error (sendto) on ARP response: %s' % err)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
|
|
|
|
self._log('DBG', 'ARP response sent for IP: %s' % ipreq)
|
|
|
|
|
# 2) check if it responses something useful for us
|
|
|
|
|
elif arpop == 2:
|
|
|
|
|
ipres_buf = buf[28:32]
|
|
|
|
|
if unpack('>I', ipres_buf)[0] & self.SUBNET_MASK == self.SUBNET_PREFIX:
|
|
|
|
|
ipres = inet_ntoa(ipres_buf)
|
|
|
|
|
if ipres not in self.ARP_RESOLV_TABLE:
|
|
|
|
|
# WNG: no protection (at all) against ARP cache poisoning
|
|
|
|
|
self.ARP_RESOLV_TABLE[ipres] = buf[22:28]
|
|
|
|
|
self._log('DBG', 'got ARP response for new local IP: %s' % ipres)
|
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def _process_ipbuf(self, buf):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# this is an random IPv4 packet incoming into our interface:
|
|
|
|
|
# check if src IP is in our subnet and not already resolved,
|
|
|
|
|
# then store the Ethernet MAC address
|
|
|
|
|
# this is an opportunistic behaviour and disabled by default
|
|
|
|
|
ipsrc_buf = buf[26:30]
|
|
|
|
|
if unpack('>I', ipsrc_buf)[0] & self.SUBNET_MASK == self.SUBNET_PREFIX:
|
|
|
|
|
ipsrc = inet_ntoa(ipsrc_buf)
|
|
|
|
|
if ipsrc not in self.ARP_RESOLV_TABLE:
|
|
|
|
|
# WNG: no protection (at all) against ARP cache poisoning
|
|
|
|
|
self.ARP_RESOLV_TABLE[ipsrc] = buf[6:12]
|
|
|
|
|
self._log('DBG', 'got MAC address from IPv4 packet for new local IP: %s' % ipsrc)
|
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def resolve(self, ip):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# check if already resolved
|
|
|
|
|
if ip in self.ARP_RESOLV_TABLE:
|
|
|
|
|
return self.ARP_RESOLV_TABLE[ip]
|
|
|
|
|
# or outside our local network
|
|
|
|
|
ip_buf = inet_aton(ip)
|
|
|
|
|
if unpack('>i', ip_buf)[0] & self.SUBNET_MASK != self.SUBNET_PREFIX:
|
|
|
|
|
return self.ROUTER_MAC_BUF
|
|
|
|
|
# requesting an IP within our local LAN
|
|
|
|
|
# starting a resolution for it
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
self.sk_arp.sendto(
|
2018-01-14 13:20:58 +00:00
|
|
|
|
b''.join((self.ROUTER_MAC_BUF, self.GGSN_MAC_BUF, # Ethernet hdr
|
|
|
|
|
b'\x08\x06\0\x01\x08\0\x06\x04\0\x01',
|
|
|
|
|
self.GGSN_MAC_BUF, self.GGSN_IP_BUF, # ARP sender
|
|
|
|
|
b'\0\0\0\0\0\0', inet_aton(ip), # ARP target
|
|
|
|
|
b'\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0')),
|
|
|
|
|
(self.GGSN_ETH_IF, 0x0806))
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'external network error (sendto) on ARP request: %s' % err)
|
|
|
|
|
else:
|
|
|
|
|
self._log('DBG', 'ARP request sent for local IP: %s' % ip)
|
|
|
|
|
# wait for the answer
|
|
|
|
|
cnt = 0
|
|
|
|
|
while ip not in self.ARP_RESOLV_TABLE:
|
|
|
|
|
sleep(self.SELECT_SLEEP)
|
|
|
|
|
cnt += 1
|
|
|
|
|
if cnt >= 3:
|
|
|
|
|
break
|
|
|
|
|
if cnt < 3:
|
|
|
|
|
return self.ARP_RESOLV_TABLE[ip]
|
|
|
|
|
else:
|
2019-04-01 11:01:25 +00:00
|
|
|
|
return 6*b'\xFF' # LAN broadcast, maybe a bit strong !
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
# GTPUd #
|
|
|
|
|
#------------------------------------------------------------------------------#
|
|
|
|
|
|
2018-02-09 21:06:38 +00:00
|
|
|
|
BLACKHOLE_LAN = 0b01
|
|
|
|
|
BLACKHOLE_WAN = 0b10
|
|
|
|
|
IPV6_LOCAL_PREF = b'\xfe\x80\0\0\0\0\0\0'
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
class GTPUd(object):
|
|
|
|
|
'''
|
|
|
|
|
GTP-U forwarder
|
2018-02-09 21:06:38 +00:00
|
|
|
|
bridges Ethernet to GTP-U to handle IPv4v6 data traffic of connected UE.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
This is to be instantiated as a unique handler for all GTP-U tunnels
|
2018-01-02 13:57:39 +00:00
|
|
|
|
in the corenet mobile core network.
|
2018-02-09 21:06:38 +00:00
|
|
|
|
To add GTP tunnel endpoints at will, for each mobile, use the methods:
|
|
|
|
|
.add_mobile(teid_ul, mobile_addr, ran_ip, teid_dl)
|
|
|
|
|
- teid_ul will be the key used to index the connection
|
|
|
|
|
- mobile_addr is a 2-tuple (addr_type, ip_addr)
|
|
|
|
|
addr_type: 1 for IPv4, 2 for IPv6, 3 for IPv4v6
|
|
|
|
|
in case of IPv6 address, it is possible to set only the 64 1st bits
|
|
|
|
|
(the network prefix), the full address will be learnt from the 1st uplink
|
|
|
|
|
packet
|
|
|
|
|
- ran_ip is a list with the local IP address and RAN IP address for connecting
|
|
|
|
|
the GTP-U UDP socket endpoints
|
|
|
|
|
-> ran_ip and teid_dl can be None, and set afterwards by calling:
|
|
|
|
|
.set_mobile_dl(teid_ul, ran_ip, teid_dl)
|
|
|
|
|
-> this enables the forwarding of downlink traffic
|
|
|
|
|
To delete GTP tunnel endpoints, use the method:
|
|
|
|
|
.rem_mobile(teid_ul)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
When a GTP-U packet arrives on the internal interface,
|
2018-02-09 21:06:38 +00:00
|
|
|
|
the IP payload is transferred to the external Gi interface over an Ethernet header.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
When an Ethernet packet arrives on the external Gi interface,
|
2018-02-09 21:06:38 +00:00
|
|
|
|
the IP payload is transferred to the internal interface over a GTP-U header.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
A little traffic statistics feature can be used with the class attribute:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
.DPI = True
|
2018-01-02 13:57:39 +00:00
|
|
|
|
Traffic statistics are then placed into the attribute .stats
|
|
|
|
|
It is populated even if GTP-U trafic is not forwarded (see BLACKHOLING)
|
|
|
|
|
|
|
|
|
|
A blackholing feature is integrated to disable the forwarding of GTP-U packet
|
|
|
|
|
to the local LAN (with BLACKHOLE_LAN) and/or the routed WAN (with BLACKHOLE_WAN).
|
2018-02-09 21:06:38 +00:00
|
|
|
|
This is done by setting the .BLACKHOLING class attribute.
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
A whitelist feature (TCP/UDP, port) is also integrated.
|
|
|
|
|
To activate if, set the class attribute:
|
|
|
|
|
WL_ACTIVE = True
|
|
|
|
|
Then, only packets for the given protocols / ports are transferred to the Gi,
|
|
|
|
|
by looking into the class attribute:
|
|
|
|
|
WL_PORTS = [('UDP', 53), ('UDP', 123), ('TCP', 80), ...]
|
|
|
|
|
This is bypassing the blackholing feature.
|
|
|
|
|
'''
|
|
|
|
|
#
|
|
|
|
|
# verbosity level: list of log types to display when calling
|
|
|
|
|
# self._log(logtype, msg)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
DEBUG = ('ERR', 'WNG', 'INF', 'DBG')
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# packet buffer space (over MTU...)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
BUFLEN = 2048
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# select loop settings
|
2018-01-14 13:20:58 +00:00
|
|
|
|
SELECT_TO = 0.1
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# Gi interface, with GGSN ethernet IF, MAC address and IPv6 /64 network prefix
|
2018-01-02 13:57:39 +00:00
|
|
|
|
EXT_IF = ARPd.GGSN_ETH_IF
|
2018-02-09 21:06:38 +00:00
|
|
|
|
EXT_MAC_ADDR = ARPd.GGSN_MAC_ADDR
|
|
|
|
|
EXT_IPV6_PREF = '2001:123:456:abcd'
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# list of internal IP interfaces, for handling GTP-U packets from RNCs / eNBs
|
2018-01-14 13:20:58 +00:00
|
|
|
|
GTP_PORT = 2152
|
2018-02-09 21:06:38 +00:00
|
|
|
|
GTP_IF = ('10.1.1.1', '10.2.1.1', )
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# BLACKHOLING feature
|
|
|
|
|
# to enable the whole traffic: 0
|
|
|
|
|
# to disable traffic routed to the WAN: BLACKHOLE_WAN
|
|
|
|
|
# to disable traffic to the local LAN: BLACKHOLE_LAN
|
|
|
|
|
# to disable the whole forwarding of GTP-U packets: BLACKHOLE_LAN | BLACKHOLE_WAN
|
|
|
|
|
BLACKHOLING = 0
|
|
|
|
|
# traffic that we want to allow, even if BLACKHOLING is activated
|
|
|
|
|
WL_ACTIVE = False
|
|
|
|
|
WL_PORTS = [('UDP', 53), ('UDP', 123)]
|
|
|
|
|
#
|
|
|
|
|
# in case we want to generate traffic statistics (then available in .stats)
|
|
|
|
|
DPI = True
|
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# in case we want to check and drop spoofed IPv4/v6 source address
|
|
|
|
|
# in incoming GTP-U packet
|
2018-01-02 13:57:39 +00:00
|
|
|
|
DROP_SPOOF = True
|
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# in case we want to stop the listener when typing CTRL+C
|
2018-01-02 13:57:39 +00:00
|
|
|
|
CATCH_SIGINT = False
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
#
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self.EXT_MAC_BUF = mac_aton(self.EXT_MAC_ADDR)
|
|
|
|
|
self.IPV6_NET_PREF = inet_pton(AF_INET6, self.EXT_IPV6_PREF + '::')[:8]
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# 2 dict for handling mobile GTP-U packets transfers:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# key: mobile IPv4 address or v6 if suffix address (4 or 8 bytes)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# value: teid_ul (uint)
|
|
|
|
|
self._mobiles_addr = {}
|
|
|
|
|
# key: teid_ul (uint)
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# value: [ran_info (3-tuple: local IP, remote IP, sk_int ref),
|
|
|
|
|
# teid_dl (uint),
|
|
|
|
|
# ipv4_addr (4-bytes or None),
|
|
|
|
|
# ipv6_addr (8-bytes -if addr suffix- or None),
|
|
|
|
|
# ctx_num (uint)]
|
2018-01-02 13:57:39 +00:00
|
|
|
|
self._mobiles_teid = {}
|
|
|
|
|
#
|
|
|
|
|
# initialize the traffic statistics
|
|
|
|
|
self.stats = {}
|
|
|
|
|
self._prot_dict = {1:'ICMP', 6:'TCP', 17:'UDP'}
|
|
|
|
|
# initialize the list of modules that can act on GTP-U payloads
|
|
|
|
|
self.MOD = []
|
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# create two RAW PF_PACKET sockets on the `Internet` side (1 for IPv4, 1 for IPv6)
|
|
|
|
|
self.sk_ext_v4 = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
|
|
|
|
|
self.sk_ext_v4.settimeout(0.001)
|
|
|
|
|
#self.sk_ext_v4.setblocking(0)
|
|
|
|
|
self.sk_ext_v4.bind((self.EXT_IF, 0x0800))
|
|
|
|
|
set_promisc(self.sk_ext_v4, self.EXT_IF, 1)
|
|
|
|
|
#
|
|
|
|
|
self.sk_ext_v6 = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ntohs(0x86dd))
|
|
|
|
|
self.sk_ext_v6.settimeout(0.001)
|
|
|
|
|
#self.sk_ext_v6.setblocking(0)
|
|
|
|
|
self.sk_ext_v6.bind((self.EXT_IF, 0x86dd))
|
|
|
|
|
set_promisc(self.sk_ext_v6, self.EXT_IF, 1)
|
|
|
|
|
#
|
|
|
|
|
# create an UDP socket on the RNC / eNB side
|
2018-02-09 21:06:38 +00:00
|
|
|
|
sk_int, sk_int_ind, ind = [], {}, 0
|
|
|
|
|
for gtpip in self.GTP_IF:
|
|
|
|
|
sk = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
|
|
|
sk.settimeout(0.001)
|
|
|
|
|
#sk.setblocking(0)
|
|
|
|
|
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
|
sk.bind((gtpip, self.GTP_PORT))
|
|
|
|
|
sk_int.append(sk)
|
|
|
|
|
sk_int_ind[gtpip] = ind
|
|
|
|
|
ind += 1
|
|
|
|
|
self.sk_int = tuple(sk_int)
|
|
|
|
|
self._sk_int_ind = sk_int_ind
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
# interrupt handler
|
|
|
|
|
if self.CATCH_SIGINT:
|
|
|
|
|
def sigint_handler(signum, frame):
|
|
|
|
|
if self.DEBUG > 1:
|
|
|
|
|
self._log('INF', 'CTRL+C caught')
|
|
|
|
|
self.stop()
|
|
|
|
|
signal.signal(signal.SIGINT, sigint_handler)
|
|
|
|
|
#
|
|
|
|
|
# and start listening and transferring packets in background
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self.sk_list = (self.sk_ext_v4, self.sk_ext_v6) + self.sk_int
|
2018-01-02 13:57:39 +00:00
|
|
|
|
self._listening = True
|
|
|
|
|
self._listener_t = threadit(self.listen)
|
|
|
|
|
self._log('INF', 'GTP-U tunnels handler started')
|
|
|
|
|
#
|
|
|
|
|
# and finally start ARP resolver
|
|
|
|
|
self.arpd = ARPd()
|
|
|
|
|
|
|
|
|
|
def _log(self, logtype='DBG', msg=''):
|
|
|
|
|
# logtype: 'ERR', 'WNG', 'INF', 'DBG'
|
|
|
|
|
if logtype in self.DEBUG:
|
|
|
|
|
log('[%s] [GTPUd] %s' % (logtype, msg))
|
|
|
|
|
|
|
|
|
|
def init_stats(self, ip):
|
2018-01-14 13:20:58 +00:00
|
|
|
|
stats = {
|
|
|
|
|
'DNS' : set(), # IP of DNS servers requested
|
|
|
|
|
'NTP' : set(), # IP of NTP servers requested
|
|
|
|
|
'resolved': set(), # domain name resolved
|
|
|
|
|
'ICMP' : set(), # ICMP endpoint (IP) contacted
|
|
|
|
|
'TCP' : set(), # TCP endpoint (IP, port) contacted
|
|
|
|
|
'UDP' : set(), # UDP endpoint (IP, port) contacted
|
|
|
|
|
'alien' : set(), # other protocol packets
|
2018-01-02 13:57:39 +00:00
|
|
|
|
}
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self.stats[ip] = stats
|
|
|
|
|
return stats
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
# stop ARP resolver
|
|
|
|
|
self.arpd.stop()
|
|
|
|
|
# stop local GTPU handler
|
|
|
|
|
if self._listening:
|
|
|
|
|
self._listening = False
|
|
|
|
|
sleep(self.SELECT_TO * 2)
|
|
|
|
|
try:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
set_promisc(self.sk_ext_v4, self.EXT_IF, 0)
|
|
|
|
|
set_promisc(self.sk_ext_v6, self.EXT_IF, 0)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# closing sockets
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self.sk_ext_v4.close()
|
|
|
|
|
self.sk_ext_v6.close()
|
2018-02-09 21:06:38 +00:00
|
|
|
|
for sk in self.sk_int:
|
|
|
|
|
sk.close()
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'socket error: %s' % err)
|
|
|
|
|
|
|
|
|
|
def listen(self):
|
|
|
|
|
# select() until we receive something on 1 side
|
|
|
|
|
while self._listening:
|
|
|
|
|
r = select(self.sk_list, [], [], self.SELECT_TO)[0]
|
|
|
|
|
# read ext and int sockets until they are empty
|
|
|
|
|
for sk in r:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
#
|
|
|
|
|
if sk == self.sk_ext_v4:
|
|
|
|
|
# DL IPv4
|
|
|
|
|
try:
|
|
|
|
|
buf = sk.recvfrom(self.BUFLEN)[0]
|
|
|
|
|
#except timeout:
|
|
|
|
|
# pass
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'sk_ext_v4 IF error (recvfrom): %s' % err)
|
|
|
|
|
else:
|
|
|
|
|
#self._log('DBG', 'sk_ext_v4, recvfrom()')
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if len(buf) >= 34 and buf[:6] == self.EXT_MAC_BUF \
|
2018-01-14 13:20:58 +00:00
|
|
|
|
and buf[30:34] in self._mobiles_addr:
|
|
|
|
|
# IPv4 of a mobile, transfer over GTP-U
|
|
|
|
|
# after removing the Ethernet header
|
|
|
|
|
self.transfer_v4_to_int(buf[14:])
|
|
|
|
|
#threadit(self.transfer_v4_to_int, buf[14:])
|
|
|
|
|
#
|
|
|
|
|
elif sk == self.sk_ext_v6:
|
|
|
|
|
# DL IPv6
|
2018-01-02 13:57:39 +00:00
|
|
|
|
try:
|
|
|
|
|
buf = sk.recvfrom(self.BUFLEN)[0]
|
|
|
|
|
#except timeout:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# pass
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('ERR', 'sk_ext_v6 IF error (recvfrom): %s' % err)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
#self._log('DBG', 'sk_ext_v6, recvfrom()')
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if len(buf) >= 54 and buf[:6] == self.EXT_MAC_BUF \
|
|
|
|
|
and buf[46:54] in self._mobiles_addr:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# IPv6 of a mobile, transfer over GTP-U
|
|
|
|
|
# after removing the Ethernet header
|
|
|
|
|
self.transfer_v6_to_int(buf[14:])
|
|
|
|
|
#threadit(self.transfer_v6_to_int, buf[14:])
|
|
|
|
|
#
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
#sk in self.sk_int
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# UL, both IPv4 and IPv6 packets
|
2018-01-02 13:57:39 +00:00
|
|
|
|
try:
|
|
|
|
|
buf = sk.recv(self.BUFLEN)
|
|
|
|
|
#except timeout:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# pass
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('ERR', 'sk_int IF error (recv): %s' % err)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
|
|
|
|
self.transfer_to_ext(buf)
|
|
|
|
|
#threadit(self.transfer_to_ext, buf)
|
|
|
|
|
#
|
|
|
|
|
self._log('INF', 'GTPU handler stopped')
|
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def resolve_mac(self, ipdst):
|
|
|
|
|
if len(ipdst) == 4:
|
|
|
|
|
return self.arpd.resolve(inet_ntoa(ipdst))
|
|
|
|
|
else:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# TODO: implement a minimal IPv6 NDP service ?
|
|
|
|
|
return self.arpd.ROUTER_MAC_BUF
|
2018-01-14 13:20:58 +00:00
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
# UL transfer
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
2018-01-02 13:57:39 +00:00
|
|
|
|
def transfer_to_ext(self, buf):
|
|
|
|
|
try:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# extract the GTP header
|
|
|
|
|
flags, msgtype, msglen, teid_ul = unpack('>BBHI', buf[:8])
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ran_info, teid_dl, ipv4buf, ipv6buf, ctx_num = self._mobiles_teid[teid_ul]
|
2018-01-14 13:20:58 +00:00
|
|
|
|
if msgtype != 0xff:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# TODO: handle GTP ECHO
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('WNG', 'unsupported GTP type from RAN: 0x%.2x' % msgtype)
|
|
|
|
|
return
|
|
|
|
|
# get the IP packet: use the length in the GTP header to cut the buffer
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if flags & 0b111:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# GTP header extended
|
|
|
|
|
msglen -= 4
|
|
|
|
|
ipbuf = buf[-msglen:]
|
|
|
|
|
# get the IP version
|
|
|
|
|
ipvers = ord(ipbuf[0:1])>>4
|
|
|
|
|
if ipvers == 4:
|
|
|
|
|
ipsrc = ipbuf[12:16]
|
|
|
|
|
ipdst = ipbuf[16:20]
|
|
|
|
|
elif ipvers == 6:
|
|
|
|
|
ipsrc = ipbuf[8:24]
|
|
|
|
|
ipdst = ipbuf[24:40]
|
|
|
|
|
else:
|
|
|
|
|
self._log('WNG', 'invalid IP packet from UE, dropping it')
|
|
|
|
|
return
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('WNG', 'invalid GTP / IP packet from RAN / UE, dropping it')
|
2018-01-02 13:57:39 +00:00
|
|
|
|
return
|
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
if ipvers == 4:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if self.DROP_SPOOF and ipsrc != ipv4buf:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('WNG', 'spoofed IPv4 src addr, teid_ul 0x%.8x' % teid_ul)
|
|
|
|
|
return
|
|
|
|
|
if self.DPI:
|
|
|
|
|
self._analyze(ipvers, inet_ntoa(ipsrc), ipbuf)
|
|
|
|
|
if self.MOD:
|
|
|
|
|
try:
|
|
|
|
|
for mod in self.MOD:
|
|
|
|
|
if mod.TYPE == 0:
|
|
|
|
|
ipbuf = mod.handle_ul(ipbuf)
|
|
|
|
|
else:
|
|
|
|
|
mod.handle_ul(ipbuf)
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'MOD error: %s' % err)
|
|
|
|
|
# resolve the dest MAC addr
|
|
|
|
|
macdst = self.resolve_mac(ipdst)
|
|
|
|
|
# apply blackholing
|
|
|
|
|
if self.BLACKHOLING:
|
|
|
|
|
if macdst != self.arpd.ROUTER_MAC_BUF:
|
|
|
|
|
if self.BLACKHOLING & BLACKHOLE_LAN:
|
|
|
|
|
drop = True
|
|
|
|
|
else:
|
|
|
|
|
drop = False
|
|
|
|
|
else:
|
|
|
|
|
if self.BLACKHOLING & BLACKHOLE_WAN:
|
|
|
|
|
drop = True
|
|
|
|
|
else:
|
|
|
|
|
drop = False
|
|
|
|
|
if drop and self.WL_ACTIVE:
|
|
|
|
|
ipdst, prot, pay = DPIv4.get_ip_info(ipbuf)
|
|
|
|
|
if prot in (6, 17) and pay:
|
|
|
|
|
# UDP / TCP
|
|
|
|
|
port = DPIv4.get_port(pay)
|
|
|
|
|
if (self._prot_dict[prot], port) in self.WL_PORTS:
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self._transfer_v4_to_ext(macdst, ipbuf)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
else:
|
|
|
|
|
return
|
|
|
|
|
else:
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self._transfer_v4_to_ext(macdst, ipbuf)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
else:
|
|
|
|
|
#ipvers == 6
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if self.DROP_SPOOF and ipsrc[8:] != ipv6buf:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('WNG', 'spoofed IPv6 src addr, teid_ul 0x%.8x' % teid_ul)
|
|
|
|
|
return
|
|
|
|
|
if self.DPI:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._analyze(ipvers, inet_ntop(AF_INET6, ipsrc), ipbuf)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
if self.MOD:
|
|
|
|
|
try:
|
|
|
|
|
for mod in self.MOD:
|
|
|
|
|
if mod.TYPE == 0:
|
|
|
|
|
ipbuf = mod.handle_ul(ipbuf)
|
|
|
|
|
else:
|
|
|
|
|
mod.handle_ul(ipbuf)
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'MOD error: %s' % err)
|
|
|
|
|
# resolve the dest MAC addr
|
|
|
|
|
macdst = self.resolve_mac(ipdst)
|
|
|
|
|
# apply blackholing
|
|
|
|
|
if self.BLACKHOLING:
|
|
|
|
|
if macdst != self.arpd.ROUTER_MAC_BUF:
|
|
|
|
|
if self.BLACKHOLING & BLACKHOLE_LAN:
|
|
|
|
|
drop = True
|
|
|
|
|
else:
|
|
|
|
|
drop = False
|
|
|
|
|
else:
|
|
|
|
|
if self.BLACKHOLING & BLACKHOLE_WAN:
|
|
|
|
|
drop = True
|
|
|
|
|
else:
|
|
|
|
|
drop = False
|
|
|
|
|
if drop and self.WL_ACTIVE:
|
|
|
|
|
ipdst, prot, pay = DPIv6.get_ip_info(ipbuf)
|
|
|
|
|
if prot in (6, 17) and pay:
|
|
|
|
|
# UDP / TCP
|
|
|
|
|
port = DPIv6.get_port(pay)
|
|
|
|
|
if (self._prot_dict[prot], port) in self.WL_PORTS:
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self._transfer_v6_to_ext(macdst, ipbuf)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
else:
|
|
|
|
|
return
|
|
|
|
|
else:
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self._transfer_v6_to_ext_v6(macdst, ipbuf)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
|
2018-07-15 14:16:49 +00:00
|
|
|
|
def _transfer_v4_to_ext(self, macdst, ipbuf):
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# forward to the external PF_PACKET socket, over the Gi interface
|
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self.sk_ext_v4.sendto(b''.join((macdst, self.EXT_MAC_BUF, b'\x08\0', ipbuf)),
|
2018-01-14 13:20:58 +00:00
|
|
|
|
(self.EXT_IF, 0x0800))
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'sk_ext_v4 IF error (sendto): %s' % err)
|
|
|
|
|
|
2018-07-15 14:16:49 +00:00
|
|
|
|
def _transfer_v6_to_ext(self, macdst, ipbuf):
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# forward to the external PF_PACKET socket, over the Gi interface
|
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self.sk_ext_v6.sendto(b''.join((macdst, self.EXT_MAC_BUF, b'\x86\xdd', ipbuf)),
|
2018-01-14 13:20:58 +00:00
|
|
|
|
(self.EXT_IF, 0x86dd))
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'sk_ext_v6 IF error (sendto): %s' % err)
|
|
|
|
|
|
|
|
|
|
def _analyze(self, ipvers, ipsrc, ipbuf):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
try:
|
|
|
|
|
stats = self.stats[ipsrc]
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
stats = self.init_stats(ipsrc)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
if ipvers == 4:
|
|
|
|
|
dst, prot, pay = DPIv4.get_ip_info(ipbuf)
|
|
|
|
|
DPI = DPIv4
|
|
|
|
|
else:
|
|
|
|
|
dst, prot, pay = DPIv6.get_ip_info(ipbuf)
|
|
|
|
|
DPI = DPIv6
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# UDP
|
|
|
|
|
if prot == 17 and pay:
|
|
|
|
|
port = DPI.get_port(pay)
|
|
|
|
|
stats['UDP'].add((dst, port))
|
|
|
|
|
# DNS
|
|
|
|
|
if port == 53:
|
|
|
|
|
stats['DNS'].add(dst)
|
|
|
|
|
name = DPI.get_dn_req(pay[8:])
|
|
|
|
|
stats['resolved'].add(name)
|
|
|
|
|
elif port == 123:
|
|
|
|
|
stats['NTP'].add(dst)
|
|
|
|
|
# TCP
|
|
|
|
|
elif prot == 6 and pay:
|
|
|
|
|
port = DPI.get_port(pay)
|
|
|
|
|
stats['TCP'].add((dst, port))
|
2018-02-09 21:06:38 +00:00
|
|
|
|
# ICMP / ICMPv6
|
|
|
|
|
elif prot in (1, 58) and pay:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
stats['ICMP'].add(dst)
|
|
|
|
|
# alien
|
|
|
|
|
else:
|
|
|
|
|
stats['alien'].add(hexlify(ipbuf))
|
|
|
|
|
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
# DL transfer
|
|
|
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
|
|
|
|
|
def transfer_v4_to_int(self, buf):
|
|
|
|
|
#self._log('DBG', 'transfer_v4_to_int()')
|
|
|
|
|
# buf length is guaranteed >= 20 and ipdst in self._mobiles_addr
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
if self.MOD:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# possibly process the DL GTP-U payload within modules
|
2018-01-02 13:57:39 +00:00
|
|
|
|
try:
|
|
|
|
|
for mod in self.MOD:
|
|
|
|
|
if mod.TYPE == 0:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
buf = mod.handle_dl(buf)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
mod.handle_dl(buf)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('ERR', 'MOD error: %s' % err)
|
|
|
|
|
#
|
|
|
|
|
teid_ul = self._mobiles_addr[buf[16:20]]
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ran_info, teid_dl = self._mobiles_teid[teid_ul][:2]
|
2018-01-14 13:20:58 +00:00
|
|
|
|
#
|
|
|
|
|
# prepend GTP header and forward to the RAN IP
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if ran_info and teid_dl is not None:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
gtphdr = pack('>BBHI', 0x30, 0xff, len(buf), teid_dl)
|
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ret = ran_info[2].sendto(gtphdr + buf, (ran_info[1], self.GTP_PORT))
|
2018-01-14 13:20:58 +00:00
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'sk_int IF error (sendto): %s' % err)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
else:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('WNG', 'teid_ul 0x%.8x, downlink GTP parameters not set' % teid_ul)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def transfer_v6_to_int(self, buf):
|
|
|
|
|
#self._log('DBG', 'transfer_v6_to_int()')
|
|
|
|
|
# buf length is guaranteed >= 40 and ipdst in self._mobiles_addr
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
if self.MOD:
|
|
|
|
|
# possibly process the DL GTP-U payload within modules
|
|
|
|
|
try:
|
|
|
|
|
for mod in self.MOD:
|
|
|
|
|
if mod.TYPE == 0:
|
|
|
|
|
buf = mod.handle_dl(buf)
|
|
|
|
|
else:
|
|
|
|
|
mod.handle_dl(buf)
|
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'MOD error: %s' % err)
|
|
|
|
|
#
|
2018-02-09 21:06:38 +00:00
|
|
|
|
teid_ul = self._mobiles_addr[buf[32:40]]
|
|
|
|
|
ran_info, teid_dl = self._mobiles_teid[teid_ul][:2]
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# prepend GTP header and forward to the RAN IP
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if ran_info and teid_dl is not None:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
gtphdr = pack('>BBHI', 0x30, 0xff, len(buf), teid_dl)
|
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ret = ran_info[2].sendto(gtphdr + buf, (ran_info[1], self.GTP_PORT))
|
2018-01-14 13:20:58 +00:00
|
|
|
|
except Exception as err:
|
|
|
|
|
self._log('ERR', 'sk_int IF error (sendto): %s' % err)
|
|
|
|
|
else:
|
|
|
|
|
self._log('WNG', 'teid_ul 0x%.8x, downlink GTP parameters not set' % teid_ul)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
#--------------------------------------------------------------------------#
|
|
|
|
|
# UE management
|
|
|
|
|
#--------------------------------------------------------------------------#
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def add_mobile(self, teid_ul, mobile_addr, ran_ip, teid_dl):
|
|
|
|
|
if teid_ul in self._mobiles_teid:
|
|
|
|
|
# just increment the ctx_num
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._mobiles_teid[teid_ul][-1] += 1
|
|
|
|
|
#
|
2018-01-14 13:20:58 +00:00
|
|
|
|
else:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if mobile_addr[0] == 1:
|
|
|
|
|
# IPv4
|
|
|
|
|
ipv4buf = inet_aton_cn(*mobile_addr)
|
|
|
|
|
if len(ipv4buf) != 4:
|
|
|
|
|
self._log('ERR', 'invalid mobile addr %r' % (mobile_addr, ))
|
|
|
|
|
return
|
|
|
|
|
ipv6buf = None
|
|
|
|
|
elif mobile_addr[0] == 2:
|
|
|
|
|
# IPv6 if suffix (8 bytes) or full IPv6 (then truncated to 8 bytes)
|
|
|
|
|
ipv6buf = inet_aton_cn(*mobile_addr)
|
|
|
|
|
if len(ipv6buf) == 16:
|
|
|
|
|
ipv6buf = ipv6buf[8:]
|
|
|
|
|
elif len(ipv6buf) != 8:
|
|
|
|
|
self._log('ERR', 'invalid mobile addr %r' % (mobile_addr, ))
|
|
|
|
|
return
|
|
|
|
|
ipv4buf = None
|
|
|
|
|
elif mobile_addr[0] == 3:
|
|
|
|
|
# IPv4v6
|
|
|
|
|
# IPv4
|
|
|
|
|
ipv4buf = inet_aton_cn(1, mobile_addr[1])
|
|
|
|
|
if len(ipv4buf) != 4:
|
|
|
|
|
self._log('ERR', 'invalid mobile addr %r' % (mobile_addr, ))
|
|
|
|
|
return
|
|
|
|
|
# IPv6 if suffix (8 bytes) or full IPv6
|
|
|
|
|
ipv6buf = inet_aton_cn(2, mobile_addr[2])
|
|
|
|
|
if len(ipv6buf) == 16:
|
|
|
|
|
ipv6buf = ipv6buf[8:]
|
|
|
|
|
elif len(ipv6buf) != 8:
|
|
|
|
|
self._log('ERR', 'invalid mobile addr %r' % (mobile_addr, ))
|
|
|
|
|
return
|
|
|
|
|
else:
|
|
|
|
|
self._log('ERR', 'invalid mobile addr %r' % (mobile_addr, ))
|
|
|
|
|
#
|
|
|
|
|
if ran_ip and ran_ip[1] is not None:
|
|
|
|
|
try:
|
|
|
|
|
# add the sk_int within ran_info
|
|
|
|
|
sk_int = self.sk_int[self._sk_int_ind[ran_ip[0]]]
|
|
|
|
|
ran_info = (ran_ip[0], ran_ip[1], sk_int)
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._log('ERR', 'invalid RAN IP, %r' % ran_ip)
|
|
|
|
|
ran_info = None
|
|
|
|
|
else:
|
|
|
|
|
ran_info = None
|
2018-01-14 13:20:58 +00:00
|
|
|
|
# insert a new context
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._mobiles_teid[teid_ul] = [ran_info, teid_dl, ipv4buf, ipv6buf, 1]
|
|
|
|
|
if ipv4buf:
|
|
|
|
|
self._mobiles_addr[ipv4buf] = teid_ul
|
|
|
|
|
if ipv6buf:
|
|
|
|
|
self._mobiles_addr[ipv6buf] = teid_ul
|
|
|
|
|
#
|
|
|
|
|
self._log('INF', 'setting GTP-U context for UE with IP %r, teid_ul 0x%.8x'\
|
|
|
|
|
% (mobile_addr, teid_ul))
|
2018-01-14 13:20:58 +00:00
|
|
|
|
|
|
|
|
|
def set_mobile_dl(self, teid_ul, ran_ip=None, teid_dl=None):
|
|
|
|
|
# enables to reconfigure the DL parameters (RAN IP, DL TEID)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ran_info_ori, teid_dl_ori, ipv4buf, ipv6buf, ctx_num = self._mobiles_teid[teid_ul]
|
2018-01-02 13:57:39 +00:00
|
|
|
|
except Exception as err:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
self._log('ERR', 'invalid teid_ul 0x%.8x' % teid_ul)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
return
|
2018-01-14 13:20:58 +00:00
|
|
|
|
else:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
if ran_ip:
|
|
|
|
|
try:
|
|
|
|
|
# add the sk_int within ran_info
|
|
|
|
|
sk_int = self.sk_int[self._sk_int_ind[ran_ip[0]]]
|
|
|
|
|
ran_info = (ran_ip[0], ran_ip[1], sk_int)
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._log('ERR', 'invalid RAN IP, %r' % ran_ip)
|
|
|
|
|
ran_info = None
|
|
|
|
|
else:
|
|
|
|
|
ran_info = None
|
2018-01-14 13:20:58 +00:00
|
|
|
|
if teid_dl is None:
|
|
|
|
|
teid_dl = teid_dl_ori
|
2018-02-09 21:06:38 +00:00
|
|
|
|
self._mobiles_teid[teid_ul] = [ran_info, teid_dl, ipv4buf, ipv6buf, ctx_num]
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def rem_mobile(self, teid_ul):
|
|
|
|
|
if teid_ul in self._mobiles_teid:
|
|
|
|
|
mobile_ctx = self._mobiles_teid[teid_ul]
|
|
|
|
|
if mobile_ctx[-1] > 1:
|
|
|
|
|
# decrement the number of GTP contexts
|
|
|
|
|
mobile_ctx[-1] -= 1
|
|
|
|
|
else:
|
|
|
|
|
# delete the mobile context
|
|
|
|
|
del self._mobiles_teid[teid_ul]
|
2018-02-09 21:06:38 +00:00
|
|
|
|
ran_info, teid_dl, ipv4buf, ipv6buf, ctx_num = mobile_ctx
|
|
|
|
|
if ipv4buf:
|
|
|
|
|
ipv4addr = inet_ntoa(ipv4buf)
|
2018-01-14 13:20:58 +00:00
|
|
|
|
try:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
del self._mobiles_addr[ipv4buf]
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-01-14 13:20:58 +00:00
|
|
|
|
pass
|
2018-02-09 21:06:38 +00:00
|
|
|
|
else:
|
|
|
|
|
ipv4addr = None
|
|
|
|
|
if ipv6buf:
|
|
|
|
|
ipv6addr = inet_ntop(AF_INET6, self.IPV6_NET_PREF + ipv6buf)
|
|
|
|
|
try:
|
|
|
|
|
del self._mobiles_addr[ipv6buf]
|
2020-04-30 11:18:22 +00:00
|
|
|
|
except Exception:
|
2018-02-09 21:06:38 +00:00
|
|
|
|
pass
|
|
|
|
|
else:
|
|
|
|
|
ipv6addr = None
|
|
|
|
|
if ipv4addr and ipv6addr:
|
|
|
|
|
ipaddr = 'IPv4 %s / IPv6 %s' % (ipv4addr, ipv6addr)
|
|
|
|
|
elif ipv6addr is None:
|
|
|
|
|
ipaddr = 'IPv4 ' + ipv4addr
|
|
|
|
|
else:
|
|
|
|
|
ipaddr = 'IPv6 ' + ipv6addr
|
|
|
|
|
self._log('DBG', 'deleting GTP-U context for UE with addr %s, teid_ul 0x%.8x'\
|
2018-01-14 13:20:58 +00:00
|
|
|
|
% (ipaddr, teid_ul))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _DPI(object):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
@staticmethod
|
|
|
|
|
def get_port(pay):
|
|
|
|
|
"""return the port TCP / UDP number
|
|
|
|
|
"""
|
|
|
|
|
return unpack('!H', pay[2:4])[0]
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def __get_dn_req_py2(req):
|
|
|
|
|
"""return the DNS name requested
|
|
|
|
|
"""
|
|
|
|
|
# remove fixed DNS header and Type / Class
|
|
|
|
|
s = req[12:-4]
|
|
|
|
|
n = []
|
|
|
|
|
while len(s) > 1:
|
|
|
|
|
l = ord(s[0])
|
|
|
|
|
n.append( s[1:1+l] )
|
|
|
|
|
s = s[1+l:]
|
|
|
|
|
return b'.'.join(n)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def __get_dn_req_py3(req):
|
|
|
|
|
"""return the DNS name requested
|
|
|
|
|
"""
|
|
|
|
|
# remove fixed DNS header and Type / Class
|
|
|
|
|
s = req[12:-4]
|
|
|
|
|
n = []
|
|
|
|
|
while len(s) > 1:
|
|
|
|
|
l = s[0]
|
|
|
|
|
n.append( s[1:1+l] )
|
|
|
|
|
s = s[1+l:]
|
|
|
|
|
return b'.'.join(n)
|
|
|
|
|
|
|
|
|
|
if python_version < 3:
|
|
|
|
|
get_dn_req = __get_dn_req_py2
|
|
|
|
|
else:
|
|
|
|
|
get_dn_req = __get_dn_req_py3
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
|
2018-01-14 13:20:58 +00:00
|
|
|
|
class DPIv4(_DPI):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def __get_ip_info_py2(ipbuf):
|
|
|
|
|
"""return a 3-tuple: ipdst (asc), protocol (uint), payload (bytes)
|
|
|
|
|
"""
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# returns a 3-tuple: dst IP, protocol, payload buffer
|
|
|
|
|
# get IP header length
|
2018-01-14 13:20:58 +00:00
|
|
|
|
l = (ord(ipbuf[0]) & 0x0F) * 4
|
2018-01-02 13:57:39 +00:00
|
|
|
|
# get dst IP
|
|
|
|
|
dst = inet_ntoa(ipbuf[16:20])
|
|
|
|
|
# get protocol
|
2018-01-14 13:20:58 +00:00
|
|
|
|
prot = ord(ipbuf[9])
|
2018-01-02 13:57:39 +00:00
|
|
|
|
#
|
|
|
|
|
return (dst, prot, ipbuf[l:])
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def __get_ip_info_py3(ipbuf):
|
|
|
|
|
"""return a 3-tuple: ipdst (asc), protocol (uint), payload (bytes)
|
|
|
|
|
"""
|
|
|
|
|
# returns a 3-tuple: dst IP, protocol, payload buffer
|
|
|
|
|
# get IP header length
|
|
|
|
|
l = (ipbuf[0] & 0x0F) * 4
|
|
|
|
|
# get dst IP
|
|
|
|
|
dst = inet_ntoa(ipbuf[16:20])
|
|
|
|
|
# get protocol
|
|
|
|
|
prot = ipbuf[9]
|
|
|
|
|
#
|
|
|
|
|
return (dst, prot, ipbuf[l:])
|
|
|
|
|
|
|
|
|
|
if python_version < 3:
|
|
|
|
|
get_ip_info = __get_ip_info_py2
|
|
|
|
|
else:
|
|
|
|
|
get_ip_info = __get_ip_info_py3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DPIv6(_DPI):
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
@staticmethod
|
2018-01-14 13:20:58 +00:00
|
|
|
|
def __get_ip_info_py2(ipbuf):
|
|
|
|
|
"""return a 3-tuple: ipdst (asc), protocol (uint), payload (bytes)
|
|
|
|
|
"""
|
|
|
|
|
# returns a 3-tuple: dst IP, protocol, payload buffer
|
|
|
|
|
# get payload length
|
|
|
|
|
pl = unpack('>H', ipbuf[4:6])[0]
|
|
|
|
|
# get dst IP
|
|
|
|
|
dst = inet_ntop(AF_INET6, ipbuf[24:40])
|
|
|
|
|
# get protocol
|
|
|
|
|
# TODO: unstack IPv6 opts
|
|
|
|
|
prot = ord(ipbuf[6])
|
|
|
|
|
#
|
|
|
|
|
return (dst, prot, ipbuf[-pl:])
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def __get_ip_info_py3(ipbuf):
|
|
|
|
|
"""return a 3-tuple: ipdst (asc), protocol (uint), payload (bytes)
|
|
|
|
|
"""
|
|
|
|
|
# returns a 3-tuple: dst IP, protocol, payload buffer
|
|
|
|
|
# get payload length
|
|
|
|
|
pl = unpack('>H', ipbuf[4:6])[0]
|
|
|
|
|
# get dst IP
|
|
|
|
|
dst = inet_ntop(AF_INET6, ipbuf[24:40])
|
|
|
|
|
# get protocol
|
|
|
|
|
# TODO: unstack IPv6 opts
|
|
|
|
|
prot = ipbuf[6]
|
|
|
|
|
#
|
|
|
|
|
return (dst, prot, ipbuf[-pl:])
|
|
|
|
|
|
|
|
|
|
if python_version < 3:
|
|
|
|
|
get_ip_info = __get_ip_info_py2
|
|
|
|
|
else:
|
|
|
|
|
get_ip_info = __get_ip_info_py3
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MOD(object):
|
|
|
|
|
# This is a skeleton for GTP-U payloads specific handler.
|
|
|
|
|
# After It gets loaded by the GTPUd instance,
|
|
|
|
|
# it acts on each GTP-U payloads (UL and DL)
|
|
|
|
|
|
|
|
|
|
# In can work actively on GTP-U packets (possibly changing them)
|
|
|
|
|
# with TYPE = 0
|
|
|
|
|
# or passively (not able to change them), only processing a copy of them,
|
|
|
|
|
# with TYPE = 1
|
|
|
|
|
TYPE = 0
|
|
|
|
|
|
|
|
|
|
# reference to the GTPUd instance
|
|
|
|
|
GTPUd = None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def _log(self, logtype, msg):
|
|
|
|
|
self.GTPUd._log(logtype, '[MOD.%s] %s' % (self.__class__.__name__, msg))
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def handle_ul(self, ippuf):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def handle_dl(self, ipbuf):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DNSRESP(MOD):
|
|
|
|
|
'''
|
|
|
|
|
This module answers to any DNS request incoming from UE (UL direction)
|
2018-07-15 14:16:49 +00:00
|
|
|
|
with a single or random IP address, over IPv4
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
To be used with GTPUd.BLACKHOLING capability to avoid UE getting real
|
|
|
|
|
DNS responses from servers in parallel
|
|
|
|
|
'''
|
|
|
|
|
TYPE = 1
|
|
|
|
|
|
|
|
|
|
# compute UDP checksum in DNS response
|
|
|
|
|
UDP_CS = True
|
|
|
|
|
# in case we want to answer random addresses
|
|
|
|
|
RAND = False
|
|
|
|
|
# the IPv4 address to answer all requests
|
|
|
|
|
IP_RESP = '192.168.1.50'
|
|
|
|
|
|
2018-07-15 14:16:49 +00:00
|
|
|
|
DEBUG = False
|
|
|
|
|
|
2018-01-02 13:57:39 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def handle_ul(self, ipbuf):
|
|
|
|
|
# check if we have an UDP/53 request
|
2018-07-15 14:16:49 +00:00
|
|
|
|
ip_vers, ip_proto, (udpsrc, udpdst) = \
|
|
|
|
|
ord(ipbuf[0:1])>>4, ord(ipbuf[9:10]), unpack('!HH', ipbuf[20:24])
|
|
|
|
|
if ip_vers != 4 or ip_proto != 53 or udp_dst != 53:
|
|
|
|
|
# not IPv4, not UDP or not on DNS port 53
|
2018-01-02 13:57:39 +00:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# build the UDP / DNS response: invert src / dst UDP ports
|
|
|
|
|
if self.UDP_CS:
|
|
|
|
|
udp = UDP(val={'src':udpdst, 'dst':udpsrc}, hier=1)
|
|
|
|
|
else:
|
|
|
|
|
udp = UDP(val={'src':udpdst, 'dst':udpsrc, 'cs':0}, hier=1)
|
|
|
|
|
# DNS request: transaction id, flags, questions, queries
|
|
|
|
|
dnsreq = ipbuf[28:]
|
|
|
|
|
transac_id, questions, queries = dnsreq[0:2], \
|
|
|
|
|
unpack('!H', dnsreq[4:6])[0], \
|
|
|
|
|
dnsreq[12:]
|
|
|
|
|
if questions > 1:
|
|
|
|
|
# not supported
|
|
|
|
|
self._log('WNG', '%i questions, unsupported' % questions)
|
|
|
|
|
# DNS response: transaction id, flags, questions, answer RRs,
|
|
|
|
|
# author RRs, add RRs, queries, answers, autor nameservers, add records
|
|
|
|
|
if self.RAND:
|
|
|
|
|
ip_resp = _urandom(4)
|
|
|
|
|
else:
|
|
|
|
|
ip_resp = inet_aton(self.IP_RESP)
|
|
|
|
|
dnsresp = b''.join((transac_id, b'\x81\x80\0\x01\0\x01\0\0\0\0', queries,
|
|
|
|
|
b'\xc0\x0c\0\x01\0\x01\0\0\0\x20\0\x04', ip_resp))
|
|
|
|
|
|
|
|
|
|
# build the IPv4 header: invert src / dst addr
|
|
|
|
|
ipsrc, ipdst = inet_ntoa(ipbuf[12:16]), inet_ntoa(ipbuf[16:20])
|
|
|
|
|
iphdr = IPv4(val={'src':ipdst, 'dst':ipsrc}, hier=0)
|
|
|
|
|
#
|
|
|
|
|
pkt = Envelope('p', GEN=(iphdr, udp, Buf('dns', val=dnsresp, hier=2)))
|
|
|
|
|
# send back the DNS response
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self.GTPUd.transfer_v4_to_int(pkt.to_bytes())
|
|
|
|
|
if self.DEBUG:
|
|
|
|
|
self.GTPUd._log('DBG', '[DNSRESP] DNS response sent')
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TCPSYNACK(MOD):
|
|
|
|
|
'''
|
2018-07-15 14:16:49 +00:00
|
|
|
|
This module answers to TCP SYN request incoming from UE (UL direction)
|
|
|
|
|
over IPv4 with a TCP SYN-ACK, enabling to get the 1st TCP data packet
|
|
|
|
|
from the UE
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
To be used with GTPUd.BLACKHOLING capability to avoid UE getting SYN-ACK
|
|
|
|
|
from real servers in parallel
|
|
|
|
|
'''
|
|
|
|
|
TYPE = 1
|
|
|
|
|
|
2018-07-15 14:16:49 +00:00
|
|
|
|
DEBUG = False
|
|
|
|
|
|
2018-01-02 13:57:39 +00:00
|
|
|
|
@classmethod
|
|
|
|
|
def handle_ul(self, ipbuf):
|
|
|
|
|
# check if we have a TCP SYN
|
2018-07-15 14:16:49 +00:00
|
|
|
|
ip_vers, ip_proto, ip_pay = ord(ipbuf[0:1])>>4, ord(ipbuf[9:10]), ipbuf[20:]
|
|
|
|
|
if ip_vers != 4 or ip_proto != 6 or ip_pay[13:14] != b'\x02':
|
|
|
|
|
# not IPv4, not TCP, not SYN
|
2018-01-02 13:57:39 +00:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# build the TCP SYN-ACK: invert src / dst ports, seq num (random),
|
|
|
|
|
# ack num (SYN seq num + 1)
|
|
|
|
|
tcpsrc, tcpdst, seq = unpack('!HHI', ip_pay[:8])
|
2018-07-15 14:16:49 +00:00
|
|
|
|
tcp_synack = TCP(val={'seq': randint(1, 4294967295),
|
|
|
|
|
'ack': (1+seq)%4294967296,
|
|
|
|
|
'src': tcpdst, 'dst': tcpsrc,
|
|
|
|
|
'SYN': 1, 'ACK': 1, 'win': 0x1000}, hier=1)
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|
|
|
|
|
# build the IPv4 header: invert src / dst addr
|
|
|
|
|
ipsrc, ipdst = inet_ntoa(ipbuf[12:16]), inet_ntoa(ipbuf[16:20])
|
|
|
|
|
iphdr = IPv4(val={'src':ipdst, 'dst':ipsrc}, hier=0)
|
|
|
|
|
#
|
|
|
|
|
pkt = Envelope('p', GEN=(iphdr, tcp_synack))
|
|
|
|
|
# send back the TCP SYN-ACK
|
2018-07-15 14:16:49 +00:00
|
|
|
|
self.GTPUd.transfer_v4_to_int(pkt.to_bytes())
|
|
|
|
|
if self.DEBUG:
|
|
|
|
|
self.GTPUd._log('DBG', '[TCPSYNACK] TCP SYN ACK response sent')
|
2018-01-02 13:57:39 +00:00
|
|
|
|
|