(पूरी तरह से इस सवाल का जवाब आसान बनाने के लिए फिर से लिखा। मैं अपने सी शीर्षक और स्रोत फ़ाइलों में काफी कुछ बग ठीक नीचे सूचीबद्ध।)
linux-netdev mailing list पर एक चर्चा about exactly this था अप्रैल 2014 में, विषय "802.1AD पैकेट्स - कर्नेल सभी पैकेट पर 88 ए 8 से 8100 तक ईथर प्रकार बदलता है"।
यह पता चला है कि कर्नेल ईथर प्रकार को नहीं बदलता है, यह बस इसे एक पैकेट प्राप्त करने पर खपत करता है। मैं नीचे दिखाता हूं कि हाल ही में पर्याप्त कर्नेल दिए गए वीएलएएन रूटिंग (802.1AD और 802.1Q वीएलएएन के लिए अलग-अलग नियमों सहित) के लिए इसका सही ढंग से उपयोग किया जाता है। भले ही वीएलएएन टैग का उपयोग रूटिंग के लिए नहीं किया जाता है (कहें, यदि कोई वीएलएएन कॉन्फ़िगर नहीं किया गया है, या यदि 8021q कर्नेल मॉड्यूल लोड नहीं होता है), तो VLAN टैग कर्नेल द्वारा खपत किया जाता है।
इस प्रकार, मूल प्रश्न, "इथर-प्रकार फिर से लिखा जा रहा है ईथरनेट भेजने फ्रेम", गलत है: इथर-प्रकार नहीं किया जा रहा फिर से लिखा है। यह कर्नेल द्वारा खपत है।
क्योंकि वीएलएएन टैग कर्नेल द्वारा उपभोग किया जाता है, libpcap - जो टीसीपीडम्प, वायर्सहार्क एट अल द्वारा उपयोग की जाने वाली पैकेट कैप्चर लाइब्रेरी है। - इसे वापस पैकेट हेडर में पुन: पेश करने का प्रयास करता है। दुर्भाग्यवश, यह हमेशा 802.1Q VLAN शीर्षलेख (8100) का उपयोग करता है।
libpcap में suggested change है जो libpcap में ठीक से इस समस्या को हल करता है, लेकिन इस लेखन के रूप में, ऐसा लगता है कि यह अभी तक शामिल नहीं है; आप अभी भी htons(ETH_P_8021Q)
libpcap source file for Linux में कई स्थानों पर हार्डकोडेड देख सकते हैं।
मैं कल्पना नहीं कर सकते तो आप इस के लिए मेरे शब्द ले लेंगे, तो मुझे बताएंगे कि कैसे आप खुद के लिए यह पता लगाने कर सकते हैं।
चलिए एक साधारण पैकेट प्रेषक और रिसीवर लिखते हैं, जो कि libpcap की सहायता के बिना सीधे कर्नेल इंटरफेस का उपयोग करता है।
rawpacket.h:
#ifndef RAWPACKET_H
#define RAWPACKET_H
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netpacket/packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <linux/if_ether.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
static int rawpacket_socket(const int protocol,
const char *const interface,
void *const hwaddr)
{
struct ifreq iface;
struct sockaddr_ll addr;
int socketfd, result;
int ifindex = 0;
if (!interface || !*interface) {
errno = EINVAL;
return -1;
}
socketfd = socket(AF_PACKET, SOCK_RAW, htons(protocol));
if (socketfd == -1)
return -1;
do {
memset(&iface, 0, sizeof iface);
strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
result = ioctl(socketfd, SIOCGIFINDEX, &iface);
if (result == -1)
break;
ifindex = iface.ifr_ifindex;
memset(&iface, 0, sizeof iface);
strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
result = ioctl(socketfd, SIOCGIFFLAGS, &iface);
if (result == -1)
break;
iface.ifr_flags |= IFF_PROMISC;
result = ioctl(socketfd, SIOCSIFFLAGS, &iface);
if (result == -1)
break;
memset(&iface, 0, sizeof iface);
strncpy((char *)&iface.ifr_name, interface, IFNAMSIZ);
result = ioctl(socketfd, SIOCGIFHWADDR, &iface);
if (result == -1)
break;
memset(&addr, 0, sizeof addr);
addr.sll_family = AF_PACKET;
addr.sll_protocol = htons(protocol);
addr.sll_ifindex = ifindex;
addr.sll_hatype = 0;
addr.sll_pkttype = 0;
addr.sll_halen = ETH_ALEN; /* Assume ethernet! */
memcpy(&addr.sll_addr, &iface.ifr_hwaddr.sa_data, addr.sll_halen);
if (hwaddr)
memcpy(hwaddr, &iface.ifr_hwaddr.sa_data, ETH_ALEN);
if (bind(socketfd, (struct sockaddr *)&addr, sizeof addr))
break;
errno = 0;
return socketfd;
} while (0);
{
const int saved_errno = errno;
close(socketfd);
errno = saved_errno;
return -1;
}
}
static unsigned int tci(const unsigned int priority,
const unsigned int drop,
const unsigned int vlan)
{
return (vlan & 0xFFFU)
| ((!!drop) << 12U)
| ((priority & 7U) << 13U);
}
static size_t rawpacket_qinq(unsigned char *const buffer, size_t const length,
const unsigned char *const srcaddr,
const unsigned char *const dstaddr,
const unsigned int service_tci,
const unsigned int customer_tci,
const unsigned int ethertype)
{
unsigned char *ptr = buffer;
uint32_t tag;
uint16_t type;
if (length < 2 * ETH_ALEN + 4 + 4 + 2) {
errno = ENOSPC;
return (size_t)0;
}
memcpy(ptr, dstaddr, ETH_ALEN);
ptr += ETH_ALEN;
memcpy(ptr, srcaddr, ETH_ALEN);
ptr += ETH_ALEN;
/* Service 802.1AD tag. */
tag = htonl(((uint32_t)(ETH_P_8021AD) << 16U)
| ((uint32_t)service_tci & 0xFFFFU));
memcpy(ptr, &tag, 4);
ptr += 4;
/* Client 802.1Q tag. */
tag = htonl(((uint32_t)(ETH_P_8021Q) << 16U)
| ((uint32_t)customer_tci & 0xFFFFU));
memcpy(ptr, &tag, 4);
ptr += 4;
/* Ethertype tag. */
type = htons((uint16_t)ethertype);
memcpy(ptr, &type, 2);
ptr += 2;
return (size_t)(ptr - buffer);
}
#endif /* RAWPACKET_H */
sender.c:
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include "rawpacket.h"
static size_t parse_data(unsigned char *const data, const size_t size,
const char *const string)
{
char *ends = strncpy((char *)data, string, size);
return (size_t)(ends - (char *)data);
}
static int parse_hwaddr(const char *const string,
void *const hwaddr)
{
unsigned int addr[6];
char dummy;
if (sscanf(string, " %02x:%02x:%02x:%02x:%02x:%02x %c",
&addr[0], &addr[1], &addr[2],
&addr[3], &addr[4], &addr[5],
&dummy) == 6 ||
sscanf(string, " %02x%02x%02x%02x%02x%02x %c",
&addr[0], &addr[1], &addr[2],
&addr[3], &addr[4], &addr[5],
&dummy) == 6) {
if (hwaddr) {
((unsigned char *)hwaddr)[0] = addr[0];
((unsigned char *)hwaddr)[1] = addr[1];
((unsigned char *)hwaddr)[2] = addr[2];
((unsigned char *)hwaddr)[3] = addr[3];
((unsigned char *)hwaddr)[4] = addr[4];
((unsigned char *)hwaddr)[5] = addr[5];
}
return 0;
}
errno = EINVAL;
return -1;
}
int main(int argc, char *argv[])
{
unsigned char packet[ETH_FRAME_LEN + ETH_FCS_LEN];
unsigned char srcaddr[6], dstaddr[6];
int socketfd;
size_t size, i;
ssize_t n;
if (argc < 3 || argc > 4 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s interface hwaddr [message]\n", argv[0]);
fprintf(stderr, "\n");
return 1;
}
if (parse_hwaddr(argv[2], &dstaddr)) {
fprintf(stderr, "%s: Invalid destination hardware address.\n", argv[2]);
return 1;
}
socketfd = rawpacket_socket(ETH_P_ALL, argv[1], &srcaddr);
if (socketfd == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return 1;
}
memset(packet, 0, sizeof packet);
/* Construct a QinQ header for a fake Ethernet packet type. */
size = rawpacket_qinq(packet, sizeof packet, srcaddr, dstaddr,
tci(7, 0, 1U), tci(7, 0, 2U),
ETH_P_IP);
if (!size) {
fprintf(stderr, "Failed to construct QinQ headers: %s.\n", strerror(errno));
close(socketfd);
return 1;
}
/* Add packet payload. */
if (argc > 3)
size += parse_data(packet + size, sizeof packet - size, argv[3]);
else
size += parse_data(packet + size, sizeof packet - size, "Hello!");
/* Pad with zeroes to minimum 64 octet length. */
if (size < 64)
size = 64;
/* Send it. */
n = send(socketfd, packet, size, 0);
if (n == -1) {
fprintf(stderr, "Failed to send packet: %s.\n", strerror(errno));
shutdown(socketfd, SHUT_RDWR);
close(socketfd);
return 1;
}
fprintf(stderr, "Sent %ld bytes:", (long)n);
for (i = 0; i < size; i++)
fprintf(stderr, " %02x", packet[i]);
fprintf(stderr, "\n");
fflush(stderr);
shutdown(socketfd, SHUT_RDWR);
if (close(socketfd)) {
fprintf(stderr, "Error closing socket: %s.\n", strerror(errno));
return 1;
}
return 0;
}
receiver.c:
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include "rawpacket.h"
static volatile sig_atomic_t done = 0;
static void handle_done(int signum)
{
done = signum;
}
static int install_done(const int signum)
{
struct sigaction act;
sigemptyset(&act.sa_mask);
act.sa_handler = handle_done;
act.sa_flags = 0;
if (sigaction(signum, &act, NULL))
return errno;
return 0;
}
static const char *protocol_name(const unsigned int protocol)
{
static char buffer[16];
switch (protocol & 0xFFFFU) {
case 0x0001: return "ETH_P_802_3";
case 0x0002: return "ETH_P_AX25";
case 0x0003: return "ETH_P_ALL";
case 0x0060: return "ETH_P_LOOP";
case 0x0800: return "ETH_P_IP";
case 0x0806: return "ETH_P_ARP";
case 0x8100: return "ETH_P_8021Q (802.1Q VLAN)";
case 0x88A8: return "ETH_P_8021AD (802.1AD VLAN)";
default:
snprintf(buffer, sizeof buffer, "0x%04x", protocol & 0xFFFFU);
return (const char *)buffer;
}
}
static const char *header_type(const unsigned int hatype)
{
static char buffer[16];
switch (hatype) {
case 1: return "ARPHRD_ETHER: Ethernet 10Mbps";
case 2: return "ARPHRD_EETHER: Experimental Ethernet";
case 768: return "ARPHRD_TUNNEL: IP Tunnel";
case 772: return "ARPHRD_LOOP: Loopback";
default:
snprintf(buffer, sizeof buffer, "0x%04x", hatype);
return buffer;
}
}
static const char *packet_type(const unsigned int pkttype)
{
static char buffer[16];
switch (pkttype) {
case PACKET_HOST: return "PACKET_HOST";
case PACKET_BROADCAST: return "PACKET_BROADCAST";
case PACKET_MULTICAST: return "PACKET_MULTICAST";
case PACKET_OTHERHOST: return "PACKET_OTHERHOST";
case PACKET_OUTGOING: return "PACKET_OUTGOING";
default:
snprintf(buffer, sizeof buffer, "0x%02x", pkttype);
return (const char *)buffer;
}
}
static void fhex(FILE *const out,
const char *const before,
const char *const after,
const void *const src, const size_t len)
{
const unsigned char *const data = src;
size_t i;
if (len < 1)
return;
if (before)
fputs(before, out);
for (i = 0; i < len; i++)
fprintf(out, " %02x", data[i]);
if (after)
fputs(after, out);
}
int main(int argc, char *argv[])
{
struct sockaddr_ll addr;
socklen_t addrlen;
unsigned char data[2048];
ssize_t n;
int socketfd, flag;
if (argc != 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
fprintf(stderr, " %s interface\n", argv[0]);
fprintf(stderr, "\n");
return 1;
}
if (install_done(SIGINT) ||
install_done(SIGHUP) ||
install_done(SIGTERM)) {
fprintf(stderr, "Cannot install signal handlers: %s.\n", strerror(errno));
return 1;
}
socketfd = rawpacket_socket(ETH_P_ALL, argv[1], NULL);
if (socketfd == -1) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return 1;
}
flag = 1;
if (setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof flag)) {
fprintf(stderr, "Cannot set REUSEADDR socket option: %s.\n", strerror(errno));
close(socketfd);
return 1;
}
if (setsockopt(socketfd, SOL_SOCKET, SO_BINDTODEVICE, argv[1], strlen(argv[1]) + 1)) {
fprintf(stderr, "Cannot bind to device %s: %s.\n", argv[1], strerror(errno));
close(socketfd);
return 1;
}
while (!done) {
memset(data, 0, sizeof data);
memset(&addr, 0, sizeof addr);
addrlen = sizeof addr;
n = recvfrom(socketfd, &data, sizeof data, 0,
(struct sockaddr *)&addr, &addrlen);
if (n == -1) {
if (errno == EINTR)
continue;
fprintf(stderr, "Receive error: %s.\n", strerror(errno));
break;
}
printf("Received %d bytes:\n", (int)n);
printf("\t Protocol: %s\n", protocol_name(htons(addr.sll_protocol)));
printf("\t Interface: %d\n", (int)addr.sll_ifindex);
printf("\t Header type: %s\n", header_type(addr.sll_hatype));
printf("\t Packet type: %s\n", packet_type(addr.sll_pkttype));
fhex(stdout, "\t Address:", "\n", addr.sll_addr, addr.sll_halen);
fhex(stdout, "\t Data:", "\n", data, n);
printf("\n");
fflush(stdout);
}
shutdown(socketfd, SHUT_RDWR);
close(socketfd);
return 0;
}
संकलन के लिए, आप उपयोग कर सकते हैं
gcc -O2 receiver.c -o receiver
gcc -O2 sender.c -o sender
किसी भी के लिए उपयोग देखने के लिए पैरामीटर के बिना चलाएं, या -h
के साथ। sender
केवल एक पैकेट भेजता है। receiver
निर्दिष्ट इंटरफ़ेस (विशिष्ट मोड में) सुनता है, जब तक आप इसे बाधित नहीं करते हैं (Ctrl + सी) या इसे TERM
सिग्नल भेजें।
लूपबैक इंटरफेस पर एक आभासी टर्मिनल में
प्रारंभ रिसीवर:
sudo ./receiver lo
एक ही मशीन पर एक और आभासी टर्मिनल में, चल
sudo ./sender lo FF:FF:FF:FF:FF:FF '_The contents of a 64-byte Ethernet frame_'
इच्छा निर्गम (नई-पंक्तियों और खरोज समझ में आसानी के लिए जोड़ा)
Sent 64 bytes: ff ff ff ff ff ff
00 00 00 00 00 00
88 a8 e0 01
81 00 e0 02
08 00
5f 54 68 65 20 63 6f 6e
74 65 6e 74 73 20 6f 66
20 61 20 36 34 2d 62 79
74 65 20 45 74 68 65 72
6e 65 74 20 66 72 61 6d
65 5f
रिसीवर टर्मिनल में, कैसे ver, हम (नई-पंक्तियों और खरोज जोड़ा) देखें:
Received 64 bytes:
Protocol: ETH_P_ALL
Interface: 1
Header type: ATPHRD_LOOP: Loopback
Packet type: PACKET_OUTGOING
Address: 00 00 00 00 00 00
Data: ff ff ff ff ff ff
00 00 00 00 00 00
88 a8 e0 01
81 00 e0 02
08 00
5f 54 68 65 20 63 6f 6e
74 65 6e 74 73 20 6f 66
20 61 20 36 34 2d 62 79
74 65 20 45 74 68 65 72
6e 65 74 20 66 72 61 6d
65 5f
Received 60 bytes:
Protocol: ETH_P_8021Q (802.1Q VLAN)
Interface: 1
Header type: ATPHRD_LOOP: Loopback
Packet type: PACKET_MULTICAST
Address: 00 00 00 00 00 00
Data: ff ff ff ff ff ff
00 00 00 00 00 00
81 00 e0 02
08 00
5f 54 68 65 20 63 6f 6e
74 65 6e 74 73 20 6f 66
20 61 20 36 34 2d 62 79
74 65 20 45 74 68 65 72
6e 65 74 20 66 72 61 6d
65 5f
पहले एक, PACKET_OUTGOING, बाहर जाने वाले के रूप में कब्जा कर लिया था; यह दिखाता है कि जब पैकेट भेजा गया था तो कर्नेल किसी भी शीर्षलेख का उपभोग नहीं करता था।
दूसरा, PACKET_MULTICAST, जैसा कि यह पहुंचा था, पर कब्जा कर लिया गया था। (चूंकि ईथरनेट पता एफएफ था: एफएफ: एफएफ: एफएफ: एफएफ: एफएफ, यह एक मल्टीकास्ट पैकेट है।)
जैसा कि आप देख सकते हैं, बाद वाले पैकेट में केवल 802.1Q वीएलएएन हैडर - क्लाइंट वीएलएएन - -, कर्नेल ने 802.1AD सेवा वीएलएएन टैग का उपभोग किया है।
उपरोक्त लूपबैक इंटरफ़ेस के लिए परिदृश्य की पुष्टि करता है, कम से कम। कच्चे पैकेट इंटरफेस का उपयोग करते हुए, कर्नेल 802.1AD वीएलएएन हेडर (प्राप्तकर्ता के पते के तुरंत बाद) का उपभोग करता है। यदि आप रिसीवर के साथ tcpdump -i eth0
का उपयोग करते हैं, तो आप देख सकते हैं कि libpcap उपभोग वाले शीर्षलेख को पैकेट पर वापस जोड़ रहा है!
लूपबैक इंटरफ़ेस थोड़ा सा विशेष है, तो आभासी मशीनों का उपयोग करके परीक्षण को फिर से करें। मैं अद्यतित जुबंटू 14.04 (2014-06-28 के रूप में स्थापित सभी अद्यतन, उबंटू 3.13.0-29-जेनेरिक # 53 x86_64 कर्नेल) चल रहा है। प्रेषक एचडब्ल्यू पता 08 00 00 00 00 02 है, रिसीवर 08 00 00 00 00 01 है, और दोनों किसी अन्य नेटवर्क के बिना किसी आंतरिक नेटवर्क से जुड़े हुए हैं।
(फिर से, मैं इसे पढ़ने में आसान बनाने के लिए आउटपुट में न्यूलाइन और इंडेंटेशन जोड़ता हूं।)
भेजने वाले, आभासी मशीन 2 पर:
sudo ./sender eth0 08:00:00:00:00:01 '_The contents of a 64-byte Ethernet frame_'
Sent 64 bytes: 08 00 00 00 00 01
08 00 00 00 00 02
88 a8 e0 01
81 00 e0 02
08 00
5f 54 68 65 20 63 6f 6e
74 65 6e 74 73 20 6f 66
20 61 20 36 34 2d 62 79
74 65 20 45 74 68 65 72
6e 65 74 20 66 72 61 6d
65 5f
रिसीवर, आभासी मशीन 1:
sudo ./receiver eth0
Received 60 bytes:
Protocol: ETH_P_8021Q (802.1Q VLAN)
Interface: 2
Header type: ARPHRD_ETHER: Ethernet 10Mbps
Packet type: PACKET_HOST
Address: 08 00 00 00 00 02
Data: 08 00 00 00 00 01
08 00 00 00 00 02
81 00 e0 02
08 00
5f 54 68 65 20 63 6f 6e
74 65 6e 74 73 20 6f 66
20 61 20 36 34 2d 62 79
74 65 20 45 74 68 65 72
6e 65 74 20 66 72 61 6d
65 5f
आप देख सकते हैं, परिणाम मूल रूप से लूपबैक मामले के लिए जैसे ही हैं। विशेष रूप से, प्राप्त करने पर 802.1AD सेवा वीएलएएन टैग का उपभोग किया गया था। (आप प्राप्त पैकेट की तुलना करने के लिए टीसीपीडम्प या वायरशर्क का उपयोग कर सकते हैं: libpcap स्पष्ट रूप से उपभोग वाले वीएलएएन टैग पैक को पैकेट में पुन: सम्मिलित कर रहा है।)
यदि आपके पास हाल ही में पर्याप्त कर्नेल (support अप्रैल 2013 में जोड़ा गया था), तो आप कर सकते हैं का उपयोग कर प्राप्तकर्ता पर एक 802.1AD VLAN (रों) कॉन्फ़िगर करें:
sudo modprobe 8021q
sudo ip link add link eth0 eth0.service1 type vlan proto 802.1ad id 1
eth0
पर प्राप्त सभी पैकेट प्राप्त होगा, लेकिन eth0.service1
केवल एक 802.1AD VLAN टैग के साथ उन लोगों के पैकेट, VLAN आईडी के साथ पर 1. यह नहीं है उसी वीएलएएन आईडी के साथ 802.1Q वीएलएएन टैग के साथ फ़्रेम कैप्चर करें, जिसका अर्थ है कि आप 802.1AD और 802 दोनों के लिए प्राप्त होने पर पूर्ण रूटिंग कर सकते हैं। 1 क्यू वीएलएएनएस
मुझे केवल उपर्युक्त परीक्षण पर भरोसा नहीं था; (नहीं मैं हर एक पर अलग receive
उदाहरणों के साथ 802.1AD और 802.1Q VLANs का गठन किया है, और बदल पैकेट हेडर केवल सेवा (प्रथम) tci()
और ग्राहक (दूसरा) rawpacket_qinq()
में कॉल sender.c में tci()
को 802.1AD (88a8) और 802.1Q (8100) VLAN शीर्षलेखों को प्राप्त करने के लिए सही ढंग से रूट किया गया है, यह सत्यापित करने के लिए सेवा और क्लाइंट वीएलएएन आईडी बदलें, लेकिन rawpacket.h को भी बदलना। यह सब बिना किसी हिचकी के खूबसूरती से काम किया।
सारांश में:
हाल ही में एक पर्याप्त लिनक्स कर्नेल संस्करण, ईथरनेट फ्रेम सही ढंग से, लिनक्स कर्नेल (8021q मॉड्यूल द्वारा) द्वारा रूट किया जाता है पर प्राप्त एक ही साथ 802.1AD और 802.1Q के लिए अलग VLAN इंटरफेस सहित देखते हुए वीएलएएन आईडी कर्नेल रूटिंग के लिए उपयोग किए जाने वाले वीएलएएन हेडर का उपभोग करता है, भले ही कोई वीएलएएन कॉन्फ़िगर नहीं किया गया हो।
प्रश्न?
यह सुनिश्चित करने के लिए 'ethtool' का उपयोग करें कि आपके एनआईसी (ओं) पर कोई भी वीएलएएन ऑफलोडिंग अक्षम है। इसके अलावा, आप 'sockFD' कैसे खोल रहे हैं? –
'sockFD = सॉकेट (AF_PACKET, SOCK_RAW, htons (ETH_P_ALL));' – jwbensley
मैं प्राप्त करने और अपडेट करने के अगले मौके पर ethtool आउटपुट की जांच करूंगा। – jwbensley