端口转发工具关于 epoll 框架使用
本文最后更新于 2023年3月24日 中午
端口映射也叫做端口转发,或者叫做虚拟服务器
epoll API接口
文档:https://man7.org/linux/man-pages/man7/epoll.7.html
操作流程:
1、使用epoll_create()
2、调用epoll_ctl()注册fd的interest ,这会将项目添加 interest list
#include <sys/epoll.h>
int epoll_ctl(int epfd , int op , int fd , struct epoll_event *_Nullable event );
3、调用epoll_wait(),等待I/O 事件,如果当前没有可用的事件,则阻塞调用线程。(这个系统调用可以被认为是从epoll实例的就绪列表中获取项目。)水平触发和边缘触发
4、处理事件
示例:
在此示例中,侦听器是已调用listen(2)的非阻塞套接字。函数do_use_fd()使用新的就绪文件描述符,直到
read(2)或write(2)返回EAGAIN为止。一个事件-
驱动状态机应用程序应该在收到EAGAIN,记录其当前状态,以便在下次调用do_use_fd()时它将继续从其位置读取(2)或写入(2)之前就停了。
#define MAX_EVENTS 10
struct epoll_event ev, events[MAX_EVENTS];
int listen_sock, conn_sock, nfds, epollfd;
/* Code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted. */
epollfd = epoll_create1(0);
if (epollfd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
perror("epoll_ctl: listen_sock");
exit(EXIT_FAILURE);
}
for (;;) {
nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
conn_sock = accept(listen_sock,
(struct sockaddr *) &addr, &addrlen);
if (conn_sock == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock,
&ev) == -1) {
perror("epoll_ctl: conn_sock");
exit(EXIT_FAILURE);
}
} else {
do_use_fd(events[n].data.fd);
}
}
}
拉了N级运营商的宽带 (多级路由),没有外网ip,人在外面的时候(攻击者)无法访问家里的电脑(内网)
在外网机器运行:
tunnel -s port1[给内网机器连接的端口] prot2[给客户端连接的端口,
比如secureCRT 这种ssh客户端] 如:tunnel -s 2222 3333
在内网机器上运行:
tunnel -c port1[要连接的本机服务端口,比如家里的ssh服务22端口] prot2[外网机器的监听的端口]
如:tunnel -c 22 2222
将22端口转发出来了,给了外网机器
优化,中间的2222端口可以隐藏起来,以提高用户体验
任意机器连接外网的3333端口,发送的数据都将转发给内网机器22端口上(如secureCRT连接 外网ip:3333)
往往这个服务端机器也作为客户端使用了。
示例源代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <time.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#define MAX_EVENTS 100
#define MAX_BUFFER_SIZE 65535
struct my_tcp_info
{
uint8_t tcpi_state;
uint8_t tcpi_ca_state;
uint8_t tcpi_retransmits;
uint8_t tcpi_probes;
uint8_t tcpi_backoff;
uint8_t tcpi_options;
uint8_t tcpi_snd_wscale : 4, tcpi_rcv_wscale : 4;
uint32_t tcpi_rto;
uint32_t tcpi_ato;
uint32_t tcpi_snd_mss;
uint32_t tcpi_rcv_mss;
uint32_t tcpi_unacked;
uint32_t tcpi_sacked;
uint32_t tcpi_lost;
uint32_t tcpi_retrans;
uint32_t tcpi_fackets;
/* Times. */
uint32_t tcpi_last_data_sent;
uint32_t tcpi_last_ack_sent; /* Not remembered, sorry. */
uint32_t tcpi_last_data_recv;
uint32_t tcpi_last_ack_recv;
/* Metrics. */
uint32_t tcpi_pmtu;
uint32_t tcpi_rcv_ssthresh;
uint32_t tcpi_rtt;
uint32_t tcpi_rttvar;
uint32_t tcpi_snd_ssthresh;
uint32_t tcpi_snd_cwnd;
uint32_t tcpi_advmss;
uint32_t tcpi_reordering;
uint32_t tcpi_rcv_rtt;
uint32_t tcpi_rcv_space;
uint32_t tcpi_total_retrans;
};
struct epoll_event *events = NULL;
int epoll_fd = -1,listen_fd,trigger_count;
char *real_server_name;
unsigned short real_server_port;
void TimePrinter()
{
time_t t = time(NULL);
struct tm* lt = localtime(&t);
fflush(stdout);
printf("[ %d-%d-%d %d:%d:%d ] ", lt->tm_year + 1900, lt->tm_mon + 1, lt->tm_mday, lt->tm_hour, lt->tm_min,
lt->tm_sec);
}
int setup_socket_server(unsigned short port)
{
struct sockaddr_in addr;
socklen_t addr_length;
int fd, opt, opt_length;
if((fd = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, 0)) < 0)
return -1;
opt = 1;
opt_length = sizeof(opt);
if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, opt_length) == -1)
return -1;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr_length = sizeof(addr);
if(bind(fd, (struct sockaddr *)&addr, addr_length) < 0)
return -1;
if(listen(fd, 3) < 0)
return -1;
return fd;
}
int setup_epoll(int size, struct epoll_event **events)
{
int fd;
if((*events = (struct epoll_event*)malloc(sizeof(struct epoll_event) * size)) == NULL)
return -1;
if((fd = epoll_create(size)) == -1)
free(*events);
return fd;
}
int add_event(int epoll_fd, int flags, int socket_fd)
{
struct epoll_event event;
event.data.fd = socket_fd;
event.events = flags;
return epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event);
}
int delete_event(int epoll_fd, int socket_fd)
{
return epoll_ctl(epoll_fd, EPOLL_CTL_DEL, socket_fd, NULL);
}
int create_socket(char *host, int port)
{
int fd;
int flag = 1;
struct sockaddr_in server_info;
if((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
return -1;
server_info.sin_family = AF_INET;
server_info.sin_port = htons(port);
server_info.sin_addr.s_addr = inet_addr(host);
if(connect(fd, (struct sockaddr *)&server_info, sizeof(server_info)) == -1)
return -1;
return fd;
}
int is_server_alive(int confd)
{
struct my_tcp_info info;
int len=sizeof(info);
getsockopt(confd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len);
if((info.tcpi_state == TCP_ESTABLISHED))
{
return 0;
}
else{return -1;}
}
void epoll_lt(int sockfd,int sendfd)
{
char buffer[MAX_BUFFER_SIZE] ;
int ret;
memset(buffer, 0, MAX_BUFFER_SIZE);
ret = recv(sockfd, buffer, MAX_BUFFER_SIZE, 0);
char s[ret];
memset(s,0,ret);
if (ret > 0){
TimePrinter();
memcpy(s,buffer,ret-1);
printf("Receve msg\"%s\"from client.\t Massage size:%d bytes\n",s, ret-1);
if(is_server_alive(sendfd) == 0)
{
send(sendfd,buffer,MAX_BUFFER_SIZE,0);
}
}
else
{
if (ret == 0)
TimePrinter();
printf("client close!!!\n");
close(sockfd);
delete_event(epoll_fd,sockfd);
}
}
void epoll_lt1(int sockfd)
{
int ret;
char buffer[MAX_BUFFER_SIZE] ;
memset(buffer,0,MAX_BUFFER_SIZE);
ret = recv(sockfd,buffer,MAX_BUFFER_SIZE,0);
char s[ret];
memset(s,0,ret);
if (ret > 0) {
memcpy(s,buffer,ret-1);
TimePrinter();
printf("Receve msg\"%s\"from server.\t Massage size:%d bytes\n", s, ret - 1);
for(int j = 0;j<trigger_count;j++) {
if ((events[j].events & EPOLLOUT) && (events[j].data.fd != sockfd)&& (events[j].data.fd != listen_fd)) {
send(events[j].data.fd, buffer, MAX_BUFFER_SIZE, 0);
}
}
} else {
if (ret == 0)
TimePrinter();
printf("server close!!!\n");
close(sockfd);
}
}
int main(int argc, char *argv[])
{
struct sockaddr_in addr;
socklen_t addr_length;
int i, client_fd,user_fd;
unsigned short port;
if(argc != 6)
{
fprintf(stdout, "usage: pfwd -l [listen port] -c [forward host] [forward port]\n");
fprintf(stdout, "\tlisten port: The port pfwd sould listen on.\n");
fprintf(stdout, "\tforward host: The forwarding server ip.\n");
fprintf(stdout, "\tforward port: The forwarding server port.\n\n");
exit(1);
}
for( i = 1;i<argc;) {
if(!strcmp(argv[i],"-c"))
{
real_server_name = argv[i+1];
real_server_port = (unsigned short)atoi(argv[i+2]);
i = i+3;
}
else if(!strcmp(argv[i],"-l"))
{
port = (unsigned short)atoi(argv[i + 1]);
i =i+2;
}
}
if((listen_fd = setup_socket_server(port)) == -1)
fprintf(stderr,"Unable to setup socket.");
if((epoll_fd = setup_epoll(MAX_EVENTS, &events)) == -1)
fprintf(stderr,"Unable to setup Epoll events.");
if(add_event(epoll_fd, EPOLLIN, listen_fd) == -1)
fprintf(stderr,"Unable to add listening socket to Epoll event queue.");
fprintf(stdout, "Resquests in port %d will be forwarded to %s:%d...\n", port, real_server_name, real_server_port);
addr_length = sizeof(addr);
user_fd =-1;
while(1)
{
if(is_server_alive(user_fd) == -1)
{
close(user_fd);
user_fd = create_socket(real_server_name, real_server_port);
add_event(epoll_fd,EPOLLIN,user_fd);
}
if((trigger_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1)) < 0)
fprintf(stderr,"Unable to Epoll wait.");
for(i = 0; i < trigger_count; i++)
{
client_fd = events[i].data.fd;
if(events[i].events & (EPOLLHUP | EPOLLERR))
{
fprintf(stderr, "Client disconnected (socket %d).\n", client_fd);
delete_event(epoll_fd, client_fd);
break;
}
else if(client_fd == listen_fd)
{
if((client_fd= accept(listen_fd, (struct sockaddr *)&addr, &addr_length)) == -1)
{
fprintf(stderr, "Unable to accept new client connection.\n");
continue;
}
if(add_event(epoll_fd, EPOLLIN|EPOLLOUT, client_fd) == -1)
{
fprintf(stderr, "Unable to add soecket to Epoll event queue, closing (socket %d).\n", client_fd);
close(client_fd);
continue;
}
TimePrinter();
fprintf(stdout, "New client connection from %s (socket %d).\n", inet_ntoa(addr.sin_addr), client_fd);
}
else if ((events[i].events & EPOLLIN))
{
if(is_server_alive(user_fd) == 0){
if (client_fd == user_fd)
{
epoll_lt1(client_fd);
}
else{
epoll_lt(client_fd,user_fd);
}
}
}
}
}
exit(0);
}
端口转发攻击场景使用:
建立反向连接:
有一台内网机器(被攻陷,具有双网卡,跳板机,类似路由器等)
一台攻击机(本地机)(同某一个网段的机器)
攻击机作为服务端监听 8888 ;
跳板机运行客户端连接服务器 host:8888(攻击机);
端口转发的两个应用:本地端口转发和远程端口转发