前几天项目中需要一个抓取并分析TCP包的工具,在网上倒腾了一阵子整理了一个工具,现在发布在这儿提供参考。
这个工具是使用C语言开发的一个TCP Proxy,实现TCP转发的功能并dump出来数据包的内容。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#define LOG_ERROR(...)
#define LOG_INFO(...)
#define LOG_DEBUG(...)
#define DEBUG 0
#define BUFFER_SIZE 16384
#define LINE_SIZE 32
/* parameter variable */
int listen_port = 0;
int remote_port = 0;
char * remote_host = NULL;
int foreground = 1;
/*global variable */
int listen_socket;
void help(char * program) {
printf("Usage syntax: %s -l listen_port -r remote_host -p remote_port [-f (in foreground)]\n", program);
}
void dumpBufferRaw(const unsigned char buffer[], size_t len) {
int i = 0;
int j = 0;
int k = 0;
char output[LINE_SIZE * 4 + 10] = { ' ' };
// print header
for (k = 0; k < LINE_SIZE; k++) {
printf("%02d ", k);
}
printf("\n");
for (k = 0; k < LINE_SIZE; k++) {
printf("---");
}
printf("\n");
memset(output, ' ', sizeof(output));
for (; i < len; i++) {
sprintf(output + j * 3, "%02X ", buffer[i]);
sprintf(output + LINE_SIZE * 3 + 4 + j , "%c", isprint(buffer[i]) ? buffer[i] : '.');
j++;
if (j >= LINE_SIZE) {
output[j * 3] = ' ';
printf("%s\n", output);
memset(output, ' ', sizeof(output));
j = 0;
}
}
if (j > 0) {
output[j * 3] = ' ';
printf("%s\n", output);
};
// print tailer
for (k = 0; k < LINE_SIZE; k++) {
printf("---");
}
printf("\n");
}
/* create server socket */
int server_listen() {
int optval = 1;
struct sockaddr_in listen_addr;
LOG_DEBUG("Entry of server_listen");
if ((listen_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
LOG_ERROR("Cannot create listen socket, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0) {
LOG_ERROR("Cannot set listen socket option, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
memset(&listen_addr, 0, sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(listen_port);
listen_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_socket, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) != 0) {
LOG_ERROR("Cannot bind listen socket, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
if (listen(listen_socket, 5) < 0) {
LOG_ERROR("Cannot listen socket, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
LOG_DEBUG("Server listen on port: %d", listen_port);
return 0;
}
/**
* return > 0 : success
* = 0 : socket close
* < 0 : error
*/
ssize_t forward_data(int fromsocket, int tosocket) {
unsigned char buffer[BUFFER_SIZE];
ssize_t n = recv(fromsocket, buffer, sizeof(buffer), 0);
if (n > 0) {
LOG_DEBUG("data socket: %d -> %d, size: %d", fromsocket, tosocket, n);
if (!DEBUG) {
dumpBufferRaw(buffer, n);
}
ssize_t m = send(tosocket, buffer, n, 0); // send data to output socket
if (m < 0) {
LOG_ERROR("Cannot call send, errno=[%d], errstr=[%s]", errno, strerror(errno));
return m;
}
else if (m != n) {
LOG_ERROR("Cannot call send, return %ld, expected %ld", m, n);
return -1;
}
else {
return n;
}
}
else if (n == 0) {
LOG_INFO("socket closed: %d", fromsocket);
return 0;
}
else {
LOG_ERROR("Cannot call recv, errno=[%d], errstr=[%s]", errno, strerror(errno));
return n;
}
}
void message_loop(int client_socket) {
fd_set fdsets;
int remote_socket = connect_remote(client_socket);
if (remote_socket > 0) {
LOG_INFO("Remote connection, remote socket=%d", remote_socket);
while (1) {
FD_ZERO(&fdsets);
FD_SET(client_socket, &fdsets);
FD_SET(remote_socket, &fdsets);
int ret = select(FD_SETSIZE, &fdsets, NULL, NULL, NULL);
if (ret < 0) {
LOG_ERROR("Cannot call select, errno=[%d], errstr=[%s]", errno, strerror(errno));
break;
}
else if (ret == 0) {
continue;
}
if (FD_ISSET(client_socket, &fdsets)) {
ssize_t n = forward_data(client_socket, remote_socket);
if (n <= 0) {
break;
}
}
if (FD_ISSET(remote_socket, &fdsets)) {
ssize_t n = forward_data(remote_socket, client_socket);
if (n <= 0) {
break;
}
}
}
close(remote_socket);
}
close(client_socket);
}
void server_loop() {
while (1) {
struct sockaddr client_addr;
socklen_t client_size = sizeof(client_addr);
int client_socket = accept(listen_socket, (struct sockaddr *)&client_addr, &client_size);
if (client_socket < 0) {
LOG_ERROR("Cannot accept client connection, errno=[%d], errstr=[%s]", errno, strerror(errno));
continue;
}
LOG_INFO("Connection accept, client socket=%d", client_socket);
pid_t pid = fork();
if (pid > 0) { // parent process
LOG_INFO("close client socket in parent process");
close(client_socket);
}
else if (pid == 0) { // child process
close(listen_socket);
LOG_INFO("close listen socket in child process");
message_loop(client_socket);
exit(0);
}
else if (pid < 0) {
LOG_ERROR("Cannot fork client process, errno=[%d], errstr=[%s]", errno, strerror(errno));
continue;
}
}
}
int connect_remote(int client_socket) {
struct sockaddr_in remote_addr;
struct hostent * remote_server;
int remote_socket;
if ((remote_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
LOG_ERROR("Cannot create remote socket, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
if ((remote_server = gethostbyname(remote_host)) == NULL) {
LOG_ERROR("Cannot get host by remote host name %s, errno=[%d], errstr=[%s]", remote_host, errno, strerror(errno));
return -1;
}
memset(&remote_addr, 0, sizeof(remote_addr));
remote_addr.sin_family = AF_INET;
memcpy(&remote_addr.sin_addr.s_addr, remote_server->h_addr, remote_server->h_length);
remote_addr.sin_port = htons(remote_port);
if (connect(remote_socket, (struct sockaddr *) &remote_addr, sizeof(remote_addr)) < 0) {
LOG_ERROR("Cannot connect to remote host %s:%d, errno=[%d], errstr=[%s]", remote_host, remote_port, errno, strerror(errno));
return -1;
}
return remote_socket;
}
int parse_options(int argc, char *argv[]) {
int c = 0;
while ((c = getopt(argc, argv, "l:r:p:f:h")) != -1) {
switch(c) {
case 'l':
listen_port = atoi(optarg);
break;
case 'r':
remote_host = optarg;
break;
case 'p':
remote_port = atoi(optarg);
break;
case 'f':
foreground = 1;
break;
case 'h':
help(argv[0]);
exit(0);
default:
fprintf(stderr, "ERROR: unsupported parameter %c\n", c);
return -1;
}
}
if (listen_port == 0) {
return -1;
}
LOG_INFO("Server configuration, listen on %d", listen_port);
return 0;
}
/* handle finished child process */
void sigchld_handler(int signal) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main(int argc, char *argv[]) {
if (parse_options(argc, argv) < 0) {
help(argv[0]);
return -1;
}
if (server_listen() < 0) { // start server
LOG_ERROR("Cannot start server listen on port %d", listen_port);
return -1;
}
signal(SIGCHLD, sigchld_handler); // prevent ended children from becoming zombies
if (!foreground) {
pid_t pid = fork();
if (pid > 0) { // parent
exit(0);
}
else if (pid == 0) { // // deamonized child
}
else { // error
LOG_ERROR("Cannot create listen socket, errno=[%d], errstr=[%s]", errno, strerror(errno));
return -1;
}
}
server_loop();
return 0;
}
Linux环境下编译测试通过
$ gcc main.c
$ ./a.out
Usage syntax: ./a.out-l listen_port -r remote_host -p remote_port [-f (in foreground)]
此外,如果需要支持client使用HTTP CONNECT命令指定的proxy则需要在int connect_remote(int client_socket)
里把proxy信息取出来即可。
例如:
// Retrieve proxy information from CONNECT HTTP command if
// they are not provided through command line.
if (remote_host == NULL) {
char buffer[4096];
int n = read(client_sock, buffer, 4096 - 1);
buffer[n] = '\0';
// Extract the target host and port from the CONNECT request
char method[10], host[256];
int port;
sscanf(buffer, "%s %255s", method, host);
char *port_str = strchr(host, ':');
if (port_str) {
*port_str = '\0';
port = atoi(port_str + 1);
} else {
port = 443; // Default to HTTPS
}
}
这里读出的host和port分别存储proxy的主机和端口信息,只要再把他们赋值给remote_host, remote_port即可。