1/*
2    Copyright Copyright (C) 2013 Andrey Uzunov
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16*/
17
18/**
19 * @file mhd2spdy.c
20 * @brief  The main file of the HTTP-to-SPDY proxy with the 'main' function
21 *         and event loop. No threads are used.
22 *         Currently only GET is supported.
23 *         TODOs:
24 *         - non blocking SSL connect
25 *         - check certificate
26 *         - on closing spdy session, close sockets for all requests
27 * @author Andrey Uzunov
28 */
29
30
31#include "mhd2spdy_structures.h"
32#include "mhd2spdy_spdy.h"
33#include "mhd2spdy_http.h"
34
35
36static int run = 1;
37//static int spdy_close = 0;
38
39
40static void
41catch_signal(int signal)
42{
43  (void)signal;
44  //spdy_close = 1;
45  run = 0;
46}
47
48
49void
50print_stat()
51{
52  if(!glob_opt.statistics)
53    return;
54
55  printf("--------------------------\n");
56  printf("Statistics (TLS overhead is ignored when used):\n");
57  //printf("HTTP bytes received: %lld\n", glob_stat.http_bytes_received);
58  //printf("HTTP bytes sent: %lld\n", glob_stat.http_bytes_sent);
59  printf("SPDY bytes sent: %lld\n", glob_stat.spdy_bytes_sent);
60  printf("SPDY bytes received: %lld\n", glob_stat.spdy_bytes_received);
61  printf("SPDY bytes received and dropped: %lld\n", glob_stat.spdy_bytes_received_and_dropped);
62}
63
64
65int
66run_everything ()
67{
68  unsigned long long timeoutlong=0;
69  struct timeval timeout;
70  int ret;
71  fd_set rs;
72  fd_set ws;
73  fd_set es;
74  int maxfd = -1;
75  int maxfd_s = -1;
76  struct MHD_Daemon *daemon;
77  nfds_t spdy_npollfds = 1;
78  struct URI * spdy2http_uri = NULL;
79  struct SPDY_Connection *connection;
80  struct SPDY_Connection *connections[MAX_SPDY_CONNECTIONS];
81  struct SPDY_Connection *connection_for_delete;
82
83  if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
84    PRINT_INFO("signal failed");
85
86  if (signal(SIGINT, catch_signal) == SIG_ERR)
87    PRINT_INFO("signal failed");
88
89  glob_opt.streams_opened = 0;
90  glob_opt.responses_pending = 0;
91  //glob_opt.global_memory = 0;
92
93  srand(time(NULL));
94
95  if(init_parse_uri(&glob_opt.uri_preg))
96    DIE("Regexp compilation failed");
97
98  if(NULL != glob_opt.spdy2http_str)
99  {
100    ret = parse_uri(&glob_opt.uri_preg, glob_opt.spdy2http_str, &spdy2http_uri);
101    if(ret != 0)
102      DIE("spdy_parse_uri failed");
103  }
104
105  SSL_load_error_strings();
106  SSL_library_init();
107  glob_opt.ssl_ctx = SSL_CTX_new(SSLv23_client_method());
108  if(glob_opt.ssl_ctx == NULL) {
109    PRINT_INFO2("SSL_CTX_new %s", ERR_error_string(ERR_get_error(), NULL));
110    abort();
111  }
112  spdy_ssl_init_ssl_ctx(glob_opt.ssl_ctx, &glob_opt.spdy_proto_version);
113
114  daemon = MHD_start_daemon (
115          MHD_SUPPRESS_DATE_NO_CLOCK,
116          glob_opt.listen_port,
117          NULL, NULL, &http_cb_request, NULL,
118          MHD_OPTION_URI_LOG_CALLBACK, &http_cb_log, NULL,
119          MHD_OPTION_NOTIFY_COMPLETED, &http_cb_request_completed, NULL,
120          MHD_OPTION_END);
121  if(NULL==daemon)
122    DIE("MHD_start_daemon failed");
123
124  do
125  {
126    timeout.tv_sec = 0;
127    timeout.tv_usec = 0;
128
129    if(NULL == glob_opt.spdy_connection && NULL != glob_opt.spdy2http_str)
130    {
131      glob_opt.spdy_connection = spdy_connect(spdy2http_uri, spdy2http_uri->port, strcmp("https", spdy2http_uri->scheme)==0);
132      if(NULL == glob_opt.spdy_connection && glob_opt.only_proxy)
133        PRINT_INFO("cannot connect to the proxy");
134    }
135
136    FD_ZERO(&rs);
137    FD_ZERO(&ws);
138    FD_ZERO(&es);
139
140    ret = MHD_get_timeout(daemon, &timeoutlong);
141    if(MHD_NO == ret || timeoutlong > 5000)
142      timeout.tv_sec = 5;
143    else
144    {
145      timeout.tv_sec = timeoutlong / 1000;
146      timeout.tv_usec = (timeoutlong % 1000) * 1000;
147    }
148
149    if(MHD_NO == MHD_get_fdset (daemon,
150                                  &rs,
151                                  &ws,
152                                  &es,
153                                  &maxfd))
154    {
155      PRINT_INFO("MHD_get_fdset error");
156    }
157    assert(-1 != maxfd);
158
159    maxfd_s = spdy_get_selectfdset(
160                                  &rs,
161                                  &ws,
162                                  &es,
163                                  connections, MAX_SPDY_CONNECTIONS, &spdy_npollfds);
164    if(maxfd_s > maxfd)
165      maxfd = maxfd_s;
166
167    PRINT_INFO2("MHD timeout %lld %lld", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec);
168
169    glob_opt.spdy_data_received = false;
170
171    ret = select(maxfd+1, &rs, &ws, &es, &timeout);
172    PRINT_INFO2("timeout now %lld %lld ret is %i", (unsigned long long)timeout.tv_sec, (unsigned long long)timeout.tv_usec, ret);
173
174    switch(ret)
175    {
176      case -1:
177        PRINT_INFO2("select error: %i", errno);
178        break;
179      case 0:
180        //break;
181      default:
182      PRINT_INFO("run");
183        //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
184        MHD_run(daemon);
185        spdy_run_select(&rs, &ws, &es, connections, spdy_npollfds);
186        if(glob_opt.spdy_data_received)
187        {
188          PRINT_INFO("MHD run again");
189          //MHD_run_from_select(daemon,&rs, &ws, &es); //not closing FDs at some time in past
190          MHD_run(daemon);
191        }
192        break;
193    }
194  }
195  while(run);
196
197  MHD_stop_daemon (daemon);
198
199  //TODO SSL_free brakes
200  spdy_free_connection(glob_opt.spdy_connection);
201
202  connection = glob_opt.spdy_connections_head;
203  while(NULL != connection)
204  {
205    connection_for_delete = connection;
206    connection = connection_for_delete->next;
207    glob_opt.streams_opened -= connection_for_delete->streams_opened;
208    DLL_remove(glob_opt.spdy_connections_head, glob_opt.spdy_connections_tail, connection_for_delete);
209    spdy_free_connection(connection_for_delete);
210  }
211
212  free_uri(spdy2http_uri);
213
214  deinit_parse_uri(&glob_opt.uri_preg);
215
216  SSL_CTX_free(glob_opt.ssl_ctx);
217  ERR_free_strings();
218  EVP_cleanup();
219
220  PRINT_INFO2("spdy streams: %i; http requests: %i", glob_opt.streams_opened, glob_opt.responses_pending);
221  //PRINT_INFO2("memory allocated %zu bytes", glob_opt.global_memory);
222
223  print_stat();
224
225  return 0;
226}
227
228
229void
230display_usage()
231{
232  printf(
233    "Usage: mhd2spdy [-ovs] [-b <SPDY2HTTP-PROXY>] -p <PORT>\n\n"
234    "OPTIONS:\n"
235    "    -p, --port            Listening port.\n"
236    "    -b, --backend-proxy   If set, he proxy will send requests to\n"
237    "                          that SPDY server or proxy. Set the address\n"
238    "                          in the form 'http://host:port'. Use 'https'\n"
239    "                          for SPDY over TLS, or 'http' for plain SPDY\n"
240    "                          communication with the backend.\n"
241    "    -o, --only-proxy      If set, the proxy will always forward the\n"
242    "                          requests to the backend proxy. If not set,\n"
243    "                          the proxy will first try to establsh SPDY\n"
244    "                          connection to the requested server. If the\n"
245    "                          server does not support SPDY and TLS, the\n"
246    "                          backend proxy will be used for the request.\n"
247    "    -v, --verbose         Print debug information.\n"
248    "    -s, --statistics      Print simple statistics on exit.\n\n"
249
250  );
251}
252
253
254int
255main (int argc,
256      char *const *argv)
257{
258  int getopt_ret;
259  int option_index;
260  struct option long_options[] = {
261    {"port",  required_argument, 0, 'p'},
262    {"backend-proxy",  required_argument, 0, 'b'},
263    {"verbose",  no_argument, 0, 'v'},
264    {"only-proxy",  no_argument, 0, 'o'},
265    {"statistics",  no_argument, 0, 's'},
266    {0, 0, 0, 0}
267  };
268
269  while (1)
270  {
271    getopt_ret = getopt_long( argc, argv, "p:b:vos", long_options, &option_index);
272    if (getopt_ret == -1)
273      break;
274
275    switch(getopt_ret)
276    {
277      case 'p':
278        glob_opt.listen_port = atoi(optarg);
279        break;
280
281      case 'b':
282        glob_opt.spdy2http_str = strdup(optarg);
283        if(NULL == glob_opt.spdy2http_str)
284          return 1;
285        break;
286
287      case 'v':
288        glob_opt.verbose = true;
289        break;
290
291      case 'o':
292        glob_opt.only_proxy = true;
293        break;
294
295      case 's':
296        glob_opt.statistics = true;
297        break;
298
299      case 0:
300        PRINT_INFO("0 from getopt");
301        break;
302
303      case '?':
304        display_usage();
305        return 1;
306
307      default:
308        DIE("default from getopt");
309    }
310  }
311
312  if(
313    0 == glob_opt.listen_port
314    || (glob_opt.only_proxy && NULL == glob_opt.spdy2http_str)
315    )
316  {
317    display_usage();
318    return 1;
319  }
320
321  return run_everything();
322}
323