[Source Code Walkthrough] NetSpeeder: A Simple and Brutal Server-Side Network Accelerator

Preface

First, a simple analysis of how this little thing works: it directly calls libpcap to sniff network packets at the data link layer, then uses a specific syntax (i.e., tcpdump syntax) to filter out packets of interest, and then calls the libnet library to brutally resend those packets once, achieving a “double sending” effect. This can help reduce packet loss and optimize network connections. But if you use this kind of thing on a VPS, it will consume a large amount of international outbound bandwidth. Essentially it benefits yourself at others' expense, so it is not really recommended. This is just a source code analysis.

Still, a complaint: such a simple little thing of about a hundred lines got more than 300 stars on github, while a much more complex and advanced low-level network library like WinDivert does not even have a single star! So what “Wheel Bro” said really is right: when you actually solve problems faced by experts, you won't get much attention, because there are too few people capable of paying attention...

PS: The project is here

Code Annotations

#include <pcap.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
/*
    * A brief introduction:
    * libnet is a small library of interface functions, mainly written in C, providing low-level network packet construction,
    * processing, and sending capabilities.
    * The goal of developing libnet is to establish a simple, unified network programming interface to hide the differences
    * in low-level network programming across different operating systems, so that programmers can focus their efforts on
    * solving key problems.
    */
#include <libnet.h>

/* default snap length (maximum bytes per packet to capture) */
#define SNAP_LEN 65535

#ifdef COOKED
#define ETHERNET_H_LEN 16
#else
#define ETHERNET_H_LEN 14
#endif

#define SPECIAL_TTL 88

void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);

void print_usage(void);


/
* Print the help message. This is a common Linux program convention, nothing much to say here.
/
void print_usage(void) {
printf("Usage: %s [interface][\"filter rule\"]\n", "net_speeder");
printf("\n");
printf("Options:\n");
printf(" interface Listen on for packets.\n");
printf(" filter Rules to filter packets.\n");
printf("\n");
}


/
* Each time a packet is captured, this callback function is automatically invoked.
* The parameters are:
* 1. A pointer to user-defined data
* 2. A pcap-defined struct that describes the currently captured packet;
* it contains the timestamp, captured length, full length, etc. It can be used to compute data offsets, though it is rarely used.
* 3. A pointer to the currently captured data. Of course, the data buffer is allocated and managed by libpcap itself.
* Note that what this pointer points to depends on the interface type. In general it is the standard IEEE 802.3 Ethernet
* data link layer protocol, and this program handles it that way as well.
* For specific link-layer types and specifications, see: http://www.tcpdump.org/linktypes.html
/
void got_packet(u_char args, const struct pcap_pkthdr header, const u_char packet) {
static int count = 1;
struct libnet_ipv4_hdr
ip;

    // First, take out the libnet handle passed in, which can be used to send packets later.
    libnet_t *libnet_handler = (libnet_t *)args;
    count++;

    // The start address of the data plus the Ethernet frame header length gives the IP header address.
    ip = (struct libnet_ipv4_hdr *)(packet + ETHERNET_H_LEN);

    if (ip->ip_ttl != SPECIAL_TTL) {
        // Set a special TTL value for this packet as a marker.
        // Because after we resend this packet, libpcap will capture it again.
        // Without distinguishing it, any packet would be resent infinitely, causing a lot of meaningless traffic.
        ip->ip_ttl = SPECIAL_TTL;
        // Call a libnet function to forcibly send it again. So brutal!
        int len_written = libnet_adv_write_raw_ipv4(libnet_handler, (u_int8_t *)ip, ntohs(ip->ip_len));
        // Of course, sending may fail—anything can happen on a network.
        // So print the error message.
        if (len_written < 0) {
            printf("packet len:[%d] actual write:[%d]\n", ntohs(ip->ip_len), len_written);
            printf("err msg:[%s]\n", libnet_geterror(libnet_handler));
        }
    } else {
        // If the captured packet was resent by this program itself, just ignore it.
        //The packet net_speeder sent. nothing todo
    }
    return;
}

// Splitting such a short initialization function out separately is a bit unnecessary.
libnet_t *start_libnet(char *dev) {
    char errbuf[LIBNET_ERRBUF_SIZE];
    libnet_t *libnet_handler = libnet_init(LIBNET_RAW4_ADV, dev, errbuf);

    if (NULL == libnet_handler) {
        printf("libnet_init: error %s\n", errbuf);
    }
    return libnet_handler;
}

#define ARGC_NUM 3

int main(int argc, char **argv) {
    // Device name
    char *dev = NULL;
    // Error message buffer (this is a libpcap calling convention: most functions accept a buffer address for storing error text)
    char errbuf[PCAP_ERRBUF_SIZE];
    // This is, of course, the handle for a libpcap session, which needs to be initialized via the corresponding pcap functions.
    pcap_t *handle;

    // Pointer to the filter rule string. This string is passed to libpcap to generate the corresponding BPF rules,
    // so that it can filter out the packets the user wants and forward them.
    // For details, see https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man4/bpf.4.html
    char *filter_rule = NULL;
    // As mentioned, libpcap converts string rules into rules recognized by BPF, so this struct is used to store the generated rule.
    struct bpf_program fp;
    bpf_u_int32 net, mask;

    // Check arguments and initialize the device name and filter rule.
    if (argc == ARGC_NUM) {
        dev = argv[1];
        filter_rule = argv[2];
        printf("Device: %s\n", dev);
        printf("Filter rule: %s\n", filter_rule);
    } else {
        // If the arguments are incorrect, print an error and exit.
        print_usage();
        return -1;
    }

    // Usually the Ethernet frame header length is 14 bytes, but it seems some use 16 bytes.
    // Print this information here.
    printf("ethernet header len:[%d](14:normal, 16:cooked)\n", ETHERNET_H_LEN);

    // Call a libpcap function to check the device's subnet mask and IP address.
    // Of course, the call may fail and that is normal—for some virtual devices like pktap on Mac, there is naturally no real address.
    // In that case, just set both the address and mask to zero.
    if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
        printf("Couldn't get netmask for device %s: %s\n", dev, errbuf);
        net = 0;
        mask = 0;
    }

    // Open a pcap session handle; for parameters see the manual.
    printf("init pcap\n");
    handle = pcap_open_live(dev, SNAP_LEN, 1, 1000, errbuf);
    if (handle == NULL) {
        printf("pcap_open_live dev:[%s] err:[%s]\n", dev, errbuf);
        printf("init pcap failed\n");
        return -1;
    }

    // Similarly, initialize a libnet session handle.
    printf("init libnet\n");
    libnet_t *libnet_handler = start_libnet(dev);
    if (NULL == libnet_handler) {
        printf("init libnet failed\n");
        return -1;
    }

    // Next, call libpcap functions to compile the filter rule into underlying BPF rules.
    if (pcap_compile(handle, &fp, filter_rule, 0, net) == -1) {
        printf("filter rule err:[%s][%s]\n", filter_rule, pcap_geterr(handle));
        return -1;
    }

    // Then apply this rule to the opened pcap session.
    if (pcap_setfilter(handle, &fp) == -1) {
        printf("set filter failed:[%s][%s]\n", filter_rule, pcap_geterr(handle));
        return -1;
    }

    // Finally, the lengthy initialization is done, and we can start listening for packets by calling pcap_loop().
    // This function itself takes four parameters:
    // 1. The pcap session handle
    // 2. Exit the loop after capturing how many packets
    // 3. A callback function invoked after capturing each packet, with a pointer to the packet plus some other parameters
    // 4. User-defined data structure
    // As we can see, to make it possible to use libnet to resend each captured packet,
    // the libnet handle is passed to pcap_loop() here as user-defined data.
    // But there is actually no need to use such an annoying looping approach, because as long as the second parameter is negative,
    // pcap_loop() will loop indefinitely...
    while (1) {
        pcap_loop(handle, 1, got_packet, (u_char *)libnet_handler);
    }

    // Although pcap_loop() is an infinite loop, under some circumstances, e.g., after registering a signal handler, it can be interrupted.
    // Even though this program does not implement such a mechanism, cleanup should still be done properly, such as closing handles.
    // This is good programming practice.
    /* cleanup */
    pcap_freecode(&fp);
    pcap_close(handle);
    libnet_destroy(libnet_handler);
    return 0;
}
comments powered by Disqus
Published:
2015-08-31
Category:
Tag:
C7