c - Linux: When sending Ethernet frames the ethertype is being re-written -
i have written c programme writing ethernet frames straight onto wire (which runs in 2 modes, sender or receiver). the sender sending frames 2 vlan tags on them (qinq) strangely when frame reaches receiver ethertype has changed of standard (single) vlan encapsulated frame. possible nic doing this, or linux not allow this? wireshark shows same behaviour tcpdump.
to explain image below sender sending frames ethernet broadcast address ff:ff:ff:ff:ff:ff find receiver (these 2 test machines connected via crossover cable result below same switch or hub). can see frames coming in 2 vlan tags on them, outer tag has ethertype of 0x8100 , vlan id of 40, inner vlan has ethertype 0x8100 , vlan id of 20. know though, qinq frames outer frame should have ethertype of 0x88a8!
when frames sent sender in application have outer ethertype of 0x88a8 per image below received 0x8100 on both inner , outer ethertypes. highlighted text receiver sending reply, can see frames have 0x88a8 on outer frame , 0x8100 on inner. tcpdump on other machine shows same (it's same code! frames sent 0x88a8 outer 0x8100 inner received 0x8100 outer , 0x8100 inner).
void buildheaders(char* &txbuffer, unsigned char (&destmac)[6], unsigned char (&sourcemac)[6], short &pcp, short &vlanid, short &qinqid, short &qinqpcp, int &headerslength) { int offset = 0; short tpi = 0; short tci = 0; short *p = &tpi; short *c = &tci; short vlanidtemp; // re-create destination , source mac addresses memcpy((void*)txbuffer, (void*)destmac, eth_alen); memcpy((void*)(txbuffer+eth_alen), (void*)sourcemac, eth_alen); offset = (eth_alen*2); // add together on qinq tag protocol identifier vlanidtemp = qinq tpi = htons(0x88a8); //0x88a8 == ieee802.1ad, 0x9100 == older ieee802.1qinq memcpy((void*)(txbuffer+offset), p, 2); offset+=2; // build qinq tag command identifier: tci = (qinqpcp & 0x07) << 5; qinqid = qinqid >> 8; tci = tci | (qinqid & 0x0f); qinqid = vlanidtemp; qinqid = qinqid << 8; tci = tci | (qinqid & 0xffff); memcpy((void*)(txbuffer+offset), c, 2); offset+=2; // vlan headers vlanidtemp = vlanid; tpi = htons(0x8100); memcpy((void*)(txbuffer+offset), p, 2); offset+=2; tci = (pcp & 0x07) << 5; vlanid = vlanid >> 8; tci = tci | (vlanid & 0x0f); vlanid = vlanidtemp; vlanid = vlanid << 8; tci = tci | (vlanid & 0xffff); memcpy((void*)(txbuffer+offset), c, 2); offset+=2; // force on ethertype (ipv4) payload tpi = htons(0x0800); memcpy((void*)(txbuffer+offset), p, 2); offset+=2; headerslength = offset; } sendresult = sendto(sockfd, txbuffer, fsizetotal, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
(fully rewritten simplify answer. fixed quite few bugs in c header , source files listed below.)
there give-and-take about this on linux-netdev mailing list in apr 2014, subject "802.1ad packets - kernel changes ether type 88a8 8100 on packets".
it turns out kernel not alter ether type, consumes on receiving packet. show below correctly used vlan routing (including separate rules 802.1ad , 802.1q vlans), given recent plenty kernel. if vlan tag not used routing (say, if there no vlans configured, or if 8021q kernel module not loaded), vlan tag consumed kernel.
thus, original question, "when sending ethernet frames ethertype beingness re-written", incorrect: the ethertype not beingness re-written. consumed kernel.
because vlan tag consumed kernel, libpcap -- packet capture library used tcpdump, wireshark et al. -- tries reintroduce packet headers. unfortunately, always uses 802.1q vlan header (8100).
there suggested change libpcap fixes problem in libpcap, of writing, not seem have been included yet; can still see htons(eth_p_8021q) hardcoded in several places in libpcap source file linux.
i cannot assume you'll take word this, allow me show how can ascertain yourself.
let's write simple packet sender , receiver, uses kernel interfaces directly, without assistance of 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; homecoming -1; } socketfd = socket(af_packet, sock_raw, htons(protocol)); if (socketfd == -1) homecoming -1; { 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; homecoming socketfd; } while (0); { const int saved_errno = errno; close(socketfd); errno = saved_errno; homecoming -1; } } static unsigned int tci(const unsigned int priority, const unsigned int drop, const unsigned int vlan) { homecoming (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; homecoming (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; homecoming (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); homecoming (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]; } homecoming 0; } errno = einval; homecoming -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"); homecoming 1; } if (parse_hwaddr(argv[2], &dstaddr)) { fprintf(stderr, "%s: invalid destination hardware address.\n", argv[2]); homecoming 1; } socketfd = rawpacket_socket(eth_p_all, argv[1], &srcaddr); if (socketfd == -1) { fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno)); homecoming 1; } memset(packet, 0, sizeof packet); /* build qinq header false 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 build qinq headers: %s.\n", strerror(errno)); close(socketfd); homecoming 1; } /* add together 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 zeroes minimum 64 octet length. */ if (size < 64) size = 64; /* send it. */ n = send(socketfd, packet, size, 0); if (n == -1) { fprintf(stderr, "failed send packet: %s.\n", strerror(errno)); shutdown(socketfd, shut_rdwr); close(socketfd); homecoming 1; } fprintf(stderr, "sent %ld bytes:", (long)n); (i = 0; < 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)); homecoming 1; } homecoming 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)) homecoming errno; homecoming 0; } static const char *protocol_name(const unsigned int protocol) { static char buffer[16]; switch (protocol & 0xffffu) { case 0x0001: homecoming "eth_p_802_3"; case 0x0002: homecoming "eth_p_ax25"; case 0x0003: homecoming "eth_p_all"; case 0x0060: homecoming "eth_p_loop"; case 0x0800: homecoming "eth_p_ip"; case 0x0806: homecoming "eth_p_arp"; case 0x8100: homecoming "eth_p_8021q (802.1q vlan)"; case 0x88a8: homecoming "eth_p_8021ad (802.1ad vlan)"; default: snprintf(buffer, sizeof buffer, "0x%04x", protocol & 0xffffu); homecoming (const char *)buffer; } } static const char *header_type(const unsigned int hatype) { static char buffer[16]; switch (hatype) { case 1: homecoming "arphrd_ether: ethernet 10mbps"; case 2: homecoming "arphrd_eether: experimental ethernet"; case 768: homecoming "arphrd_tunnel: ip tunnel"; case 772: homecoming "arphrd_loop: loopback"; default: snprintf(buffer, sizeof buffer, "0x%04x", hatype); homecoming buffer; } } static const char *packet_type(const unsigned int pkttype) { static char buffer[16]; switch (pkttype) { case packet_host: homecoming "packet_host"; case packet_broadcast: homecoming "packet_broadcast"; case packet_multicast: homecoming "packet_multicast"; case packet_otherhost: homecoming "packet_otherhost"; case packet_outgoing: homecoming "packet_outgoing"; default: snprintf(buffer, sizeof buffer, "0x%02x", pkttype); homecoming (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 info = src; size_t i; if (len < 1) return; if (before) fputs(before, out); (i = 0; < 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"); homecoming 1; } if (install_done(sigint) || install_done(sighup) || install_done(sigterm)) { fprintf(stderr, "cannot install signal handlers: %s.\n", strerror(errno)); homecoming 1; } socketfd = rawpacket_socket(eth_p_all, argv[1], null); if (socketfd == -1) { fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno)); homecoming 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); homecoming 1; } if (setsockopt(socketfd, sol_socket, so_bindtodevice, argv[1], strlen(argv[1]) + 1)) { fprintf(stderr, "cannot bind device %s: %s.\n", argv[1], strerror(errno)); close(socketfd); homecoming 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); homecoming 0; } to compile, can use
gcc -o2 receiver.c -o receiver gcc -o2 sender.c -o sender run without parameters, or -h, see usage either one. sender sends 1 packet. receiver listens on specified interface (in promiscuous mode), until interrupt (ctrl+c) or send term signal.
start receiver in 1 virtual terminal on loopback interface:
sudo ./receiver lo in virtual terminal on same machine, running
sudo ./sender lo ff:ff:ff:ff:ff:ff '_the contents of 64-byte ethernet frame_' will output (newlines , indentation added ease of understanding)
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 in receiver terminal, however, see (newlines , indentation added):
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 the first one, packet_outgoing, captured outgoing; shows kernel did not consume headers when packet sent.
the sec one, packet_multicast, captured arrived. (since ethernet address ff:ff:ff:ff:ff:ff, multicast packet.)
as can see, latter packet has 802.1q vlan header -- client vlan --, kernel having consumed 802.1ad service vlan tag.
the above confirms scenario loopback interface, @ least. using raw packet interface, kernel consumes 802.1ad vlan header (the 1 next recipient address). if utilize tcpdump -i eth0 alongside receiver, can see libpcap reinserting consumed header packet!
loopback interface bit special, let's redo test using virtual machines. happen running up-to-date xubuntu 14.04 (all updates installed of 2014-06-28, ubuntu 3.13.0-29-generic #53 x86_64 kernel). sender hw address 08 00 00 00 00 02, receivers 08 00 00 00 00 01, , 2 connected internal network without else present.
(again, add together newlines , indentation output create easier read.)
sender, on virtual machine 2:
sudo ./sender eth0 08:00:00:00:00:01 '_the contents of 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 receiver, on virtual machine 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 as can see, results same loopback case. in particular, 802.1ad service vlan tag consumed on receive. (you can utilize tcpdump or wireshark, compare received packets: libpcap reinserting consumed vlan tag pack packet.)
if have recent plenty kernel (support added in apr 2013), can configure 802.1ad vlan(s) on recipient using:
sudo modprobe 8021q sudo ip link add together link eth0 eth0.service1 type vlan proto 802.1ad id 1 receiving on eth0 receive packets, on eth0.service1 packets 802.1ad vlan tag, vlan id 1. not capture frames 802.1q vlan tags same vlan id, meaning can total routing on receive both 802.1ad , 802.1q vlans.
i didn't trust above test, myself; created number of 802.1ad , 802.1q vlans, separate receive instances on each one, , changed packet headers (not service (first) tci() , client (second) tci() in rawpacket_qinq() phone call in sender.c alter service , client vlan ids, changing rawpacket.h, verify 802.1ad (88a8) , 802.1q (8100) vlan headers routed correctly on receive). worked beautifully, without hiccups.
in summary:
given recent plenty linux kernel version, ethernet frames correctly routed linux kernel (by 8021q module) on receive, including separate vlan interfaces 802.1ad , 802.1q same vlan ids. kernel consume vlan header used routing, if no vlans configured.
questions?
c linux raw-ethernet
No comments:
Post a Comment