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
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;
}