Files
xfrpc/tcp_redir.c
staylightblow8 bd13b67bb6 feat: add tcp redir service for mstsc service
cause mstsc cant change its dest port, so xfrpc do it

Signed-off-by: staylightblow8 <liudf0716@gmail.com>
2023-08-05 21:36:20 +08:00

211 lines
7.5 KiB
C

/* vim: set et ts=4 sts=4 sw=4 : */
/********************************************************************\
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/** @file tcp_redir.c
@brief xfrp tcp redirect service implemented
@author Copyright (C) 2023 Dengfeng Liu <liu_df@qq.com>
*/
#include <pthread.h>
#include <arpa/inet.h>
#include "common.h"
#include "debug.h"
#include "config.h"
#include "utils.h"
#include "tcp_redir.h"
// define a struct for tcp_redir which include proxy_service and event_base
struct tcp_redir_service {
struct event_base *base;
struct proxy_service *ps;
struct sockaddr_in server_addr;
};
static struct bufferevent *current_bev = NULL; // Global variable to hold the current connection
// define a callback function for read event
static void read_cb(struct bufferevent *bev, void *arg)
{
struct bufferevent *bev_out = (struct bufferevent *)arg;
struct evbuffer *input = bufferevent_get_input(bev);
struct evbuffer *output = bufferevent_get_output(bev_out);
evbuffer_add_buffer(output, input);
}
// define a callback function for event event
static void event_cb(struct bufferevent *bev, short events, void *arg)
{
struct bufferevent *partner = (struct bufferevent *)arg;
if (events & BEV_EVENT_CONNECTED) {
debug(LOG_INFO, "connected");
} else if (events & BEV_EVENT_ERROR) {
debug(LOG_ERR, "connection error");
bufferevent_free(bev);
bufferevent_free(partner);
current_bev = NULL;
} else if (events & BEV_EVENT_EOF) {
debug(LOG_INFO, "connection closed");
bufferevent_free(bev);
bufferevent_free(partner);
current_bev = NULL;
}
}
// define a callback function for accept event
static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *address, int socklen, void *arg)
{
if (current_bev) {
// Already have a connection, reject new connection
debug(LOG_INFO, "Rejecting new connection. Only one connection allowed at a time.");
evutil_closesocket(fd);
return;
}
// the argument is the proxy_service
struct tcp_redir_service *trs = (struct tcp_redir_service *)arg;
struct event_base *base = trs->base;
// read the data from the local port
struct bufferevent *bev_in = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
if (!bev_in) {
debug(LOG_ERR, "create bufferevent for local port failed!");
evutil_closesocket(fd);
return;
}
// connect to the remote xfrpc service
struct bufferevent *bev_out = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS);
if (!bev_out) {
debug(LOG_ERR, "create bufferevent for remote xfrps service failed!");
bufferevent_free(bev_in);
return;
}
// connect to the remote port
if (bufferevent_socket_connect(bev_out, (struct sockaddr *)&(trs->server_addr), sizeof(trs->server_addr)) < 0) {
debug(LOG_ERR, "connect to remote port failed! %s", strerror(errno));
bufferevent_free(bev_in);
bufferevent_free(bev_out);
return;
}
debug(LOG_INFO, "connect to remote xfrps service [%s:%d] success!",
get_common_config()->server_addr, trs->ps->remote_port);
bufferevent_setcb(bev_in, read_cb, NULL, event_cb, (void *)bev_out);
bufferevent_setcb(bev_out, read_cb, NULL, event_cb, (void *)bev_in);
bufferevent_enable(bev_in, EV_READ|EV_WRITE);
bufferevent_enable(bev_out, EV_READ|EV_WRITE);
current_bev = bev_in;
debug(LOG_INFO, "connect to remote port success!");
return;
}
// define a thread worker function for tcp_redir
static void *tcp_redir_worker(void *arg)
{
struct proxy_service *ps = (struct proxy_service *)arg;
struct common_conf *c_conf = get_common_config();
// the worker is based on libevent and bufferevent
// it listens on the local port and forward the data to the remote port
// the local port and remote port are defined in the proxy_service
// the proxy_service as argument is passed to the worker function
// create a event_base
struct evconnlistener *listener;
struct event_base *base = event_base_new();
if (!base) {
debug(LOG_ERR, "create event base failed!");
exit(1);
}
// define listen address and port
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(ps->local_port);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
struct tcp_redir_service trs;
trs.base = base;
trs.ps = ps;
trs.server_addr.sin_family = AF_INET;
trs.server_addr.sin_port = htons(ps->remote_port);
// if c_conf->server_addr is ip address, use inet_addr to convert it
// if c_conf->server_addr is domain name, use gethostbyname to convert it
if (is_valid_ip_address(c_conf->server_addr))
trs.server_addr.sin_addr.s_addr = inet_addr(c_conf->server_addr);
else {
struct hostent *host = gethostbyname(c_conf->server_addr);
if (!host) {
debug(LOG_ERR, "gethostbyname failed!");
exit(1);
}
// only support ipv4
if (host->h_addrtype != AF_INET) {
debug(LOG_ERR, "only support ipv4!");
exit(1);
}
trs.server_addr.sin_addr.s_addr = *(unsigned long *)host->h_addr_list[0];
}
// create a listener
listener = evconnlistener_new_bind(base, accept_cb, (void *)&trs,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, -1, (struct sockaddr *)&sin, sizeof(sin));
if (!listener) {
debug(LOG_ERR, "create listener failed!");
exit(1);
}
// start the event loop
event_base_dispatch(base);
// free the listener
evconnlistener_free(listener);
// free the event base
event_base_free(base);
return NULL;
}
void start_tcp_redir_service(struct proxy_service *ps)
{
// create a thread
pthread_t tid;
if (pthread_create(&tid, NULL, tcp_redir_worker, (void *)ps) != 0) {
debug(LOG_ERR, "create tcp_redir worker thread failed!");
exit(1);
}
debug(LOG_INFO, "create tcp_redir worker thread success!");
// detach the thread
if (pthread_detach(tid) != 0) {
debug(LOG_ERR, "detach tcp_redir worker thread failed!");
exit(1);
}
debug(LOG_INFO, "detach tcp_redir worker thread success!");
return;
}