Семинар 4: Сетевой уровень (L3). Scapy.

Настройка сетевого уровня

Получить информацию о настройках интерфейсов на сетевом уровне можно при помощи команды ip: ip address show (можно сократить до ip a) вывод будет примерно следующий:

$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host noprefixroute
       valid_lft forever preferred_lft forever
2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 30:d0:42:e8:3b:80 brd ff:ff:ff:ff:ff:ff
    altname enx30d042e83b80
    inet 10.12.8.49/22 brd 10.12.11.255 scope global dynamic noprefixroute enp0s31f6
       valid_lft 4015sec preferred_lft 3115sec
    inet6 fd5d:ef9c:6c83:1a66::100/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::db2a:7919:7ea8:4c42/64 scope link
       valid_lft forever preferred_lft forever
        

Самое важное, что тут можно увидеть - это ipv4/ipv6 адрес (после слова inet/inet6) и маску, широковещательный адрес, если маска не кратна границам байтов (после ключевого слова brd), состояние интерфейса (после ключевого слова state), mac адреса (link/ether). Всё это выделил жирным.

Для добавления ip адреса используется команда ip address add <address/mask> dev <interface> (ip a a <address/mask> dev <interface>), например:

ip addr add 10.1.0.1/24 dev eth0

Для удаления синтаксис аналогичен: ip addr del. Подробнее можно узнать в справке, например, по ip help.

Важно обратить внимание, что настройки командой ip не сохраняются после перезагрузки. Для хранения постоянных настроек используются другие программы, которые применяют настройки при старте системы, такие как NetworkManager, NetPlan и другие. В alpine используется файл /etc/network/interfaces. GNS3 позволяет редактировать этот файл в любое время, даже если устройство не запущено, через меню по клику правой кнопкой мыши "Edit config". Данный файл применяется при старте (рестарте) устройства. Синтаксис данного файла можно найти в мануале (man 5 interfaces или найти в интернете).

Возможно вам потребуется включить forwarding: sysctl -w net.ipv4.ip_forward=1

Настройка таблицы маршрутизации

Используется команда ip route. Добавить маршруты можно подкомандой add, удалить - del. Маршрут по-умолчанию (dafault gateway, next hop) - default. Также таблица маршрутизации изменяется при назначении ip адресов (с маской отличной от максимальной). Примеры работы:

# ip r
# ip addr
5: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN qlen 1000
    link/ether 02:42:9f:24:11:00 brd ff:ff:ff:ff:ff:ff
    inet6 fe80::42:9fff:fe24:1100/64 scope link
       valid_lft forever preferred_lft forever
# ip addr add 10.1.0.1 dev eth0
# ip addr
5: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UNKNOWN qlen 1000
    link/ether 02:42:9f:24:11:00 brd ff:ff:ff:ff:ff:ff
    inet 10.1.0.1/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:9fff:fe24:1100/64 scope link
       valid_lft forever preferred_lft forever
# ip route
# ip address del 10.1.0.1 dev eth0
# ip a a 10.1.0.1/24 dev eth0
# ip route
10.1.0.0/24 dev eth0 scope link  src 10.1.0.1
# ip route add 10.2.3.0/24 via 10.1.0.2
# ip r
10.1.0.0/24 dev eth0 scope link  src 10.1.0.1
10.2.3.0/24 via 10.1.0.2 dev eth0
# ip r a default via 10.1.0.101
# ip r
default via 10.1.0.101 dev eth0
10.1.0.0/24 dev eth0 scope link  src 10.1.0.1
10.2.3.0/24 via 10.1.0.2 dev eth0

Работа с ARP таблицей

Для работы с arp таблицей можно использовать команду ip neigh или команду arp. Примеры:

# ip neig
10.1.0.3 dev eth0 lladdr 02:42:1f:05:27:00 ref 1 used 0/0/0 probes 4 REACHABLE
# arp -a
? (10.1.0.3) at 02:42:1f:05:27:00 [ether]  on eth0
        

Scapy

Scapy - библиотека для python, которая позволяет манипулировать сетевыми пакетами. Производительностью не отличается, но очень удобна, если нужно генерировать небольшое число пакетов. Документация.

Интерактивная оболочка запускается командой scapy (для некоторого функционала потребуется права суперпользователя, т.е. запуск под sudo, но на представленных alpine все команды и так запускаются от root).

Удобно представлять пакеты (или их части) как переменные, заполнять только необходимые поля, остальные заполнятся по-умолчанию (или будут получены от системы). Например:

p = IP(dst="1.1.1.1")/ICMP()/b"XXXX"

Тут создаётся пакет IPv4, где в качестве адреса назначения указан 1.1.1.1 (остальные поля заполняет Scapy); / - означает инкапсуляцию или разделение уровней. ICMP пакет будет инкапсулирован в IP, а в качестве полезной нагрузки ICMP будет строка XXXX

b"" заставляет python однозначно интерпретировать строку как набор байт, без этого поведение при использовании различных локалей может отличаться, в частности некоторые символы будут занимать более одного байта

Чтобы отобразить, что получается в переменной можно просто ввести название этой переменной и нажать enter. Также полезными будут функции ls и raw. Примеры:

>>> p
<IP  frag=0 proto=icmp dst=1.1.1.1 |<ICMP  |<Raw  load='XXXX' |>>>
>>> ls(p)
version    : BitField  (4 bits)                  = 4               ('4')
ihl        : BitField  (4 bits)                  = None            ('None')
tos        : XByteField                          = 0               ('0')
len        : ShortField                          = None            ('None')
id         : ShortField                          = 1               ('1')
flags      : FlagsField                          = <Flag 0 ()>     ('<Flag 0 ()>')
frag       : BitField  (13 bits)                 = 0               ('0')
ttl        : ByteField                           = 64              ('64')
proto      : ByteEnumField                       = 1               ('0')
chksum     : XShortField                         = None            ('None')
src        : SourceIPField                       = '192.168.1.193' ('None')
dst        : DestIPField                         = '1.1.1.1'       ('None')
options    : PacketListField                     = []              ('[]')
--
type       : ByteEnumField                       = 8               ('8')
code       : MultiEnumField (Depends on 8)       = 0               ('0')
chksum     : XShortField                         = None            ('None')
id         : XShortField (Cond)                  = 0               ('0')
seq        : XShortField (Cond)                  = 0               ('0')
ts_ori     : ICMPTimeStampField (Cond)           = None            ('80513127')
ts_rx      : ICMPTimeStampField (Cond)           = None            ('80513127')
ts_tx      : ICMPTimeStampField (Cond)           = None            ('80513127')
gw         : IPField (Cond)                      = None            ("'0.0.0.0'")
ptr        : ByteField (Cond)                    = None            ('0')
reserved   : ByteField (Cond)                    = None            ('0')
length     : ByteField (Cond)                    = None            ('0')
addr_mask  : IPField (Cond)                      = None            ("'0.0.0.0'")
nexthopmtu : ShortField (Cond)                   = None            ('0')
unused     : MultipleTypeField (ShortField, IntField, StrFixedLenField) = b''             ("b''")
--
load       : StrField                            = b'XXXX'         ("b''")
>>> raw(p)
b'E\x00\x00 \x00\x01\x00\x00@\x01\xb6q\xc0\xa8\x01\xc1\x01\x01\x01\x01\x08\x00GO\x00\x00\x00\x00XXXX'

Для отправки пакетов есть несколько функций.

Для отправки L3 пакета есть функции send и sr1. Первая только отправляет пакет, вторая же ещё ожидает ответного пакета и возвращает его. Для L2 есть аналогичные функции sendp и srp1. Примеры:

>>> send(IP(dst="1.2.3.4")/ICMP())
>>> answer = sr1(IP(dst="1.1.1.1")/ICMP()/"XXXXXXXXXXX")
>>> sendp(Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst="1.1.1.1")/ICMP()/"XXXXXXXXXXX")
>>> asn = srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst="1.1.1.1")/ICMP()/"XXXXXXXXXXX", iface="eth1")

Полный пример с конструированием, отправкой и получением ответа:

Welcome to Scapy (2.5.0)
>>> p = IP(dst="1.1.1.1")/ICMP()/b"XXXX"
>>> p
<IP  frag=0 proto=icmp dst=1.1.1.1 |<ICMP  |<Raw  load='XXXX' |>>>
>>> raw(p)
b'E\x00\x00 \x00\x01\x00\x00@\x01\xb6q\xc0\xa8\x01\xc1\x01\x01\x01\x01\x08\x00GO\x00\x00\x00\x00XXXX'
>>> ls(p)
version    : BitField  (4 bits)                  = 4               ('4')
ihl        : BitField  (4 bits)                  = None            ('None')
tos        : XByteField                          = 0               ('0')
len        : ShortField                          = None            ('None')
id         : ShortField                          = 1               ('1')
flags      : FlagsField                          = <Flag 0 ()>     ('<Flag 0 ()>')
frag       : BitField  (13 bits)                 = 0               ('0')
ttl        : ByteField                           = 64              ('64')
proto      : ByteEnumField                       = 1               ('0')
chksum     : XShortField                         = None            ('None')
src        : SourceIPField                       = '192.168.1.193' ('None')
dst        : DestIPField                         = '1.1.1.1'       ('None')
options    : PacketListField                     = []              ('[]')
--
type       : ByteEnumField                       = 8               ('8')
code       : MultiEnumField (Depends on 8)       = 0               ('0')
chksum     : XShortField                         = None            ('None')
id         : XShortField (Cond)                  = 0               ('0')
seq        : XShortField (Cond)                  = 0               ('0')
ts_ori     : ICMPTimeStampField (Cond)           = None            ('80513127')
ts_rx      : ICMPTimeStampField (Cond)           = None            ('80513127')
ts_tx      : ICMPTimeStampField (Cond)           = None            ('80513127')
gw         : IPField (Cond)                      = None            ("'0.0.0.0'")
ptr        : ByteField (Cond)                    = None            ('0')
reserved   : ByteField (Cond)                    = None            ('0')
length     : ByteField (Cond)                    = None            ('0')
addr_mask  : IPField (Cond)                      = None            ("'0.0.0.0'")
nexthopmtu : ShortField (Cond)                   = None            ('0')
unused     : MultipleTypeField (ShortField, IntField, StrFixedLenField) = b''             ("b''")
--
load       : StrField                            = b'XXXX'         ("b''")
>>> r = sr1(p)
Begin emission:
Finished sending 1 packets.

Received 7 packets, got 1 answers, remaining 0 packets
>>> r
<IP  version=4 ihl=5 tos=0x0 len=28 id=32501 flags= frag=0 ttl=59 proto=icmp chksum=0x3c81 src=1.1.1.1 dst=192.168.1.193 |<ICMP  type=echo-reply code=0 chksum=0x0 id=0x0 seq=0x0 |<Raw  load='XXXX' |>>>
>>> r[IP].src
'1.1.1.1'

Пример ARP запрса:

>>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="10.0.1.1"), timeout=2, iface="eth0")
Begin emission:
Finished sending 1 packets.
*
Received 1 packets, got 1 answers, remaining 0 packets
>>> ans.summary()
Ether / ARP who has 10.0.1.1 says 10.0.1.2 ==> Ether / ARP is at 02:42:fe:a5:19:03 says 10.0.1.1

Здесь на широковещательный запрос "ARP who has 10.0.1.1" получаем ответ "ARP is at 02:42:fe:a5:19:03"

Пример отправки ARP ответа:

>>> p = ARP(op=2, pdst="1.1.1.1", hwdst="11:11:11:11:11:11", psrc="2.2.2.2")
>>> p.show()
###[ ARP ]###
  hwtype    = Ethernet (10Mb)
  ptype     = IPv4
  hwlen     = None
  plen      = None
  op        = is-at
  hwsrc     = 22:22:22:22:22:22
  psrc      = 2.2.2.2
  hwdst     = 11:11:11:11:11:11
  pdst      = 1.1.1.1
>>> send(p, iface="eth0")
.
Sent 1 packets.
        

Говорим хосту с mac 11:11:11:11:11:11 и ip 1.1.1.1, что я (хост с ip 2.2.2.2) имею mac 22:22:22:22:22:22.

Задание

1. Настроить L3 сеть

Собрать схему как на рисунке ниже. Все узлы могут быть контейнерами alpine. (Если хотите дополнительного вызова, то PC1 и PC3 сделайте из VPCS, но работоспособность никто не гарантирует)

Схема первой части задания

Настроить все 3 сети в соответствии с указанными диапазонами ip адресов. На R2 настроить bridge, как на предыдущем семинаре. Убедиться при помощи команды ping, что все узлы достижимы, в частности PC3-PC1 и PC1-PC2.

2. Scapy на примере ARP-spoofing

Запусть ping с хоста PC1 на хост PC3.

Запустить wireshark между R2 и PC2.

На хосте PC2 перехватить ping используя возможность отправки ARP ответов scapy. В wireshark должны появится ICMP Echo (ping) request (как минимум).

Запускать только на изолированном стенде!!!