cause mstsc cant change its dest port, so xfrpc do it Signed-off-by: staylightblow8 <liudf0716@gmail.com>
211 lines
7.5 KiB
C
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;
|
|
} |