Я сделал программу пересылки, которая пересылает пакеты через IPIP. Как сервер пересылки, так и сервер назначения являются Linux виртуальными машинами под управлением Ubuntu 18.04 (управление виртуальными машинами осуществляется на хосте Proxmox). На машине, на которой пакеты пересылаются (10.50.0.4
), IPIP-туннель конечной точки (10.2.0.5
) и приложение находятся вместе в сетевом пространстве имен. Приложение привязывается к IP-адресу туннеля IPIP, а шлюзом по умолчанию является туннель IPIP. То, что я пытаюсь сделать sh, - приложение отправляет пакеты напрямую обратно клиентам, а IP-адрес источника подделывается как IP-адрес сервера пересылки, а не пакеты, возвращающиеся через туннель IPIP. Я хочу сделать это, чтобы на сервере пересылки было меньше нагрузки, а время ожидания в целом меньше (например, пакетам из приложения не нужно будет go возвращаться через сервер пересылки).
Сначала я попытался создание пары veth и размещение однорангового узла в пространстве имен сети. Затем я создал мост в пространстве имен по умолчанию и назначил ему IP (10.2.0.1/16
). С этого момента я подключил veth к пространству имен по умолчанию и создал правило SNAT в цепочке POSTROUTING IPTable под таблицей NAT для 10.2.0.0/16
, источником которого является IP-адрес сервера пересылки (10.50.0.3
). Я установил маршрут к пространству имен для ветерана и следующий переход (IP-адрес моста, 10.2.0.1
). В то время как приложение могло отправлять исходящие пакеты через пару veth, которая была получена в качестве IP-адреса сервера пересылки, приложение все еще не работало должным образом. Я думаю, это потому, что приложение не поддерживает привязку к нескольким интерфейсам (туннель IPIP для получения и интерфейс маршрута по умолчанию, ветерар для отправки). К сожалению, это приложение с закрытым исходным кодом.
С учетом вышесказанного я решил попробовать создать программу C с использованием сокетов AF_PACKET. Маршрут по умолчанию в пространстве имен установлен для туннеля IPIP. Однако у меня все еще есть пара веток, связанная с пространством имен. Принимающий сокет захватывает все пакеты в туннеле IPIP (включая исходящие пакеты), а отправляющий сокет связывается с ветеринарным узлом в пространстве имен. Когда принимающий сокет перехватывает пакет, он проверяет исходный IP-адрес, и если это IP-адрес туннеля IPIP, это означает, что туннель IPIP отправляет этот пакет обратно. Поэтому я изменяю исходный IP-адрес на сервер пересылки и пытаюсь отправить его ветерану. Я также блокирую исходные пакеты, возвращающиеся на сервер пересылки через IPTables (iptables -A OUTPUT -d <forwarding server IP> -j DROP
). Вот код программы:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <linux/if.h>
#include <linux/if_packet.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <net/ethernet.h>
#include <string.h>
#include <error.h>
#include <errno.h>
#include <inttypes.h>
#include <pthread.h>
#include <sys/sysinfo.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <ctype.h>
#define REDIRECT_HEADER
#include "csum.h"
#define MAX_PCKT_LENGTH 65535
#define PACKET_MASK_ANY 0xffffffff
#define PACKET_OUTGOING 4
#define PACKET_RECV_TYPE 18
static int cont = 1;
static unsigned char sMAC[ETH_ALEN];
static unsigned char dMAC[ETH_ALEN];
void signHdl(int tmp)
{
cont = 0;
}
void GetGatewayMAC()
{
char cmd[] = "ip neigh | grep \"$(ip -4 route list 0/0 | cut -d' ' -f3) \" | cut -d' ' -f5 | tr '[a-f]' '[A-F]'";
FILE *fp = popen(cmd, "r");
if (fp != NULL)
{
char line[18];
if (fgets(line, sizeof(line), fp) != NULL)
{
sscanf(line, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", &dMAC[0], &dMAC[1], &dMAC[2], &dMAC[3], &dMAC[4], &dMAC[5]);
}
pclose(fp);
}
}
void shiftChar(char *arr, int size, int dataLen)
{
for (int16_t i = (dataLen - 1); i >= 0; i--)
{
memmove(arr + i + size, arr + i, 1);
}
for (int16_t i = 0; i < size; i++)
{
memcpy(arr + i, "0", 1);
}
}
void removeChar(char *arr, int size, int dataLen)
{
for (int16_t i = 0; i < dataLen; i++)
{
memmove(arr + i, arr + size + i, 1);
}
for (int16_t i = 0; i < size; i++)
{
memcpy(arr + size + dataLen - i, "0", 1);
}
}
int main(int argc, char *argv[])
{
if (argc < 3)
{
perror("main");
exit(1);
}
int sockfd, sendsockfd;
uint8_t type; // 1 = normal interface (includes Ethernet headers). 2 = IPIP tunnel (doesn't include Ethernet headers).
struct sockaddr_ll a, b, din;
socklen_t dinLen = sizeof(din);
if (argc > 3)
{
type = atoi(argv[3]);
}
a.sll_family = PF_PACKET;
a.sll_ifindex = if_nametoindex(argv[1]);
a.sll_protocol = htons(ETH_P_ALL);
a.sll_halen = ETH_ALEN;
b.sll_family = PF_PACKET;
b.sll_ifindex = if_nametoindex("veth2");
b.sll_protocol = htons(ETH_P_IP);
b.sll_halen = ETH_ALEN;
sockfd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
sendsockfd = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW);
if (sockfd < 0 || sendsockfd < 0)
{
perror("socket");
exit(1);
}
int v=0;
v = PACKET_MASK_ANY & ~(1<<PACKET_OUTGOING) & ~(1 << PACKET_LOOPBACK);
setsockopt(sockfd, SOL_PACKET, PACKET_RECV_TYPE, &v, sizeof(v));
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, "veth2");
if (ioctl(sendsockfd, SIOCGIFHWADDR, &ifr) != 0)
{
perror("ioctl");
exit(1);
}
memcpy(a.sll_addr, ifr.ifr_addr.sa_data, ETH_ALEN);
memcpy(sMAC, a.sll_addr, ETH_ALEN);
GetGatewayMAC();
if (bind(sockfd, (struct sockaddr *)&a, sizeof(a)) < 0)
{
perror("bind");
exit(1);
}
if (bind(sendsockfd, (struct sockaddr *)&b, sizeof(b)) < 0)
{
perror("bind");
exit(1);
}
signal(SIGINT, signHdl);
printf("Source MAC => ");
for(uint8_t i = 0; i < ETH_ALEN; i++)
{
printf("%02x", sMAC[i]);
if (i != 5)
{
printf(":");
}
}
printf("\n");
printf("Destination MAC => ");
for(uint8_t i = 0; i < ETH_ALEN; i++)
{
printf("%02x", dMAC[i]);
if (i != 5)
{
printf(":");
}
}
printf("\n\n");
while (cont)
{
unsigned char buffer[MAX_PCKT_LENGTH];
uint16_t recv;
if ((recv = recvfrom(sockfd, &buffer, MAX_PCKT_LENGTH, 0, (struct sockaddr *)&din, &dinLen)) < 1)
{
perror("recvfrom");
continue;
}
struct ethhdr *ethhdr;
struct iphdr *iphdr;
struct udphdr *udphdr;
if (type == 1)
{
ethhdr = (struct ethhdr *) (buffer);
iphdr = (struct iphdr *) (buffer + sizeof(struct ethhdr));
udphdr = (struct udphdr *) (buffer + sizeof(struct ethhdr) + (iphdr->ihl * 4));
}
else
{
iphdr = (struct iphdr *) (buffer);
udphdr = (struct udphdr *) (buffer + (iphdr->ihl * 4));
}
if (type != 1)
{
shiftChar(buffer, sizeof(struct ethhdr), ntohs(iphdr->tot_len));
ethhdr = (struct ethhdr *) (buffer);
iphdr = (struct iphdr *) (buffer + sizeof(struct ethhdr));
udphdr = (struct udphdr *) (buffer + sizeof(struct ethhdr) + (iphdr->ihl * 4));
//memcpy(ethhdr->h_source, sMAC, ETH_ALEN);
//memcpy(ethhdr->h_dest, dMAC, ETH_ALEN);
ethhdr->h_source[0] = 0x82;
ethhdr->h_source[1] = 0xB3;
ethhdr->h_source[2] = 0x6F;
ethhdr->h_source[3] = 0x24;
ethhdr->h_source[4] = 0x0E;
ethhdr->h_source[5] = 0x74;
ethhdr->h_dest[0] = 0x96;
ethhdr->h_dest[1] = 0xF0;
ethhdr->h_dest[2] = 0xB6;
ethhdr->h_dest[3] = 0xDC;
ethhdr->h_dest[4] = 0xE5;
ethhdr->h_dest[5] = 0x1A;
ethhdr->h_proto = htons(ETH_P_IP);
if (iphdr->saddr == inet_addr(argv[2]) && iphdr->protocol == IPPROTO_UDP)
{
printf("Sending out %d bytes from %s => %s. %d is version. %d is port. XDDD\n", recv, inIP, outIP, iphdr->version, ntohs(udphdr->dest));
// Change source IP
uint32_t oldAddr = iphdr->saddr;
iphdr->saddr = inet_addr("10.50.0.3");
struct in_addr in;
in.s_addr = iphdr->saddr;
char inIP[16];
strcpy(inIP, inet_ntoa(in));
struct in_addr out;
out.s_addr = iphdr->daddr;
char outIP[16];
strcpy(outIP, inet_ntoa(out));
// Recalculate checksumz.
iphdr->check = csum_diff4(oldAddr, iphdr->saddr, iphdr->check);
udphdr->check = 0;
udphdr->check = csum_tcpudp_magic(iphdr->saddr, iphdr->daddr, ntohs(udphdr->len), IPPROTO_UDP, csum_partial(udphdr, ntohs(udphdr->len), 0));
//udphdr->check = csum_diff4(oldAddr, iphdr->saddr, udphdr->check);
uint16_t sent;
if ((sent = write(sendsockfd, buffer, ntohs(iphdr->tot_len) + sizeof(struct ethhdr))) < 1)
{
perror("write");
continue;
}
printf("Sent %d (%lu) back %s => %s.\n\n", sent, ntohs(iphdr->tot_len) + sizeof(struct ethhdr), inIP, outIP);
}
}
}
close(sockfd);
exit(0);
}
Имейте в виду, что в вышеуказанной программе много бесполезного кода, так как я пытался протестировать несколько вещей. При этом при захвате пакетов в туннеле IPIP с использованием сокетов AF_PACKET он не включает заголовок Ethe rnet. Я полагаю, что это сделано специально, поскольку захват на любом другом интерфейсе системы включает заголовки Ethe rnet. Вот как я выполняю программу в пространстве имен:
root@test03:/home/roy# ip netns exec server01 ./af_packet_ipip ipip01 10.2.0.5 2
Вот информация об интерфейсе на хост-машине (пространство имен по умолчанию) вместе с пространством имен, внутри которого работает туннель IPIP и приложение:
root@test03:/home/roy# ip addr
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
valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether ae:21:14:4b:3a:6d brd ff:ff:ff:ff:ff:ff
inet 10.50.0.4/24 brd 10.50.0.255 scope global dynamic ens18
valid_lft 68316sec preferred_lft 68316sec
inet6 fe80::ac21:14ff:fe4b:3a6d/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:49:df:c2:99 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
4: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
7: veth1@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master idk state UP group default qlen 1000
link/ether 96:f0:b6:dc:e5:1a brd ff:ff:ff:ff:ff:ff link-netnsid 0
11: idk: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 96:f0:b6:dc:e5:1a brd ff:ff:ff:ff:ff:ff
inet 10.2.0.1/16 scope global idk
valid_lft forever preferred_lft forever
root@test03:/home/roy# ip netns exec server01 ip addr
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
valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
5: ipip01@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ipip 0.0.0.0 peer 10.50.0.3 link-netnsid 0
inet 10.2.0.5/16 scope global ipip01
valid_lft forever preferred_lft forever
6: veth2@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 82:b3:6f:24:0e:74 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.2.0.4/16 scope global veth2
valid_lft forever preferred_lft forever
root@test03:/home/roy# ip route
default via 10.50.0.1 dev ens18 proto dhcp src 10.50.0.4 metric 100
10.2.0.0/16 dev idk proto kernel scope link src 10.2.0.1
10.50.0.0/24 dev ens18 proto kernel scope link src 10.50.0.4
10.50.0.1 dev ens18 proto dhcp scope link src 10.50.0.4 metric 100
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1 linkdown
root@test03:/home/roy# ip netns exec server01 ip route
default dev ipip01 scope link
10.2.0.0/16 dev veth2 proto kernel scope link src 10.2.0.4
10.2.0.0/16 dev ipip01 proto kernel scope link src 10.2.0.5
Пакеты отправляются обратно на мост (idk
), и вот TCPDump, показывающий это:
root@test03:/home/roy# tcpdump -i idk dst host 10.xxx.xxx.xxx -nne
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on idk, link-type EN10MB (Ethernet), capture size 262144 bytes
17:14:48.529043 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:50.224207 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:52.024256 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 147: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 105
17:14:52.234143 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:52.519119 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:54.529119 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:55.024089 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 147: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 105
17:14:55.234171 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:56.524109 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:14:57.229134 82:b3:6f:24:0e:74 > 96:f0:b6:dc:e5:1a, ethertype IPv4 (0x0800), length 51: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
Эти пакеты отправляются обратно на мой компьютер (10.xxx.xxx.xxx
). Однако мой компьютер не получает эти пакеты, и я не могу подключиться к приложению. Вот захват пакета с использованием значения интерфейса any
:
root@test03:/home/roy# tcpdump -i any udp and src host 10.50.0.3 and dst host 10.xxx.xxx.xxx -nne
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
17:18:15.214170 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:15.214205 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:16.519127 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:16.519153 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:17.014107 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 149: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 105
17:18:17.014132 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 149: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 105
17:18:17.224127 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:17.224151 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:18.514150 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:18.514208 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:19.219091 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
17:18:19.219115 In 82:b3:6f:24:0e:74 ethertype IPv4 (0x0800), length 53: 10.50.0.3.27015 > 10.xxx.xxx.xxx.7130: UDP, length 9
Я пытался использовать разные адреса MA C источника и назначения для заголовка Ethe rnet. В настоящее время адрес источника MA C устанавливается равным адресу MA C партнера ветхого обмена внутри пространства имен, а адрес назначения MA C устанавливается равным veth / bridge в пространстве имен по умолчанию. Когда я сделал захват пакета, когда маршрут по умолчанию в пространстве имен был установлен равным veth peer вместе со следующим переходом (IP-адрес моста), это были используемые адреса источника и назначения MA C. Я также попытался установить адреса источника и назначения MA C в 0, чтобы увидеть, если это что-то делает. С учетом вышесказанного я попытался установить адрес назначения MA C в адрес MA C основного шлюза. Однако ничего из этого не сработало.
Я также попытался установить правила POSTROUTING для маскировки и SNAT. Вот некоторые из них, которые я пробовал:
Chain POSTROUTING (policy ACCEPT 9 packets, 640 bytes)
pkts bytes target prot opt in out source destination
79 5056 SNAT all -- * * 10.2.0.0/16 0.0.0.0/0 to:10.50.0.3
0 0 SNAT all -- * idk 0.0.0.0/0 0.0.0.0/0 to:10.50.0.3
0 0 SNAT all -- * veth1 0.0.0.0/0 0.0.0.0/0 to:10.50.0.3
0 0 MASQUERADE all -- * * 10.50.0.3 0.0.0.0/0
Ничего из этого не сработало. Я не уверен, что мне понадобится какое-то правило POSTROUTING, чтобы моя программа отправляла пакеты через пару / мост veth (но также подделывалась как IP-адрес сервера переадресации).
Я подтвердил контрольные суммы заголовков IP / UDP также правильны для этих пакетов.
Дополнительные примечания / вопросы:
Как только я выясняю основную проблему, я не уверен, что Лучший способ получить правильные адреса источника и назначения MA C для заголовка Ethe rnet автоматически. По какой-то причине моя функция получить адрес источника MA C от однорангового узла не работает, и я привык задавать адрес назначения MA C для адреса шлюза MA C (который равен всем 0 внутри пространство имен сети). Любые предложения приветствуются!
Вышеприведенная программа создается для целей тестирования, и я просто хочу посмотреть, сработает ли моя теория. Если я могу заставить это работать, я хочу найти более быстрое решение, чем сокеты AF_PACKET. Сокеты AF_PACKET получают копию пакета от ядра, насколько я понимаю. Следовательно, это приведет к увеличению нагрузки. Я хочу найти способ перехвата всех исходящих пакетов в туннеле IPIP и изменить сам исходный пакет перед отправкой его через ветеринарный узел в пространстве имен сети. Если у вас есть какие-либо предложения для этого, не стесняйтесь, дайте мне знать! Я хотел начать искать DPDK для этого, но я не думаю, что DPDK сможет подключаться к туннелю IPIP вместе с интерфейсом, к которому уже привязано приложение. Насколько я понимаю, это требует специального NI C.
Мне было интересно, если кто-нибудь знает, что я пропускаю или делаю здесь неправильно. Я бы предположил, что либо отсутствует правило IPTables, либо мои исходные / конечные адреса MA C неверны в отправляемом мной заголовке Ethe rnet.
Если вам нужна дополнительная информация, пожалуйста, дайте мне знать!
Любая помощь очень ценится и спасибо за ваше время.