1/*
2 * lws-minimal-raw-netcat
3 *
4 * Written in 2010-2019 by Andy Green <andy@warmcat.com>
5 *
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
8 *
9 * This demonstrates sending stdin to a remote socket and printing
10 * what is returned to stdout.
11 *
12 * All the logging is on stderr, so you can tune it out with 2>log
13 * or whatever.
14 */
15
16#include <libwebsockets.h>
17#include <string.h>
18#include <signal.h>
19#if !defined(WIN32)
20#include <sys/socket.h>
21#include <sys/types.h>
22#include <netinet/in.h>
23#include <netdb.h>
24#include <arpa/inet.h>
25#endif
26#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#if !defined(WIN32)
30#include <unistd.h>
31#endif
32#include <errno.h>
33
34static struct lws *raw_wsi, *stdin_wsi;
35static uint8_t buf[LWS_PRE + 4096];
36static int waiting, interrupted;
37static struct lws_context *context;
38static int us_wait_after_input_close = LWS_USEC_PER_SEC / 10;
39
40static int
41callback_raw_test(struct lws *wsi, enum lws_callback_reasons reason,
42		  void *user, void *in, size_t len)
43{
44	const char *cp = (const char *)in;
45
46	switch (reason) {
47
48	/* callbacks related to file descriptor */
49
50        case LWS_CALLBACK_RAW_ADOPT_FILE:
51        	lwsl_user("LWS_CALLBACK_RAW_ADOPT_FILE\n");
52                break;
53
54	case LWS_CALLBACK_RAW_CLOSE_FILE:
55		lwsl_user("LWS_CALLBACK_RAW_CLOSE_FILE\n");
56		/* stdin close, wait 1s then close the raw skt */
57		stdin_wsi = NULL; /* invalid now we close */
58		if (raw_wsi)
59			lws_set_timer_usecs(raw_wsi, us_wait_after_input_close);
60		else {
61			interrupted = 1;
62			lws_cancel_service(context);
63		}
64		break;
65
66	case LWS_CALLBACK_RAW_RX_FILE:
67		lwsl_user("LWS_CALLBACK_RAW_RX_FILE\n");
68		waiting = (int)read(0, buf, sizeof(buf));
69		lwsl_notice("raw file read %d\n", waiting);
70		if (waiting < 0)
71			return -1;
72
73		if (raw_wsi)
74			lws_callback_on_writable(raw_wsi);
75		lws_rx_flow_control(wsi, 0);
76		break;
77
78
79	/* callbacks related to raw socket descriptor */
80
81        case LWS_CALLBACK_RAW_ADOPT:
82		lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
83		lws_callback_on_writable(wsi);
84                break;
85
86	case LWS_CALLBACK_RAW_CLOSE:
87		lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
88		/*
89		 * If the socket to the remote server closed, we must close
90		 * and drop any remaining stdin
91		 */
92		interrupted = 1;
93		lws_cancel_service(context);
94		/* our pointer to this wsi is invalid now we close */
95		raw_wsi = NULL;
96		break;
97
98	case LWS_CALLBACK_RAW_RX:
99		lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
100		while (len--)
101			putchar(*cp++);
102		fflush(stdout);
103		break;
104
105	case LWS_CALLBACK_RAW_WRITEABLE:
106		lwsl_user("LWS_CALLBACK_RAW_WRITEABLE\n");
107		// lwsl_hexdump_info(buf, waiting);
108		if (stdin_wsi)
109			lws_rx_flow_control(stdin_wsi, 1);
110		if (lws_write(wsi, buf, (unsigned int)waiting, LWS_WRITE_RAW) != waiting) {
111			lwsl_notice("%s: raw skt write failed\n", __func__);
112
113			return -1;
114		}
115		break;
116
117	case LWS_CALLBACK_TIMER:
118		lwsl_user("LWS_CALLBACK_TIMER\n");
119		interrupted = 1;
120		lws_cancel_service(context);
121		return -1;
122
123	default:
124		break;
125	}
126
127	return 0;
128}
129
130static struct lws_protocols protocols[] = {
131	{ "raw-test", callback_raw_test, 0, 0, 0, NULL, 0 },
132	LWS_PROTOCOL_LIST_TERM
133};
134
135void sigint_handler(int sig)
136{
137	interrupted = 1;
138}
139
140int main(int argc, const char **argv)
141{
142	const char *server = "libwebsockets.org", *port = "80";
143	struct lws_context_creation_info info;
144	lws_sock_file_fd_type sock;
145	struct addrinfo h, *r, *rp;
146	struct lws_vhost *vhost;
147	const char *p;
148	int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
149
150	signal(SIGINT, sigint_handler);
151
152	if ((p = lws_cmdline_option(argc, argv, "-d")))
153		logs = atoi(p);
154
155	lws_set_log_level(logs, NULL);
156	lwsl_user("LWS minimal raw netcat [--server ip] [--port port] [-w ms]\n");
157
158	memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
159	info.options = LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
160
161	context = lws_create_context(&info);
162	if (!context) {
163		lwsl_err("lws init failed\n");
164		return 1;
165	}
166
167	info.port = CONTEXT_PORT_NO_LISTEN_SERVER;
168	info.protocols = protocols;
169
170	vhost = lws_create_vhost(context, &info);
171	if (!vhost) {
172		lwsl_err("lws vhost creation failed\n");
173		goto bail;
174	}
175
176	/*
177	 * Connect our own "foreign" socket to libwebsockets.org:80
178	 *
179	 * Normally you would do this with lws_client_connect_via_info() inside
180	 * the lws event loop, hiding all this detail.  But this example
181	 * demonstrates how to integrate an externally-connected "foreign"
182	 * socket, so we create one by hand.
183	 */
184
185	memset(&h, 0, sizeof(h));
186	h.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
187	h.ai_socktype = SOCK_STREAM;
188	h.ai_protocol = IPPROTO_TCP;
189
190	if ((p = lws_cmdline_option(argc, argv, "--port")))
191		port = p;
192
193	if ((p = lws_cmdline_option(argc, argv, "--server")))
194		server = p;
195
196	if ((p = lws_cmdline_option(argc, argv, "-w")))
197		us_wait_after_input_close = 1000 * atoi(p);
198
199	n = getaddrinfo(server, port, &h, &r);
200	if (n) {
201		lwsl_err("%s: problem resolving %s: %s\n", __func__,
202			 server, gai_strerror(n));
203		return 1;
204	}
205
206	for (rp = r; rp; rp = rp->ai_next) {
207		sock.sockfd = socket(rp->ai_family, rp->ai_socktype,
208				     rp->ai_protocol);
209		if (sock.sockfd != LWS_SOCK_INVALID)
210			break;
211	}
212	if (!rp) {
213		lwsl_err("%s: unable to create INET socket\n", __func__);
214		freeaddrinfo(r);
215
216		return 1;
217	}
218
219	lwsl_user("Starting connect to %s:%s...\n", server, port);
220	if (connect(sock.sockfd, rp->ai_addr, sizeof(*rp->ai_addr)) < 0) {
221		lwsl_err("%s: unable to connect\n", __func__);
222		freeaddrinfo(r);
223		return 1;
224	}
225
226	freeaddrinfo(r);
227	signal(SIGINT, sigint_handler);
228	lwsl_user("Connected...\n");
229
230	/* our foreign socket is connected... adopt it into lws */
231
232	raw_wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_SOCKET, sock,
233					     protocols[0].name, NULL);
234	if (!raw_wsi) {
235		lwsl_err("%s: foreign socket adoption failed\n", __func__);
236		goto bail;
237	}
238
239	sock.filefd = 0;
240	stdin_wsi = lws_adopt_descriptor_vhost(vhost, LWS_ADOPT_RAW_FILE_DESC,
241					       sock, protocols[0].name, NULL);
242	if (!stdin_wsi) {
243		lwsl_err("%s: stdin adoption failed\n", __func__);
244		goto bail;
245	}
246
247	while (n >= 0 && !interrupted)
248		n = lws_service(context, 0);
249
250bail:
251
252	lwsl_user("%s: destroying context\n", __func__);
253
254	lws_context_destroy(context);
255
256	return 0;
257}
258