/* * ERSPAN capture, v0.2 * * Changes from v0.1: Option to specify several VLAN IDs and ERSPAN IDs * * Open a raw GRE socket, receive ERSPAN packets and dump payload * in tcpdump-compatible pcap format to standard out. * * Compile with e.g. * gcc -O3 -Wall erspan-capture.c -o erspan-capture * * Derivative of Python ERSPAN capture code from * http://cisco.cluepon.net/index.php/ERSPAN_to_PCAP_script * (c) 2007 Phil Mayers * * Copyright 2012 - Peter Rathlev (peter.rathlev@stab.rm.dk) * * This software is licensed under the GPL version 2 ONLY * * This program is free software; you can redistribute it and/or modify * it under the terms of version 2 of the the GNU General Public License * as published by the Free Software Foundation * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * See http://www.gnu.org/licenses/gpl-2.0.html * * Tested on Linux 2.6 (Fedora 13-16, CentOS 5.5-5.9, CentOS 6.1-6.3) * Tested on AIX 5.2 * * For some reason, AIX's "ip.ip_len" doesn't seem to count the * IP header size, so it's 20 bytes off. I've tried to work * around this with a simple "#ifdef _AIX" but don't know if this * is the correct way to do it. * */ #include #include #include #include #include #include #include #include #include /* #define DEBUG */ /* Shorthand debug functions */ #ifdef DEBUG #define DEBUG_PRINTF(...) fprintf(stderr, __VA_ARGS__) #define DEBUG_PRINT_ARRAY(...) print_array(__VA_ARGS__) #else #define DEBUG_PRINTF(...) #define DEBUG_PRINT_ARRAY(...) #endif /* IPPROTO_GRE isn't present in netinet/in.h on (at least) AIX 5.2 */ #ifndef IPPROTO_GRE #define IPPROTO_GRE 47 #endif /* Allow user to specify this many VLANs and ERSPAN IDs to include */ #define MAX_NUMBER_OF_VLANS 16 #define MAX_NUMBER_OF_ERSPAN_IDS 16 /* ERSPAN "GRE" header */ struct __attribute__ ((__packed__)) erspan_gre { uint16_t erspan_gre_f; uint16_t erspan_gre_protocol_type; uint16_t erspan_gre_checksum; uint16_t erspan_gre_reserved2; // Cisco specific ERSPAN fields uint16_t erspan_gre_unknown1; uint16_t erspan_gre_unknown2; uint16_t erspan_gre_unknown3; uint16_t erspan_gre_unknown4; }; /* PCAP on-disk packet header */ struct __attribute__ ((__packed__)) pcap_packet_header { uint32_t tv_sec; uint32_t tv_usec; uint32_t caplen; uint32_t len; }; #define MAX_PACKET_LEN 65535 #define MAX_PAYLOAD_LEN (MAX_PACKET_LEN - sizeof(struct ip) - sizeof(struct erspan_gre)) #ifdef DEBUG void print_array(const char *prefix, unsigned char *data, int length) { fprintf(stderr, "%s: %02x", prefix, data[0]); int i; for (i = 1; i < length; i++) { fprintf(stderr, " %02x", data[i]); } fprintf(stderr, "\n"); } #endif void display_usage() { fprintf(stderr, "Usage missing!\n"); exit(0); } int main(int argc, char *argv[]) { struct in_addr RemotePeer = { 0 }; struct sockaddr_in server_addr; int VLAN_IDs[MAX_NUMBER_OF_VLANS]; int VLAN_ID_idx = 0; int VLAN_ID_ok; int ERSPAN_IDs[MAX_NUMBER_OF_ERSPAN_IDS]; int ERSPAN_ID_idx = 0; int ERSPAN_ID_ok; char *str = optarg; char *token; void *databuffer; struct timeval tv; struct ip *ip_pkt; struct erspan_gre *gre_pkt; void *payload; struct pcap_packet_header pcap_pkt_hdr; memset(&VLAN_IDs, 0, sizeof(VLAN_IDs)); memset(&ERSPAN_IDs, 0, sizeof(ERSPAN_IDs)); // Parse command line arguments static const char *optsString = "r:i:v:h?"; int opt = getopt(argc, argv, optsString); while (opt != -1) { switch(opt) { case 'r': DEBUG_PRINTF("DEBUG: Called with -r %s\n", optarg); if (!inet_pton(AF_INET, optarg, &RemotePeer)) { fprintf(stderr, "Invalid remote peer '%s'\n", optarg); exit(1); } break; case 'i': DEBUG_PRINTF("DEBUG: Called with -i %s\n", optarg); str = optarg; int ERSPAN_ID; while ( (token = strtok(str, ",")) ) { if (token == NULL) { break; } ERSPAN_ID = atoi(token); if (ERSPAN_ID < 1 || ERSPAN_ID > 1023) { fprintf(stderr, "ERSPAN ID must be between 1 and 1023 inclusive.\n"); exit(1); } if ( ERSPAN_ID_idx < MAX_NUMBER_OF_ERSPAN_IDS ) { ERSPAN_IDs[ERSPAN_ID_idx++] = ERSPAN_ID; DEBUG_PRINTF("DEBUG: include ERSPAN ID %s\n", token); } else { fprintf(stderr, "ERROR: Maximum number of ERSPAN IDs (MAX_NUMBER_OF_ERSPAN_IDS) exceeded.\n"); exit(1); } str = NULL; } break; case 'v': DEBUG_PRINTF("DEBUG: Called with -v %s\n", optarg); str = optarg; int VLAN_ID; while ( (token = strtok(str, ",")) ) { if (token == NULL) { break; } VLAN_ID = atoi(token); if (VLAN_ID < 1 || VLAN_ID > 4095) { fprintf(stderr, "VLAN ID must be between 1 and 4095 inclusive.\n"); exit(1); } if ( VLAN_ID_idx < MAX_NUMBER_OF_VLANS ) { VLAN_IDs[VLAN_ID_idx++] = VLAN_ID; DEBUG_PRINTF("DEBUG: include VLAN %s\n", token); } else { fprintf(stderr, "ERROR: Maximum number of VLANs (MAX_NUMBER_OF_VLANS) exceeded.\n"); exit(1); } str = NULL; } break; case 'h': case '?': DEBUG_PRINTF("DEBUG: Called with -h\n"); display_usage(); break; default: break; } opt = getopt(argc, argv, optsString); } if (optind < argc) { fprintf(stderr, "Unexpected non-option argument '%s' at position %d\n", argv[optind], optind); exit(1); } // Create socket int gre_socket = socket(AF_INET, SOCK_RAW, IPPROTO_GRE); if (gre_socket < 0) { perror("Error opening GRE socket"); exit(1); } // Create address to bind to memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; // Bind socket if (bind(gre_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("Error binding GRE socket"); exit(1); } // Allocate data buffer databuffer = malloc(MAX_PACKET_LEN); if (!databuffer) { perror("error allocating buffer memory (MAX_PACKET_LEN)"); exit(1); } memset(databuffer, 0, MAX_PACKET_LEN); // Print PCAP header at once static const struct __attribute__ ((__packed__)) pcap_file_header file_header = { 0xa1b2c3d4, /* bpf_u_int32 magic */ PCAP_VERSION_MAJOR, /* u_short version_major */ PCAP_VERSION_MINOR, /* u_short version_minor */ 0, /* bpf_int32 thiszone, gmt to local correction */ 0, /* bpf_u_int32 sigfigs, accuracy of timestamps */ MAX_PAYLOAD_LEN, /* bpf_u_int32 snaplen, max length saved portion of each pkt */ DLT_EN10MB /* bpf_u_int32 linktype, data link type (LINKTYPE_*) */ }; write(1, &file_header, sizeof(file_header)); while (1) { // Get packet if (recvfrom(gre_socket, databuffer, MAX_PACKET_LEN, 0, 0, 0) < 0) { perror("Error in recvfrom"); exit(1); } // Get packet capture time as close as possible to receiving the packet gettimeofday(&tv, 0); // Parse outer IP packet ip_pkt = (struct ip *)databuffer; // DEBUG_PRINT_ARRAY("30 bytes ip_pkt", (void *)ip_pkt, 20); if (ip_pkt->ip_v == 4 && ip_pkt->ip_p == 47) { // Check where the packet came from unless RemotePeer.s_addr == 0 if (RemotePeer.s_addr != 0) { if (ip_pkt->ip_src.s_addr != RemotePeer.s_addr) { #ifdef DEBUG char invalid_peer[64]; fprintf(stderr, "DEBUG: GRE packet from unexpected peer %s, skipping\n", inet_ntop(AF_INET, &(ip_pkt->ip_src.s_addr), invalid_peer, sizeof(invalid_peer))); #endif /* DEBUG */ // Ignore packets not from RemotePeer continue; } } // Parse GRE part gre_pkt = (void *)ip_pkt + (ip_pkt->ip_hl << 2); // DEBUG_PRINT_ARRAY("30 bytes gre_pkt", (void *)gre_pkt, 30); // Expect sequence number enabled and nothing else if (ntohs(gre_pkt->erspan_gre_f) != 0x1000) { DEBUG_PRINTF("unexpected GRE flags (%04x), skipping\n", ntohs(gre_pkt->erspan_gre_f)); continue; } // Expect protocol type 0x88be (ERSPAN) if (ntohs(gre_pkt->erspan_gre_protocol_type) != 0x88be) { DEBUG_PRINTF("unexpected GRE protocol type (%04x), skipping\n", ntohs(gre_pkt->erspan_gre_protocol_type)); continue; } // Check for correct ERSPAN ID unless we accept any if (ERSPAN_IDs[0] != 0) { ERSPAN_ID_idx = 0; ERSPAN_ID_ok = 0; while (ERSPAN_IDs[ERSPAN_ID_idx] != 0) { if ((ntohs(gre_pkt->erspan_gre_unknown2) & 0x03ff) == ERSPAN_IDs[ERSPAN_ID_idx]) { ERSPAN_ID_ok = 1; break; } ERSPAN_ID_idx++; } if (ERSPAN_ID_ok == 0) { DEBUG_PRINTF("unwanted ERSPAN ID %d, skipping\n", ntohs(gre_pkt->erspan_gre_unknown2) & 0x03ff); continue; } } // Check for correct VLAN ID unless we accept any if (VLAN_IDs[0] != 0) { VLAN_ID_idx = 0; VLAN_ID_ok = 0; while (VLAN_IDs[VLAN_ID_idx] != 0) { if ((ntohs(gre_pkt->erspan_gre_unknown1) & 0x0fff) == VLAN_IDs[VLAN_ID_idx]) { VLAN_ID_ok = 1; break; } VLAN_ID_idx++; } if (VLAN_ID_ok == 0) { DEBUG_PRINTF("unwanted VLAN ID %d, skipping\n", ntohs(gre_pkt->erspan_gre_unknown1) & 0x0fff); continue; } } DEBUG_PRINTF("accepted packet with ERSPAN ID %d and VLAN ID %d\n", ERSPAN_IDs[ERSPAN_ID_idx], VLAN_IDs[VLAN_ID_idx]); // Advance to payload payload = (void *)gre_pkt + sizeof(struct erspan_gre); // DEBUG_PRINT_ARRAY("30 bytes payload", (void *)payload, 30); // Find payload length #ifdef _AIX // AIX doesn't seem to include the IP header length in ip.ip_len pcap_pkt_hdr.len = ntohs(ip_pkt->ip_len) - sizeof(struct erspan_gre); #else /* ! _AIX */ pcap_pkt_hdr.len = ntohs(ip_pkt->ip_len) - (ip_pkt->ip_hl << 2) - sizeof(struct erspan_gre); #endif /* _AIX */ if (pcap_pkt_hdr.len > MAX_PAYLOAD_LEN) { pcap_pkt_hdr.caplen = MAX_PAYLOAD_LEN; } else { pcap_pkt_hdr.caplen = pcap_pkt_hdr.len; } // Fill out timestamp information. PCAP expects 32-bit values for tv_sec and tv_usec, // and the size of members of "struct timeval" differ among different platforms. pcap_pkt_hdr.tv_sec = tv.tv_sec & 0xffffffff; pcap_pkt_hdr.tv_usec = tv.tv_usec & 0xffffffff; // Write packet_header write(1, &pcap_pkt_hdr, sizeof(pcap_pkt_hdr)); // Write payload write(1, payload, pcap_pkt_hdr.caplen); } } free(databuffer); }