| /******************************************************************************* |
| * Copyright (c) 2016 Wind River Systems, Inc. and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * and Eclipse Distribution License v1.0 which accompany this distribution. |
| * The Eclipse Public License is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * and the Eclipse Distribution License is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * You may elect to redistribute this code under either of these licenses. |
| * |
| * Contributors: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| |
| /* |
| * This module implements PortForward service. |
| * The service allows a client to open a port and read/write to/from |
| * this port using a stream. |
| * The type of ports supported is OS specific but currently UDP, TCP and |
| * serial ports are supported. |
| */ |
| #include <tcf/config.h> |
| #include <tcf/framework/mdep-inet.h> |
| #include <tcf/framework/mdep-threads.h> |
| #include <assert.h> |
| #include <errno.h> |
| #include <tcf/framework/myalloc.h> |
| #include <tcf/framework/context.h> |
| #include <tcf/services/portforward_service.h> |
| #include <tcf/framework/errors.h> |
| |
| #include <tcf/framework/json.h> |
| #include <tcf/framework/trace.h> |
| #include <tcf/framework/exceptions.h> |
| #include <tcf/framework/asyncreq.h> |
| #include <tcf/services/streamsservice.h> |
| |
| #if SERVICE_PortForward |
| |
| #if defined(_WRS_KERNEL) |
| #define ENABLE_PortForward_Serial 0 |
| #define ENABLE_PortForward_UDP 0 |
| #elif defined(WIN32) |
| #define ENABLE_PortForward_Serial 1 |
| #define ENABLE_PortForward_UDP 0 |
| #elif defined(__linux__) |
| #define ENABLE_PortForward_Serial 1 |
| #define ENABLE_PortForward_UDP 1 |
| #else |
| #define ENABLE_PortForward_Serial 0 |
| #define ENABLE_PortForward_UDP 0 |
| #endif |
| |
| #ifdef WIN32 |
| #ifndef ECONNREFUSED |
| #define ECONNREFUSED WSAECONNREFUSED |
| #endif |
| #endif |
| |
| #if ENABLE_PortForward_Serial |
| #if !defined(WIN32) |
| #include <fcntl.h> |
| #include <ctype.h> |
| #if defined(__linux__) |
| #include <linux/serial.h> |
| #endif |
| #include <termios.h> |
| #endif |
| #endif |
| |
| |
| #define PORTFW_TCP_PORT 0 |
| #define PORTFW_UDP_PORT 1 |
| #define PORTFW_SER_PORT 2 |
| |
| #define IN_BUF_SIZE 32*1024 |
| #define OUT_BUF_SIZE 32*1024 |
| |
| #define PORTFW_SERIAL_FC_NONE 0 |
| #define PORTFW_SERIAL_FC_XON 1 |
| #define PORTFW_SERIAL_FC_RTS 2 |
| #define PORTFW_SERIAL_FC_DSR 3 |
| |
| #define PORTFW_SERIAL_PARITY_NONE 0 |
| #define PORTFW_SERIAL_PARITY_ODD 1 |
| #define PORTFW_SERIAL_PARITY_EVEN 2 |
| #define PORTFW_SERIAL_PARITY_MARK 3 |
| #define PORTFW_SERIAL_PARITY_SPACE 4 |
| |
| #if ENABLE_PortForward_Serial |
| typedef struct BaudConst { |
| int baud_const; |
| int baud_rate; |
| } BaudConst; |
| |
| typedef struct DataBitConst |
| { |
| int data_bits_const; |
| int data_bits; |
| } DataBitConst; |
| |
| #ifdef WIN32 |
| static BaudConst baud_table[] = |
| { { CBR_110, 110 }, { CBR_300, 300 }, { CBR_600, 600 }, { CBR_1200, 1200 }, |
| { CBR_2400, 2400 }, { CBR_4800, 4800 }, |
| { CBR_9600, 9600 }, { CBR_14400, 14400}, |
| { CBR_19200, 19200 }, { CBR_38400, 38400 }, |
| { CBR_57600, 57600 }, { CBR_115200, 115200 }, |
| { CBR_128000, 128000 }, { CBR_256000, 256000 }, |
| { 0, 0 } |
| }; |
| #else /* WIN32 */ |
| static BaudConst baud_table[] = |
| { { B150, 150 }, { B300, 300 }, { B600, 600 }, { B1200, 1200 }, |
| { B1800, 1800 }, { B2400, 2400 }, { B4800, 4800 }, |
| { B9600, 9600 }, { B19200, 19200 }, { B38400, 38400 }, |
| #ifdef B57600 |
| { B57600, 57600 }, |
| #endif |
| #ifdef B115200 |
| { B115200, 115200 }, |
| #endif |
| #ifdef B230400 |
| { B230400, 230400, }, |
| #endif |
| #ifdef B46080 |
| { B460800, 460800 }, |
| #endif |
| { 0, 0 } |
| }; |
| |
| static DataBitConst data_bit_table[] = |
| { {CS8, 8}, {CS7, 7}, {CS6, 6}, {CS5, 5}, {0,0} }; |
| #endif /* WIN32 */ |
| #endif /* ENABLE_PortForward_Serial */ |
| |
| typedef struct PortFwConfig PortFwConfig; |
| typedef void (*ConnectCallBack)(PortFwConfig * /* config */, int /* error */); |
| typedef int (*UserSendReqFunc)(PortFwConfig * /* config */, void * /* buffer */, size_t /* length */); |
| typedef int (*UserRecvReqFunc)(PortFwConfig * /* config */, void * /* buffer */, size_t /* length */); |
| typedef void (*UserCloseFunc)(PortFwConfig * /* config */); |
| |
| typedef struct PortConnectInfo { |
| AsyncReqInfo cnct_req; |
| ConnectCallBack cnct_callback; |
| |
| /* UDP/TCP specific parameters */ |
| struct sockaddr * addr_buf; |
| int addr_len; |
| int sock; |
| |
| /* Non network parameters */ |
| UserSendReqFunc send_req_func; |
| UserRecvReqFunc recv_req_func; |
| UserCloseFunc close_func; |
| |
| #if ENABLE_PortForward_Serial |
| /* Serial line specific parameters */ |
| |
| /* Syntax of the param string for serial line is: |
| * <baud rate>[-<data bits>[-<parity>[-<stop bits>[-<flow control>]]]] |
| * eg: 9600-8-N-1-N or 9600-8-N-1 or 9600 |
| */ |
| |
| struct { |
| /* Serial line specific parameters */ |
| int stop_bits; /* number of stop bits (1 or 2), 1 is default */ |
| int data_bits; /* number of data bits (between 5 & 8), 8 is default */ |
| int parity; /* parity, no parity is the default */ |
| int flow_ctrl; /* flow control; no flow control is the dfault */ |
| int baud_rate; /* baud rate, default to 115200 */ |
| #ifdef WIN32 |
| HANDLE handle; /* serial device handle */ |
| OVERLAPPED write_overlapped; /* overlap struct for write ops */ |
| OVERLAPPED read_overlapped; /* overlap struct for read ops */ |
| COMMTIMEOUTS saved_timeout; /* saved serial line device timeout */ |
| DCB saved_dcb; /* saved serial line configuration */ |
| #else |
| int fd; |
| #endif |
| } serial; |
| #endif |
| } PortConnectInfo; |
| |
| struct PortFwConfig { |
| LINK link; |
| char id[256]; |
| Channel * channel; |
| char token[256]; |
| int verbose; |
| int port_type; /* port type */ |
| |
| char * port_config; /* port config string (tcp:<port number>, udp:<port number>, serial:<device>) */ |
| char * port_params; /* port parameters */ |
| char * client_data; /* client data */ |
| |
| int connected; |
| VirtualStream * in_vstream; |
| VirtualStream * out_vstream; |
| char outbuf[OUT_BUF_SIZE]; |
| size_t outbuf_len; |
| char inbuf[IN_BUF_SIZE]; |
| size_t inbuf_pos; |
| size_t inbuf_len; |
| AsyncReqInfo send_req; |
| int send_in_progress; |
| AsyncReqInfo recv_req; |
| int recv_in_progress; |
| char in_stream_id[256]; |
| char out_stream_id[256]; |
| int shutdown_in_progress; |
| PortConnectInfo port_info; |
| }; |
| |
| #define link2pfwp(A) ((PortFwConfig *)((char *)(A) - offsetof(PortFwConfig, link))) |
| |
| static const char * PortForward = "PortForward"; |
| |
| static LINK portfw_config_list = TCF_LIST_INIT(portfw_config_list); |
| static const char * portfw_inv_config_err = "Unable to find specified PortForward Remote configuration"; |
| |
| static TCFBroadcastGroup * broadcast_group = NULL; |
| |
| static PortFwConfig * find_portfw_config(const char * name); |
| static void write_portfw_config(OutputStream * out, PortFwConfig * config); |
| static void write_port_to_stream(void * arg); |
| static int log_portfw; |
| |
| static void set_socket_options(int sock) { |
| int snd_buf = OUT_BUF_SIZE; |
| int rcv_buf = IN_BUF_SIZE; |
| struct linger optval; |
| int i = 1; |
| |
| /* |
| * set SO_LINGER & SO_REUSEADDR socket options so that it closes the |
| * connections gracefully, when required to close. |
| */ |
| |
| optval.l_onoff = 1; |
| optval.l_linger = 0; |
| if (setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *) &optval, |
| sizeof(optval)) != 0) { |
| trace(LOG_ALWAYS, "Unable to set SO_LINGER socket option: %s", |
| errno_to_str(errno)); |
| }; |
| |
| #if !(defined(_WIN32) || defined(__CYGWIN__)) |
| { |
| const int i = 1; |
| if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &i, sizeof(i)) |
| < 0) { |
| trace(LOG_ALWAYS, "Unable to set SO_REUSEADDR socket option: ", |
| errno_to_str(errno)); |
| } |
| } |
| #endif |
| |
| /* Set TCP_NODELAY socket option to optimize communication */ |
| |
| i = 1; |
| if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &i, sizeof(i)) |
| < 0) { |
| trace(LOG_ALWAYS, "Can't set TCP_NODELAY option on a socket: %s", |
| errno_to_str(errno)); |
| } |
| |
| if (setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (char *) &i, sizeof(i)) |
| < 0) { |
| trace(LOG_ALWAYS, "Can't set SO_KEEPALIVE option on a socket: %s", |
| errno_to_str(errno)); |
| } |
| |
| if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *) &snd_buf, |
| sizeof(snd_buf)) < 0) { |
| trace(LOG_ALWAYS, "setsockopt(SOL_SOCKET,SO_SNDBUF,%d) error: %s", |
| snd_buf, errno_to_str(errno)); |
| } |
| if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *) &rcv_buf, |
| sizeof(rcv_buf)) < 0) { |
| trace(LOG_ALWAYS, "setsockopt(SOL_SOCKET,SO_RCVBUF,%d) error: %s", |
| rcv_buf, errno_to_str(errno)); |
| } |
| } |
| |
| static void tcp_connect_done(void * args) { |
| PortFwConfig * config = (PortFwConfig *)((AsyncReqInfo *)args)->client_data; |
| PortConnectInfo * info = &config->port_info; |
| |
| loc_free(info->cnct_req.u.con.addr); |
| if (info->cnct_req.error) { |
| info->cnct_callback(config, info->cnct_req.error); |
| } |
| else { |
| config->connected = 1; |
| set_socket_options(info->sock); |
| info->cnct_callback(config, 0); |
| } |
| } |
| |
| static int tcp_connect(PortFwConfig * config, ConnectCallBack callback) { |
| int error = 0; |
| struct addrinfo hints; |
| struct addrinfo * reslist = NULL; |
| char * dev_string = loc_strdup(config->port_config); |
| struct sockaddr * addr_buf = NULL; |
| int addr_len; |
| int sock; |
| char * host_str; |
| char * port_str; |
| |
| host_str = strchr(dev_string, ':'); |
| if (host_str == NULL) { |
| error = EINVAL; |
| } |
| else { |
| host_str++; |
| port_str = strchr(host_str, ':'); |
| if (port_str == NULL) { |
| error = EINVAL; |
| } |
| } |
| if (!error) { |
| port_str[0] = '\0'; |
| port_str++; |
| if (strlen(host_str) == 0) host_str = NULL; |
| } |
| if (!error) { |
| memset(&hints, 0, sizeof hints); |
| |
| hints.ai_family = PF_INET; |
| hints.ai_socktype = SOCK_STREAM; |
| hints.ai_protocol = IPPROTO_TCP; |
| error = loc_getaddrinfo(host_str, port_str, &hints, &reslist); |
| if (error) error = set_gai_errno(error); |
| if (!error) { |
| struct addrinfo * res; |
| for (res = reslist; res != NULL; res = res->ai_next) { |
| addr_len = res->ai_addrlen; |
| addr_buf = (struct sockaddr *)loc_alloc(res->ai_addrlen); |
| memcpy(addr_buf, res->ai_addr, res->ai_addrlen); |
| sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
| if (sock < 0) { |
| error = errno; |
| } |
| else { |
| error = 0; |
| break; |
| } |
| } |
| loc_freeaddrinfo(reslist); |
| } |
| } |
| if (!error && addr_buf == NULL) error = ENOENT; |
| if (error) { |
| callback(config, error); |
| } |
| else { |
| PortConnectInfo * info = &config->port_info; |
| info->addr_buf = addr_buf; |
| info->addr_len = addr_len; |
| info->sock = sock; |
| |
| info->cnct_callback = callback; |
| info->cnct_req.client_data = config; |
| info->cnct_req.done = tcp_connect_done; |
| info->cnct_req.type = AsyncReqConnect; |
| info->cnct_req.u.con.sock = info->sock; |
| info->cnct_req.u.con.addr = info->addr_buf; |
| info->cnct_req.u.con.addrlen = info->addr_len; |
| async_req_post(&info->cnct_req); |
| } |
| loc_free(dev_string); |
| return 0; |
| } |
| |
| #if ENABLE_PortForward_UDP |
| /* UDP PORT SUPPORT */ |
| static void udp_connect_done(void * args) { |
| PortFwConfig * config = (PortFwConfig *)((AsyncReqInfo *)args)->client_data; |
| PortConnectInfo * info = &config->port_info; |
| |
| loc_free(info->cnct_req.u.con.addr); |
| if (info->cnct_req.error) { |
| info->cnct_callback(config, info->cnct_req.error); |
| } |
| else { |
| config->connected = 1; |
| info->cnct_callback(config, 0); |
| } |
| } |
| |
| static int udp_connect(PortFwConfig * config, ConnectCallBack callback) { |
| int error = 0; |
| struct addrinfo hints; |
| struct addrinfo * reslist = NULL; |
| char * dev_string = loc_strdup(config->port_config); |
| struct sockaddr * addr_buf = NULL; |
| int addr_len; |
| int sock; |
| char * host_str; |
| char * port_str; |
| |
| host_str = strchr(dev_string, ':'); |
| if (host_str == NULL) { |
| error = EINVAL; |
| } |
| else { |
| host_str++; |
| port_str = strchr(host_str, ':'); |
| if (port_str == NULL) { |
| error = EINVAL; |
| } |
| } |
| if (!error) { |
| port_str[0] = '\0'; |
| port_str++; |
| if (strlen(host_str) == 0) host_str = NULL; |
| } |
| if (!error) { |
| memset(&hints, 0, sizeof hints); |
| |
| hints.ai_family = PF_INET; |
| hints.ai_socktype = SOCK_DGRAM; |
| hints.ai_protocol = IPPROTO_UDP; |
| error = loc_getaddrinfo(host_str, port_str, &hints, &reslist); |
| if (error) error = set_gai_errno(error); |
| if (!error) { |
| struct addrinfo * res; |
| for (res = reslist; res != NULL; res = res->ai_next) { |
| addr_len = res->ai_addrlen; |
| addr_buf = (struct sockaddr *)loc_alloc(res->ai_addrlen); |
| memcpy(addr_buf, res->ai_addr, res->ai_addrlen); |
| sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); |
| if (sock < 0) { |
| error = errno; |
| } |
| else { |
| error = 0; |
| break; |
| } |
| } |
| loc_freeaddrinfo(reslist); |
| } |
| } |
| if (!error && addr_buf == NULL) error = ENOENT; |
| if (error) { |
| callback(config, error); |
| } |
| else { |
| PortConnectInfo * info = &config->port_info; |
| info->addr_buf = addr_buf; |
| info->addr_len = addr_len; |
| info->sock = sock; |
| |
| info->cnct_callback = callback; |
| info->cnct_req.client_data = config; |
| info->cnct_req.done = udp_connect_done; |
| info->cnct_req.type = AsyncReqConnect; |
| info->cnct_req.u.con.sock = info->sock; |
| info->cnct_req.u.con.addr = info->addr_buf; |
| info->cnct_req.u.con.addrlen = info->addr_len; |
| async_req_post(&info->cnct_req); |
| } |
| loc_free(dev_string); |
| return 0; |
| } |
| #endif |
| |
| static void disconnect_port(PortFwConfig * config) { |
| if (config->port_info.sock != -1) { |
| /* It seems we need to use shutdown to unblock threads blocked on recv/send */ |
| if (config->connected) shutdown(config->port_info.sock, SHUT_RDWR); |
| if (closesocket(config->port_info.sock) == -1) perror ("closesocket"); |
| config->port_info.sock = -1; |
| if (config->verbose) fprintf(stderr, "Connection with %s closed\n", config->port_config); |
| } |
| else if (config->port_info.close_func) { |
| config->port_info.close_func(config); |
| } |
| config->connected = 0; |
| } |
| #if ENABLE_PortForward_Serial |
| /* Serial line support */ |
| static int serial_parse_params(PortFwConfig * config) { |
| char * ptr; |
| char * s; |
| char * param = NULL; |
| |
| config->port_info.serial.baud_rate = 115200; |
| config->port_info.serial.stop_bits = 1; |
| config->port_info.serial.data_bits = 8; |
| config->port_info.serial.parity = PORTFW_SERIAL_PARITY_NONE; |
| config->port_info.serial.flow_ctrl = PORTFW_SERIAL_FC_NONE; |
| |
| if (config->port_params == NULL) return 0; |
| s = config->port_params; |
| |
| /* BAUD RATE */ |
| while (*s && *s != '-') s++; |
| if (s != config->port_params) { |
| param = loc_strndup(config->port_params, s - config->port_params); |
| config->port_info.serial.baud_rate = atoi(param); |
| loc_free(param); |
| } |
| |
| /* DATA BITS */ |
| if (*s) s++; |
| ptr = s; |
| while (*s && *s != '-') s++; |
| if (s != ptr) { |
| param = loc_strndup(ptr, s - ptr); |
| config->port_info.serial.data_bits = atoi(param); |
| loc_free(param); |
| } |
| |
| |
| /* PARITY */ |
| if (*s) s++; |
| ptr = s; |
| while (*s && *s != '-') s++; |
| if (s != ptr) { |
| param = loc_strndup(ptr, s - ptr); |
| if (strlen(param) != 1) { |
| errno = EINVAL; |
| loc_free(param); |
| return -1; |
| } |
| if (param[0] == 'O' || param[0] == 'o') config->port_info.serial.parity = PORTFW_SERIAL_PARITY_ODD; |
| else if (param[0] == 'E' || param[0] == 'e') config->port_info.serial.parity = PORTFW_SERIAL_PARITY_EVEN; |
| else if (param[0] == 'N' || param[0] == 'n') config->port_info.serial.parity = PORTFW_SERIAL_PARITY_NONE; |
| else if (param[0] == 'S' || param[0] == 's') config->port_info.serial.parity = PORTFW_SERIAL_PARITY_SPACE; |
| else if (param[0] == 'M' || param[0] == 'm') config->port_info.serial.parity = PORTFW_SERIAL_PARITY_MARK; |
| else { |
| errno = EINVAL; |
| loc_free(param); |
| return -1; |
| } |
| loc_free(param); |
| } |
| |
| /* STOP BITS */ |
| if (*s) s++; |
| ptr = s; |
| while (*s && *s != '-') s++; |
| if (s != ptr) { |
| param = loc_strndup(ptr, s - ptr); |
| config->port_info.serial.stop_bits = atoi(param); |
| loc_free(param); |
| } |
| |
| |
| /* FLOW CONTROL */ |
| if (*s) s++; |
| ptr = s; |
| while (*s && *s != '-') s++; |
| if (s != ptr) { |
| param = loc_strndup(ptr, s - ptr); |
| if (strlen(param) != 1) { |
| errno = EINVAL; |
| loc_free(param); |
| return -1; |
| } |
| if (param[0] == 'N' || param[0] == 'n') config->port_info.serial.flow_ctrl = PORTFW_SERIAL_FC_NONE; |
| else if (param[0] == 'S' || param[0] == 's') config->port_info.serial.flow_ctrl = PORTFW_SERIAL_FC_XON; |
| else if (param[0] == 'R' || param[0] == 'r') config->port_info.serial.flow_ctrl = PORTFW_SERIAL_FC_RTS; |
| else if (param[0] == 'D' || param[0] == 'd') config->port_info.serial.flow_ctrl = PORTFW_SERIAL_FC_DSR; |
| else { |
| errno = EINVAL; |
| loc_free(param); |
| return -1; |
| } |
| loc_free(param); |
| } |
| |
| if (config->port_info.serial.data_bits < 5 || config->port_info.serial.data_bits > 8) { |
| errno = EINVAL; |
| return -1; |
| } |
| if (config->port_info.serial.stop_bits < 1 || config->port_info.serial.stop_bits > 2) { |
| errno = EINVAL; |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void serial_connect_done(void * args) { |
| PortFwConfig * config = (PortFwConfig *)((AsyncReqInfo *)args)->client_data; |
| PortConnectInfo * info = &config->port_info; |
| |
| if (info->cnct_req.error) { |
| info->cnct_callback(config, info->cnct_req.error); |
| } |
| else { |
| config->connected = 1; |
| info->cnct_callback(config, 0); |
| } |
| } |
| |
| static int serial_connect_req_func(void * reqdata) { |
| PortFwConfig * config = (PortFwConfig *)reqdata; |
| PortConnectInfo * info = &config->port_info; |
| char * dev_name = config->port_config + strlen("serial:"); |
| int ix; |
| int baud_const = 0; |
| #ifdef WIN32 |
| DCB dcb = {0}; |
| COMMTIMEOUTS timeouts; |
| HANDLE handle; |
| char com_dev_name[128]; |
| #endif |
| trace(log_portfw, "Try to open serial device %s", dev_name); |
| |
| for (ix = 0; baud_table[ix].baud_rate != 0; ix++) { |
| if (baud_table[ix].baud_rate == info->serial.baud_rate) |
| baud_const = baud_table[ix].baud_const; |
| } |
| #ifdef WIN32 |
| if (baud_const == 0) baud_const = info->serial.baud_rate; |
| #else |
| if (baud_const == 0) { |
| trace(log_portfw, "ERROR: Invalid baud rate"); |
| errno = EINVAL; |
| return -1; |
| } |
| #endif |
| |
| #ifdef WIN32 |
| |
| snprintf(com_dev_name, sizeof(com_dev_name), "\\\\.\\%s", dev_name); |
| handle = CreateFile (com_dev_name, |
| GENERIC_READ | GENERIC_WRITE, |
| 0, |
| 0, |
| OPEN_EXISTING, |
| FILE_FLAG_OVERLAPPED, |
| (void *)NULL); |
| |
| if (handle == INVALID_HANDLE_VALUE) { |
| set_win32_errno(GetLastError()); |
| trace(log_portfw, "ERROR: Unable to open open serial device %s: %d", dev_name, GetLastError()); |
| return -1; |
| } |
| |
| info->serial.read_overlapped.hEvent = CreateEvent ( |
| NULL, /* security attributes */ |
| 1, /* manual reset (required by GetOverlappedResult()) */ |
| 0, /* non signalled initial state */ |
| NULL /* event name */); |
| |
| if (info->serial.read_overlapped.hEvent == NULL) { |
| set_win32_errno(GetLastError()); |
| CloseHandle (handle); |
| return -1; |
| } |
| |
| info->serial.write_overlapped.hEvent = CreateEvent ( |
| NULL, /* security attributes */ |
| 1, /* manual reset (required by GetOverlappedResult()) */ |
| 0, /* non signalled initial state */ |
| NULL /* event name */); |
| |
| if (info->serial.write_overlapped.hEvent == NULL) { |
| set_win32_errno(GetLastError()); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (handle); |
| return -1; |
| } |
| |
| info->serial.saved_dcb.DCBlength= sizeof(DCB); |
| if (!GetCommState(handle, &info->serial.saved_dcb)) |
| { |
| set_win32_errno(GetLastError()); |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| return -1; |
| } |
| dcb = info->serial.saved_dcb; |
| dcb.BaudRate = baud_const; |
| dcb.ByteSize = info->serial.data_bits; |
| |
| if (info->serial.parity == PORTFW_SERIAL_PARITY_ODD) dcb.Parity = ODDPARITY; |
| else if (info->serial.parity == PORTFW_SERIAL_PARITY_EVEN) dcb.Parity = EVENPARITY; |
| else if (info->serial.parity == PORTFW_SERIAL_PARITY_NONE) dcb.Parity = NOPARITY; |
| else if (info->serial.parity == PORTFW_SERIAL_PARITY_SPACE) dcb.Parity = SPACEPARITY; |
| else if (info->serial.parity == PORTFW_SERIAL_PARITY_MARK) dcb.Parity = MARKPARITY; |
| else { |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| trace(log_portfw, "ERROR: Invalid parity"); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| if (info->serial.stop_bits == 1) |
| dcb.StopBits = ONESTOPBIT; |
| else if (info->serial.stop_bits == 2) |
| dcb.StopBits = TWOSTOPBITS; |
| else { |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| trace(log_portfw, "ERROR: Invalid stop bits number"); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| /* Documented unsupported configurations */ |
| |
| if (info->serial.stop_bits == 2 && |
| info->serial.data_bits == 5) |
| { |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| errno = EINVAL; |
| trace(log_portfw, "ERROR: Invalid stop bits number"); |
| return -1; |
| } |
| |
| dcb.fOutX = 0; |
| dcb.fInX = 0; |
| |
| if (info->serial.flow_ctrl == PORTFW_SERIAL_FC_NONE) { |
| dcb.fOutxCtsFlow = 0; |
| dcb.fOutxDsrFlow = 0; |
| dcb.fDtrControl = DTR_CONTROL_DISABLE; |
| dcb.fDsrSensitivity = 0; |
| dcb.fRtsControl = RTS_CONTROL_DISABLE; |
| } |
| #ifdef SUPPORT_FLOW_CTRL |
| else if (info->serial.flow_ctrl == PORTFW_SERIAL_FC_XON) { |
| dcb.fOutxCtsFlow = 0; |
| dcb.fOutxDsrFlow = 0; |
| dcb.fDtrControl = DTR_CONTROL_DISABLE; |
| dcb.fDsrSensitivity = 0; |
| dcb.fRtsControl = RTS_CONTROL_DISABLE; |
| dcb.fOutX = dcb.fInX = 1; |
| } |
| else if (info->serial.flow_ctrl == PORTFW_SERIAL_FC_RTS) { |
| dcb.fOutxCtsFlow = 1; |
| dcb.fOutxDsrFlow = 0; |
| dcb.fDtrControl = DTR_CONTROL_DISABLE; |
| dcb.fDsrSensitivity = 0; |
| dcb.fRtsControl = RTS_CONTROL_ENABLE; |
| } |
| else if (info->serial.flow_ctrl == PORTFW_SERIAL_FC_DSR) { |
| dcb.fOutxCtsFlow = 0; |
| dcb.fOutxDsrFlow = 1; |
| dcb.fDtrControl = DTR_CONTROL_ENABLE; |
| dcb.fDsrSensitivity = 1; |
| dcb.fRtsControl = RTS_CONTROL_DISABLE; |
| } |
| #endif /* SUPPORT_FLOW_CTRL */ |
| else { |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| trace(log_portfw, "ERROR: Invalid flow control"); |
| errno = EINVAL; |
| return -1; |
| } |
| |
| dcb.fAbortOnError = 0; |
| |
| if (!SetCommState(handle, &dcb)) { |
| set_win32_errno(GetLastError()); |
| CloseHandle (handle); |
| CloseHandle (info->serial.read_overlapped.hEvent); |
| CloseHandle (info->serial.write_overlapped.hEvent); |
| trace(log_portfw, "Unable to set comm state: %d", errno); |
| return -1; |
| } |
| |
| GetCommTimeouts (handle, &info->serial.saved_timeout); |
| timeouts.ReadIntervalTimeout = MAXDWORD; |
| timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; |
| timeouts.ReadTotalTimeoutConstant = 2000; /* set a timeout of 2s max */ |
| timeouts.WriteTotalTimeoutMultiplier = 15; /* max = 15ms per character */ |
| timeouts.WriteTotalTimeoutConstant = 200; /* const: 200ms */ |
| SetCommTimeouts (handle, &timeouts); |
| info->serial.handle = handle; |
| #else /* WIN32 */ |
| struct termios termIo; |
| #if defined(__linux__) |
| struct serial_struct serial; |
| #endif |
| int data_bits_const = CS8; |
| |
| for (ix = 0; data_bit_table[ix].data_bits != 0; ix++) { |
| if (data_bit_table[ix].data_bits == info->serial.data_bits) |
| data_bits_const = data_bit_table[ix].data_bits_const; |
| } |
| |
| #if defined(__APPLE__) |
| if ((info->serial.fd = open(dev_name, O_RDWR|O_NONBLOCK, 0)) < 0) { |
| #else |
| if ((info->serial.fd = open(dev_name, O_RDWR, 0)) < 0) { |
| #endif |
| trace(log_portfw, "Unable to open serial port: %d", errno); |
| return -1; |
| } |
| if (tcgetattr(info->serial.fd, &termIo) == -1) { |
| trace(log_portfw, "Unable to get serial port attributes: %d", errno); |
| close(info->serial.fd); |
| info->serial.fd = -1; |
| return -1; |
| } |
| |
| termIo.c_iflag = 0; /* no input procesing */ |
| termIo.c_oflag = 0; /* no output procesing */ |
| termIo.c_cflag = CREAD | data_bits_const | CLOCAL; |
| |
| if (info->serial.stop_bits == 2) termIo.c_cflag |= CSTOPB; |
| |
| if (info->serial.parity == PORTFW_SERIAL_PARITY_ODD) termIo.c_cflag |= PARODD; |
| else if (info->serial.parity == PORTFW_SERIAL_PARITY_EVEN) termIo.c_cflag |= PARENB; |
| else if (info->serial.parity != PORTFW_SERIAL_PARITY_NONE) { |
| errno = EINVAL; |
| close(info->serial.fd); |
| info->serial.fd = -1; |
| trace(log_portfw, "ERROR: Invalid parity"); |
| return -1; |
| } |
| |
| termIo.c_lflag = 0; /* no local processing */ |
| |
| /* time out value (<val> * 0.1s) */ |
| termIo.c_cc[VMIN] = 0; |
| termIo.c_cc[VTIME] = 3 * 10; |
| |
| #ifdef ASYNC_LOW_LATENCY |
| if (ioctl(info->serial.fd, TIOCGSERIAL, &serial) == 0) |
| { |
| serial.flags |= ASYNC_LOW_LATENCY; |
| ioctl(info->serial.fd, TIOCSSERIAL, &serial); |
| } |
| #endif /* ASYNC_LOW_LATENCY */ |
| |
| /* configure baud rate for input and output */ |
| |
| cfsetispeed(&termIo, baud_const); |
| cfsetospeed(&termIo, baud_const); |
| |
| /* set the attributes */ |
| |
| tcsetattr(info->serial.fd, TCSAFLUSH, &termIo); |
| |
| #if defined(__APPLE__) |
| fcntl(info->serial.fd, F_SETFL, fcntl(info->serial.fd, F_GETFL) & ~O_NONBLOCK); |
| #endif /* __APPLE__ */ |
| #endif |
| trace(log_portfw, "SUCCESS: serial port opened"); |
| return 0; |
| } |
| |
| static int serial_send_req_func(PortFwConfig * config, void * buffer, size_t length) { |
| int ret; |
| #ifdef WIN32 |
| BOOL status; |
| DWORD write_count; |
| DWORD total_write_count = 0; |
| HANDLE WriteEvent; |
| WriteEvent = config->port_info.serial.write_overlapped.hEvent; |
| |
| do |
| { |
| memset (&config->port_info.serial.write_overlapped, 0, sizeof(config->port_info.serial.write_overlapped)); |
| config->port_info.serial.write_overlapped.hEvent = WriteEvent; |
| ResetEvent (WriteEvent); |
| status = WriteFile (config->port_info.serial.handle, |
| (char *)buffer + total_write_count, |
| length - total_write_count, &write_count, |
| &config->port_info.serial.write_overlapped); |
| |
| /* ...and wait till last char is sent. */ |
| |
| if (status == FALSE) { |
| DWORD error = GetLastError (); |
| |
| if (error == ERROR_IO_PENDING) { |
| status = GetOverlappedResult (config->port_info.serial.handle, |
| &config->port_info.serial.write_overlapped, |
| &write_count, TRUE); |
| if (!status) { |
| set_win32_errno(GetLastError()); |
| ResetEvent (WriteEvent); |
| return -1; |
| } |
| } |
| else { |
| ResetEvent (WriteEvent); |
| set_win32_errno(error); |
| return -1; |
| } |
| } |
| total_write_count += write_count; |
| } while ((int) total_write_count < length); |
| |
| ResetEvent (WriteEvent); |
| ret = length; |
| #else |
| do { |
| ret = write(config->port_info.serial.fd, buffer, length); |
| } |
| while (ret < 0 && (errno == EINTR)); |
| #endif |
| return ret; |
| } |
| |
| static int serial_recv_req_func(PortFwConfig * config, void * buffer, size_t length) { |
| #ifdef WIN32 |
| BOOL fWaitingOnRead = FALSE; |
| HANDLE handle = config->port_info.serial.handle; /* serial device handle */ |
| OVERLAPPED * read_overlapped = &config->port_info.serial.read_overlapped; |
| int total_read_count = 0; |
| |
| ResetEvent(read_overlapped->hEvent); |
| while (1) { |
| if (!fWaitingOnRead) { |
| DWORD read_count; |
| DWORD max_bytes = 0; |
| DWORD com_errors; |
| COMSTAT com_status; /* To get the pending input bytes */ |
| |
| /* Get the number of pending chars. If there are more than 1 read all of them; otherwise wait |
| * only for the first character. Waiting for more than 1 character if they are not available |
| * would lead to block until the specified number of bytes has been received. |
| */ |
| |
| if (ClearCommError (handle, &com_errors, &com_status)) { |
| max_bytes = com_status.cbInQue; |
| } |
| |
| /* If there are enough received characters to transfer immediately to the client or if there are no |
| * more pending read characters and we have some characters to transfer; do the transfer right now. |
| * Note that READ_THRESHOLD is also used to avoid buffer overflow. |
| */ |
| if (max_bytes == 0 && total_read_count != 0) { |
| return total_read_count; |
| } |
| |
| /* If there are no pending characters; try to read only one. Otherwise, we would block until |
| * we have received the number of characters specified. |
| */ |
| if (max_bytes == 0) max_bytes = 1; |
| else { |
| if ((total_read_count + max_bytes) > length) max_bytes = (length - total_read_count); |
| } |
| |
| if (!ReadFile(handle, (char *)buffer + total_read_count, max_bytes, &read_count, read_overlapped)) { |
| if (GetLastError() != ERROR_IO_PENDING) { |
| set_win32_errno(GetLastError()); |
| return -1; |
| } |
| else { |
| fWaitingOnRead = TRUE; |
| } |
| } |
| else { |
| /* read completed immediately |
| * Read the character but do not make it available to the client right now; check if |
| * we can read more data first. |
| */ |
| |
| total_read_count += read_count; |
| fWaitingOnRead = FALSE; |
| } |
| } |
| else { |
| /* We are waiting for the result of an asynchronous read */ |
| |
| DWORD dwRes = WaitForSingleObject(read_overlapped->hEvent, INFINITE); |
| DWORD read_count; |
| switch(dwRes) { |
| case WAIT_OBJECT_0: |
| if (!GetOverlappedResult(handle, read_overlapped, &read_count, TRUE)) { |
| set_win32_errno(GetLastError()); |
| return -1; |
| } |
| else { |
| /* Read completed successfully. */ |
| total_read_count += read_count; |
| |
| /* Reset flag so that another operation can be issued. */ |
| fWaitingOnRead = FALSE; |
| } |
| break; |
| case WAIT_TIMEOUT: |
| /* Operation isn't complete yet. fWaitingOnRead flag isn't |
| * changed since I'll loop back around, and I don't want |
| * to issue another read until the first one finishes. |
| * |
| * This is a good time to do some background work. |
| */ |
| break; |
| |
| default: |
| set_win32_errno(GetLastError()); |
| return -1; |
| } |
| } |
| } |
| return total_read_count; |
| #else |
| int ret; |
| do |
| { |
| ret = read(config->port_info.serial.fd, buffer, length); |
| } |
| while (ret == 0 || (ret < 0 && (errno == EINTR))); |
| return ret; |
| #endif |
| } |
| |
| static void serial_close(PortFwConfig * config) { |
| #ifdef WIN32 |
| if (config->port_info.serial.handle == NULL) return; |
| SetCommTimeouts (config->port_info.serial.handle, &config->port_info.serial.saved_timeout); |
| SetCommState(config->port_info.serial.handle, &config->port_info.serial.saved_dcb); |
| CloseHandle (config->port_info.serial.handle); |
| config->port_info.serial.handle = NULL; |
| if (config->port_info.serial.read_overlapped.hEvent) CloseHandle(config->port_info.serial.read_overlapped.hEvent); |
| if (config->port_info.serial.write_overlapped.hEvent) CloseHandle(config->port_info.serial.write_overlapped.hEvent); |
| config->port_info.serial.read_overlapped.hEvent = NULL; |
| config->port_info.serial.write_overlapped.hEvent = NULL; |
| #else |
| if (config->port_info.serial.fd == -1) return; |
| close(config->port_info.serial.fd); |
| #endif |
| if (config->verbose) fprintf(stderr, "Connection with %s closed\n", config->port_config); |
| } |
| |
| static void serial_connect(PortFwConfig * config, ConnectCallBack callback) { |
| int error = 0; |
| |
| if (serial_parse_params(config) == -1) { |
| error = errno; |
| callback(config, error); |
| return; |
| } |
| else { |
| PortConnectInfo * info = &config->port_info; |
| |
| info->send_req_func = serial_send_req_func; |
| info->recv_req_func = serial_recv_req_func; |
| info->close_func = serial_close; |
| info->cnct_callback = callback; |
| info->cnct_req.client_data = config; |
| info->cnct_req.done = serial_connect_done; |
| info->cnct_req.type = AsyncReqUser; |
| info->cnct_req.u.user.func = serial_connect_req_func; |
| info->cnct_req.u.user.data = config; |
| async_req_post(&info->cnct_req); |
| } |
| } |
| /* END SERIAL LINE SUPPORT */ |
| #endif /* ENABLE_PortForward_Serial */ |
| |
| static void display_buffer(unsigned char * buffer, size_t size) { |
| if ((log_mode & log_portfw) && log_file) { |
| size_t i; |
| char tmp_buffer[256]; |
| if (size > sizeof(tmp_buffer)) size = sizeof(tmp_buffer) - 1; |
| for (i = 0; i < size; i++) { |
| if (isprint(buffer[i])) tmp_buffer[i] = buffer[i]; |
| else tmp_buffer[i] = '.'; |
| } |
| tmp_buffer[i] = 0; |
| trace(log_portfw, tmp_buffer); |
| } |
| } |
| |
| static void send_event_portfw_config_added(PortFwConfig * config) { |
| if (broadcast_group == NULL) return; |
| else { |
| OutputStream * out = &broadcast_group->out; |
| write_stringz(out, "E"); |
| write_stringz(out, PortForward); |
| write_stringz(out, "configAdded"); |
| write_portfw_config (out, config); |
| write_stream(out, 0); |
| write_stream(out, MARKER_EOM); |
| } |
| } |
| |
| static void send_event_portfw_config_removed(PortFwConfig * config) { |
| if (broadcast_group == NULL) return; |
| else { |
| OutputStream * out = &broadcast_group->out; |
| write_stringz(out, "E"); |
| write_stringz(out, PortForward); |
| write_stringz(out, "configRemoved"); |
| write_stream(out, '['); |
| json_write_string(out, config->id); |
| write_stream(out, ']'); |
| write_stream(out, 0); |
| write_stream(out, MARKER_EOM); |
| } |
| } |
| |
| static void write_portfw_config(OutputStream * out, PortFwConfig * config) { |
| write_stream(out, '{'); |
| json_write_string(out, "ID"); |
| write_stream(out, ':'); |
| json_write_string(out, config->id); |
| write_stream(out, ','); |
| json_write_string(out, "InputStream"); |
| write_stream(out, ':'); |
| json_write_string(out, config->in_stream_id); |
| write_stream(out, ','); |
| json_write_string(out, "OutputStream"); |
| write_stream(out, ':'); |
| json_write_string(out, config->out_stream_id); |
| if (config->client_data) { |
| write_stream(out, ','); |
| json_write_string(out, "ClientData"); |
| write_stream(out, ':'); |
| json_write_string(out, config->client_data); |
| } |
| write_stream(out, ','); |
| json_write_string(out, "Port"); |
| write_stream(out, ':'); |
| json_write_string(out, config->port_config); |
| if (config->port_params) { |
| write_stream(out, ','); |
| json_write_string(out, "Params"); |
| write_stream(out, ':'); |
| json_write_string(out, config->port_params); |
| } |
| write_stream(out, ','); |
| json_write_string(out, "Verbose"); |
| write_stream(out, ':'); |
| json_write_boolean(out, config->verbose); |
| write_stream(out, '}'); |
| } |
| |
| static void portfw_cmd_get_config(char * token, Channel * c) { |
| char id[256]; |
| PortFwConfig * config; |
| int err = 0; |
| json_read_string(&c->inp, id, sizeof(id)); |
| if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| if ((config = find_portfw_config(id)) == NULL) err = set_errno(ERR_OTHER, |
| portfw_inv_config_err); |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, token); |
| write_errno(&c->out, err); |
| if (err == 0) { |
| write_portfw_config(&c->out, config); |
| write_stream(&c->out, 0); |
| } |
| else { |
| write_stringz(&c->out, "null"); |
| } |
| write_stream(&c->out, MARKER_EOM); |
| } |
| |
| static void portfw_cmd_list(char * token, Channel * c) { |
| LINK * l = portfw_config_list.next; |
| int cnt = 0; |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, token); |
| write_errno(&c->out, 0); |
| write_stream(&c->out, '['); |
| while (l != &portfw_config_list) { |
| PortFwConfig * config = link2pfwp(l); |
| if (cnt > 0) write_stream(&c->out, ','); |
| json_write_string(&c->out, config->id); |
| l = l->next; |
| cnt++; |
| } |
| write_stream(&c->out, ']'); |
| write_stream(&c->out, 0); |
| write_stream(&c->out, MARKER_EOM); |
| } |
| |
| |
| static PortFwConfig * find_portfw_config(const char * id) { |
| LINK * l = portfw_config_list.next; |
| while (l != &portfw_config_list) { |
| PortFwConfig * config = link2pfwp(l); |
| if (strcmp(id, config->id) == 0 && !config->shutdown_in_progress) { |
| return config; |
| } |
| l = l->next; |
| } |
| return NULL ; |
| |
| } |
| |
| static int user_send_func(void * data) { |
| PortFwConfig * config = (PortFwConfig *) data; |
| return config->port_info.send_req_func(config, config->outbuf, config->outbuf_len); |
| } |
| |
| static int user_recv_func(void * data) { |
| PortFwConfig * config = (PortFwConfig *) data; |
| return config->port_info.recv_req_func(config, config->inbuf, IN_BUF_SIZE); |
| } |
| |
| static void write_stream_to_port(PortFwConfig * config) { |
| int eos = 0; |
| size_t read_size = 0; |
| assert (config->send_in_progress == 0); |
| |
| if (virtual_stream_get_data(config->out_vstream, |
| config->outbuf + config->outbuf_len, |
| OUT_BUF_SIZE - config->outbuf_len, |
| &read_size, &eos) < 0) return; |
| config->outbuf_len += read_size; |
| |
| if (read_size != 0) { |
| size_t write_size = 0; |
| write_size = config->outbuf_len; |
| |
| if (write_size != 0) { |
| config->send_in_progress = 1; |
| if (config->send_req.type != AsyncReqUser) { |
| config->send_req.u.sio.bufp = config->outbuf; |
| config->send_req.u.sio.bufsz = write_size; |
| } |
| async_req_post(&config->send_req); |
| } |
| } |
| else |
| config->send_in_progress = 0; |
| } |
| |
| static void release_config(PortFwConfig * config) { |
| loc_free(config->port_config); |
| loc_free(config); |
| } |
| |
| static void delete_config(PortFwConfig * config) { |
| size_t wrote = 0; |
| config->shutdown_in_progress = 1; |
| if (config->connected) { |
| disconnect_port(config); |
| } |
| if (config->send_in_progress || config->recv_in_progress) { |
| return; |
| } |
| virtual_stream_add_data(config->in_vstream, NULL, 0, &wrote, 1); |
| virtual_stream_disconnect(config->channel, NULL, config->out_stream_id); |
| virtual_stream_disconnect(config->channel, NULL, config->in_stream_id); |
| virtual_stream_delete(config->in_vstream); |
| virtual_stream_delete(config->out_vstream); |
| list_remove (&config->link); |
| send_event_portfw_config_removed(config); |
| |
| release_config(config); |
| } |
| |
| static void done_recv_request(void * args) { |
| AsyncReqInfo * req = (AsyncReqInfo *)args; |
| PortFwConfig * config = (PortFwConfig *)(req)->client_data; |
| int rval; |
| |
| if (req->type == AsyncReqUser) rval = req->u.user.rval; |
| else rval = req->u.sio.rval; |
| |
| config->recv_in_progress = 0; |
| if (rval == 0 |
| || ((rval == -1 && req->error != EINTR) |
| && (rval == -1 && (req->error != ECONNREFUSED || config->port_type != PORTFW_UDP_PORT))) |
| || config->shutdown_in_progress) { |
| delete_config(config); |
| return; |
| } |
| if (rval == -1) { |
| /* Interrupted system call */ |
| async_req_post(&config->recv_req); |
| return; |
| } |
| config->inbuf_len = rval; |
| if (rval > 0) display_buffer((unsigned char *)config->inbuf, rval); |
| write_port_to_stream(config); |
| } |
| |
| static void done_send_request(void * args) { |
| AsyncReqInfo * req = (AsyncReqInfo *)args; |
| PortFwConfig * config = (PortFwConfig *)(req)->client_data; |
| int rval; |
| |
| config->send_in_progress = 0; |
| if (req->type == AsyncReqUser) rval = req->u.user.rval; |
| else rval = req->u.sio.rval; |
| if (rval == 0 |
| || ((rval == -1 && req->error != EINTR) |
| && (rval == -1 && (req->error != ECONNREFUSED || config->port_type != PORTFW_UDP_PORT))) |
| || config->shutdown_in_progress) { |
| delete_config(config); |
| return; |
| } |
| if (rval == -1) { |
| /* Interrupted system call */ |
| async_req_post(&config->send_req); |
| return; |
| } |
| if (rval > 0) display_buffer((unsigned char *)config->outbuf, rval); |
| if (config->outbuf_len != (size_t)rval) { |
| memmove(config->outbuf, config->outbuf + rval, config->outbuf_len - rval); |
| config->outbuf_len -= (size_t)rval; |
| async_req_post(&config->send_req); |
| return; |
| } |
| |
| config->outbuf_len -= (size_t)rval; |
| write_stream_to_port(config); |
| } |
| |
| static void write_stream_to_port_callback(VirtualStream *stream, int event, |
| void * arg) { |
| PortFwConfig * config = (PortFwConfig *) arg; |
| if (event == VS_EVENT_DATA_AVAILABLE) { |
| if (config->connected == 0 || config->shutdown_in_progress) return; |
| if (config->send_in_progress) |
| return; |
| write_stream_to_port(config); |
| } |
| } |
| |
| static void write_port_to_stream(void * arg) { |
| PortFwConfig * config = (PortFwConfig *) arg; |
| |
| assert (config->recv_in_progress == 0); |
| |
| if (config->inbuf_len != 0) { |
| size_t wrote = 0; |
| virtual_stream_add_data(config->in_vstream, |
| config->inbuf + config->inbuf_pos, config->inbuf_len, &wrote, |
| 0); |
| config->inbuf_pos += wrote; |
| config->inbuf_len -= wrote; |
| } |
| |
| /* If all data has been consumed, start a new read request. Otherwise wait |
| * for the full buffer to be consumed. |
| */ |
| if (config->inbuf_len == 0) { |
| config->inbuf_pos = 0; |
| if (config->recv_req.type != AsyncReqUser) { |
| config->recv_req.u.sio.bufp = config->inbuf; |
| config->recv_req.u.sio.bufsz = IN_BUF_SIZE; |
| } |
| config->recv_in_progress = 1; |
| async_req_post(&config->recv_req); |
| } |
| } |
| |
| static void write_port_to_stream_callback(VirtualStream *stream, |
| int event, void * arg) { |
| PortFwConfig * config = (PortFwConfig *) arg; |
| if (config->connected == 0 || config->shutdown_in_progress) return; |
| switch (event) { |
| case VS_EVENT_SPACE_AVAILABLE: |
| { |
| if (config->recv_in_progress) return; |
| write_port_to_stream(config); |
| } |
| break; |
| case VS_EVENT_DATA_AVAILABLE: |
| default: |
| /* |
| portfw_client_read(config); |
| */ |
| str_exception(ERR_OTHER, "Unhandled stream callback\n"); |
| } |
| } |
| |
| static void connect_callback(PortFwConfig * config, int error) { |
| if (error) { |
| if (config->verbose) fprintf(stderr, "Error opening %s: %d (%s)\n", config->port_config, error, errno_to_str(error)); |
| } |
| else { |
| if (config->verbose) fprintf(stdout, "Successfully opened %s\n", config->port_config); |
| } |
| channel_unlock_with_msg(config->channel, PortForward); |
| write_stringz(&config->channel->out, "R"); |
| write_stringz(&config->channel->out, config->token); |
| write_errno(&config->channel->out, error); |
| write_stream(&config->channel->out, MARKER_EOM); |
| |
| if (error == 0) { |
| virtual_stream_create(PortForward, config->id, IN_BUF_SIZE, |
| VS_ENABLE_REMOTE_READ, write_port_to_stream_callback, config, |
| &config->in_vstream); |
| virtual_stream_create(PortForward, config->id, OUT_BUF_SIZE, |
| VS_ENABLE_REMOTE_WRITE, write_stream_to_port_callback, config, |
| &config->out_vstream); |
| virtual_stream_get_id(config->in_vstream, config->in_stream_id, |
| sizeof(config->in_stream_id)); |
| virtual_stream_get_id(config->out_vstream, config->out_stream_id, |
| sizeof(config->out_stream_id)); |
| virtual_stream_connect(config->channel, NULL, config->out_stream_id); |
| virtual_stream_connect(config->channel, NULL, config->in_stream_id); |
| |
| list_add_first(&config->link, &portfw_config_list); |
| |
| send_event_portfw_config_added(config); |
| |
| config->send_req.client_data = config; |
| config->send_req.done = done_send_request; |
| |
| config->recv_req.client_data = config; |
| config->recv_req.done = done_recv_request; |
| |
| if (config->port_info.sock != -1) { |
| config->send_req.type = AsyncReqSend; |
| config->send_req.u.sio.sock = config->port_info.sock; |
| config->send_req.u.sio.flags = 0; |
| |
| config->recv_req.type = AsyncReqRecv; |
| config->recv_req.u.sio.sock = config->port_info.sock; |
| config->recv_req.u.sio.flags = 0; |
| config->recv_req.u.sio.bufp = config->inbuf; |
| config->recv_req.u.sio.bufsz = IN_BUF_SIZE; |
| } |
| else { |
| config->send_req.type = AsyncReqUser; |
| config->send_req.u.user.func = user_send_func; |
| config->send_req.u.user.data = config; |
| |
| config->recv_req.type = AsyncReqUser; |
| config->recv_req.u.user.func = user_recv_func; |
| config->recv_req.u.user.data = config; |
| } |
| |
| config->recv_in_progress = 1; |
| async_req_post(&config->recv_req); |
| } |
| else { |
| disconnect_port(config); |
| release_config(config); |
| } |
| } |
| |
| static void read_portfw_property(InputStream * inp, const char * name, |
| void * args) { |
| PortFwConfig * config = (PortFwConfig *) args; |
| if (strcmp(name, "ID") == 0) json_read_string(inp, config->id, |
| sizeof(config->id)); |
| else if (strcmp(name, "Port") == 0) config->port_config = |
| json_read_alloc_string(inp); |
| else if (strcmp(name, "Params") == 0) config->port_params = json_read_alloc_string(inp); |
| else if (strcmp(name, "Verbose") == 0) config->verbose = json_read_boolean(inp); |
| else if (strcmp(name, "ClientData") == 0) config->client_data = json_read_alloc_string(inp); |
| else exception(ERR_JSON_SYNTAX); |
| } |
| |
| static void portfw_cmd_create(char * token, Channel * c) { |
| int err = 0; |
| PortFwConfig * config = (PortFwConfig *) loc_alloc_zero(sizeof(PortFwConfig)); |
| config->port_info.sock = -1; |
| |
| json_read_struct(&c->inp, read_portfw_property, config); |
| if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| |
| if (config->id[0] == 0) str_exception(ERR_OTHER, "PortForward configuration must have an ID"); |
| |
| if (find_portfw_config(config->id) != NULL) err = set_errno(ERR_OTHER, |
| "PortForward Remote configuration already exists"); |
| |
| if (config->port_config == NULL) { |
| err = set_errno (ERR_OTHER, "Need to specify a port configuration"); |
| } |
| |
| if (err == 0) { |
| config->channel = c; |
| strlcpy(config->token, token, sizeof(config->token)); |
| |
| trace(log_portfw, "Received connection request for port %s", config->port_config); |
| |
| if (strncasecmp(config->port_config, "tcp:", strlen("tcp:")) == 0) { |
| channel_lock_with_msg(c, PortForward); |
| config->port_type = PORTFW_TCP_PORT; |
| if (config->verbose) fprintf(stdout, "Received connection request for %s\n", config->port_config); |
| tcp_connect(config, connect_callback); |
| } |
| else if (strncasecmp(config->port_config, "udp:", strlen("udp:")) == 0) { |
| config->port_type = PORTFW_UDP_PORT; |
| #if ENABLE_PortForward_UDP |
| channel_lock_with_msg(c, PortForward); |
| if (config->verbose) fprintf(stdout, "Received connection request for %s\n", config->port_config); |
| udp_connect(config, connect_callback); |
| #else |
| if (config->verbose) fprintf(stderr, "Unsupported protocol \"udp\"\n"); |
| err = EINVAL; |
| #endif |
| } |
| #if ENABLE_PortForward_Serial |
| else if (strncasecmp(config->port_config, "serial:", strlen("serial:")) == 0) { |
| config->port_type = PORTFW_SER_PORT; |
| if (config->verbose) fprintf(stdout, "Received connection request for %s\n", config->port_config); |
| channel_lock_with_msg(c, PortForward); |
| serial_connect(config, connect_callback); |
| } |
| #endif |
| else { |
| if (config->verbose) fprintf(stderr, "Unsupported connection request for %s\n", config->port_config); |
| err = EINVAL; |
| } |
| } |
| |
| if (err == 0) { |
| return; |
| } |
| else { |
| release_config(config); |
| } |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, token); |
| write_errno(&c->out, err); |
| write_stream(&c->out, MARKER_EOM); |
| } |
| |
| static void portfw_cmd_delete(char * token, Channel * c) { |
| char id[256]; |
| int err = 0; |
| PortFwConfig * config; |
| |
| json_read_string(&c->inp, id, sizeof(id)); |
| if (read_stream(&c->inp) != 0) exception(ERR_JSON_SYNTAX); |
| if (read_stream(&c->inp) != MARKER_EOM) exception(ERR_JSON_SYNTAX); |
| |
| if ((config = find_portfw_config(id)) == NULL) err = set_errno(ERR_OTHER, |
| portfw_inv_config_err); |
| else { |
| delete_config(config); |
| } |
| write_stringz(&c->out, "R"); |
| write_stringz(&c->out, token); |
| write_errno(&c->out, err); |
| write_stream(&c->out, MARKER_EOM); |
| } |
| |
| static void portfw_cmd_get_capabilities(char * token, Channel * c) { |
| char id[256]; |
| PortFwConfig * config = NULL; |
| OutputStream * out = &c->out; |
| int err = 0; |
| |
| json_read_string(&c->inp, id, sizeof(id)); |
| json_test_char(&c->inp, MARKER_EOA); |
| json_test_char(&c->inp, MARKER_EOM); |
| |
| if (strlen(id) > 0) { |
| if ((config = find_portfw_config(id)) == NULL) err = set_errno(ERR_OTHER, |
| portfw_inv_config_err); |
| } |
| |
| write_stringz(out, "R"); |
| write_stringz(out, token); |
| write_errno(out, err); |
| if (err) { |
| write_stringz(&c->out, "null"); |
| } |
| else { |
| write_stream(out, '{'); |
| json_write_string(out, "TCP"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| #if ENABLE_PortForward_Serial |
| write_stream(out,','); |
| json_write_string(out, "Serial"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| #endif /* ENABLE_PortForward_Serial */ |
| #if ENABLE_PortForward_UDP |
| write_stream(out,','); |
| json_write_string(out, "UDP"); |
| write_stream(out, ':'); |
| json_write_boolean(out, 1); |
| #endif /* ENABLE_PortForward_UDP */ |
| write_stream(out, '}'); |
| write_stream(out, 0); |
| } |
| write_stream(out, MARKER_EOM); |
| } |
| |
| static void channel_close_listener(Channel * c) { |
| LINK * l = portfw_config_list.next; |
| for (l = portfw_config_list.next; l != &portfw_config_list;) { |
| PortFwConfig * config = link2pfwp(l); |
| l = l->next; |
| if (config->channel == c) delete_config(config); |
| } |
| } |
| |
| void ini_port_forward_service(Protocol *proto, TCFBroadcastGroup * bcg) { |
| static int ini_portfw = 0; |
| |
| if (ini_portfw == 0) { |
| add_channel_close_listener(channel_close_listener); |
| broadcast_group = bcg; |
| ini_portfw = 1; |
| } |
| log_portfw = add_trace_mode(0, "portfw", "Port Forwarding"); |
| |
| add_command_handler(proto, PortForward, "getConfig", portfw_cmd_get_config); |
| add_command_handler(proto, PortForward, "list", portfw_cmd_list); |
| add_command_handler(proto, PortForward, "create", portfw_cmd_create); |
| add_command_handler(proto, PortForward, "delete", portfw_cmd_delete); |
| add_command_handler(proto, PortForward, "getCapabilities", portfw_cmd_get_capabilities); |
| } |
| |
| #endif |