scclib
Stable Cloud Computing C++ Library
netmain.cc
Go to the documentation of this file.
1 /*
2 BSD 3-Clause License
3 
4 Copyright (c) 2022, Stable Cloud Computing, Inc.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions are met:
8 
9 1. Redistributions of source code must retain the above copyright notice, this
10  list of conditions and the following disclaimer.
11 
12 2. Redistributions in binary form must reproduce the above copyright notice,
13  this list of conditions and the following disclaimer in the documentation
14  and/or other materials provided with the distribution.
15 
16 3. Neither the name of the copyright holder nor the names of its
17  contributors may be used to endorse or promote products derived from
18  this software without specific prior written permission.
19 
20 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31 #include <iostream>
32 #include <cstring>
33 #include <system_error>
34 #include <getopt.h>
35 #include <chrono>
36 #include <thread>
37 #include <net/net_if.h>
38 #include <net/inet.h>
39 #include <util/poller.h>
40 #include <util/event.h>
41 #include <net/inet.h>
42 #include <util/iostream.h>
43 #include <util/logger.h>
44 
51 using std::exception;
52 using std::getline;
53 using std::string;
54 using std::thread;
55 using std::runtime_error;
56 using std::endl;
57 using std::chrono::milliseconds;
58 using std::chrono::seconds;
59 using scc::util::Logger;
60 using scc::util::Event;
61 using scc::util::Poller;
65 using scc::net::NetIf;
66 using scc::net::InetAddr;
69 
70 Logger lout;
71 
72 void print_available_addrs()
73 {
74  lout << endl;
75  lout << "Any addrs:" << endl;
76  InetAddr sa;
77  sa.host("::ffff:0.0.0.0");
78  lout << " " << sa << endl;
79  sa.host("::");
80  lout << " " << sa << endl;
81 
82  auto ifs = NetIf::all_interfaces();
83  for (auto& i : ifs)
84  {
85  lout << "Interface " << i.name() << " (index " << i.index() << "):" << endl;
86  for (auto& s : i.addrs())
87  {
88  lout << " " << s << endl;
89  }
90  }
91 }
92 
93 void print_interfaces()
94 {
95  lout << endl;
96  lout << "Interfaces: " << endl;
97  auto ifs = NetIf::all_interfaces();
98  for (auto& i : ifs)
99  {
100  lout << i.str() << endl;
101  }
102 }
103 
104 string resolve(const string& host, bool udp=false)
105 {
106  auto hads = NetIf::host_addrs(host, udp ? NetIf::SocketType::udp_datagram : NetIf::SocketType::tcp_stream);
107  if (hads.size())
108  {
109  lout << "* " << host << " --> " << hads[0] << endl;
110  return hads[0].host();
111  }
112  return host;
113 }
114 
115 void test_tcp(const string& host, int port, unsigned scope, int timeout)
116 {
117  InetAddr addr;
118  addr.host(host);
119  if (scope) addr.scope_id(scope);
120  addr.port(port);
121 
122  InetTcpSock s;
123 
124  if (timeout >= 0)
125  {
126  lout << "* connecting with " << timeout << " second timeout..." << endl;
127 
128  std::error_code ec;
129  s.non_blocking(true);
130  s.connect(addr, ec); // don't let EINPROGRESS throw an exception
131 
132  if (ec.value() != 0 && ec.value() != EINPROGRESS)
133  {
134  throw std::system_error(ec.value(), std::system_category());
135  }
136 
137  Poller pout;
138  pout.set(s.fd(), Poller::output);
139  pout.wait(seconds(timeout));
140 
141  if (pout.event(s.fd()) == 0)
142  {
143  throw runtime_error("timed out");
144  return;
145  }
146 
147  s.non_blocking(false);
148  s.connect(addr); // this will either connect or throw
149  }
150  else
151  {
152  lout << "* connecting with no timeout" << endl;
153  s.connect(addr);
154  }
155  lout << "* connected OK" << endl;
156 }
157 
158 
159 void listen_tcp(const string& host, int port, unsigned scope)
160 {
161  InetAddr addr;
162  addr.host(host);
163  if (scope) addr.scope_id(scope);
164  addr.port(port);
165 
166  lout << "* server tcp address: " << addr << endl;
167 
168  InetTcpSock s;
169  s.reuse_addr(true);
170 
171  lout << "* tcp binding to address" << endl;
172  s.bind(addr);
173 
174  lout << "* tcp listening" << endl;
175  s.listen();
176 
177  for (;;)
178  {
179  InetAddr from;
180 
181  lout << "* tcp waiting for connection" << endl;
182  auto conn = s.accept(from);
183  lout << "* connection from " << from << endl;
184 
185  IoStream st(conn, conn);
186 
187  string got;
188  while (getline(st, got)) // loop until eof (socket closed)
189  {
190  lout << "echo > " << got << endl;
191  st << got << endl;
192  }
193  lout << "* input stream failed: eof()=" << st.eof() << endl;
194  }
195 }
196 
197 void connect_tcp(const string& host, int port, unsigned scope)
198 {
199  InetAddr addr;
200  addr.host(host);
201  if (scope) addr.scope_id(scope);
202  addr.port(port);
203 
204  lout << "* address: " << addr << endl;
205 
206  InetTcpSock s;
207  s.reuse_addr(true);
208 
209  lout << "* tcp connect..." << endl;
210  s.connect(addr);
211 
212  lout << "* connected, sending keyboard input to server" << endl;
213 
214  Event done;
215 
216  thread t([&s, &done]()
217  {
218  Logger tout;
219  tout.add_cout();
220 
221  InStream st(s);
222 
223  Poller pin;
224  pin.set(done, Poller::input);
225  pin.set(s.fd(), Poller::input);
226 
227  while (1)
228  {
229  pin.wait(milliseconds(100));
230  if (pin.event(done))
231  {
232  tout << "* done signal, thread exit" << endl;
233  break;
234  }
235  std::string got;
236  while (getline(st, got))
237  {
238  tout << "got > " << got << endl;
239  }
240  }
241  });
242 
243  OutStream st(s);
244 
245  for (;;)
246  {
247  std::this_thread::sleep_for(milliseconds(100));
248 
249  string cmd;
250  getline(std::cin, cmd);
251 
252  if (!(st << cmd << endl))
253  {
254  lout << "* output stream failed, exit" << endl;
255  break;
256  }
257  st.flush();
258  }
259 
260  done.write(1);
261  s.close();
262  t.join();
263 }
264 
265 void listen_udp(const string& host, int port, unsigned scope)
266 {
267  InetAddr addr;
268  addr.host(host);
269  if (scope) addr.scope_id(scope);
270  addr.port(port);
271 
272  lout << "* server udp address: " << addr << endl;
273 
274  InetUdpSock s;
275  s.reuse_addr(true);
276 
277  lout << "* udp binding to address" << endl;
278  s.bind(addr);
279 
280  Poller pin;
281  pin.set(s.fd(), Poller::input);
282 
283  string got;
284  for (;;)
285  {
286  pin.wait();
287 
288  auto sz = s.recv_next();
289  InetAddr from;
290  got.resize(sz+1);
291  s.recv(&got[0], sz, from);
292  got[sz] = '\0';
293 
294  lout << "echo to " << from.host() << " > " << got;
295  s.send(got.data(), sz, from);
296  }
297 }
298 
299 void connect_udp(const string& host, int port, unsigned scope)
300 {
301  InetAddr addr;
302  addr.host(host);
303  if (scope) addr.scope_id(scope);
304  addr.port(port);
305 
306  lout << "* connect udp address: " << addr << endl;
307 
308  InetUdpSock s;
309  s.reuse_addr(true);
310 
311  Event done;
312 
313  thread t([&s, &done]()
314  {
315  Logger tout;
316  tout.add_cout();
317 
318  Poller pin;
319  pin.set(s.fd(), Poller::input);
320  pin.set(done, Poller::input);
321 
322  string got;
323  for (;;)
324  {
325  pin.wait();
326  if (pin.event(done))
327  {
328  tout << "* done signal, thread exit" << endl;
329  break;
330  }
331  if (pin.event(s.fd()))
332  {
333  auto sz = s.recv_next();
334 
335  InetAddr from;
336  got.resize(sz+1);
337  if (s.recv(&got[0], sz, from) <= 0)
338  {
339  tout << "* recv failed, thread exit" << endl;
340  break;
341  }
342  got[sz] = '\0';
343 
344  tout << "got from " << from << " > " << got;
345  }
346  }
347  });
348 
349  lout << "* sending keyboard input to server" << endl;
350 
351  for (;;)
352  {
353  std::this_thread::sleep_for(milliseconds(100));
354 
355  string cmd;
356  getline(std::cin, cmd);
357 
358  cmd += '\n';
359 
360  if (s.send(&cmd[0], cmd.size(), addr) <= 0)
361  {
362  lout << "* send failed, exit" << endl;
363  break;
364  }
365  }
366 
367  done.write(1);
368  s.close();
369  t.join();
370 }
371 
372 void test_connection(const string& host, int port, unsigned scope, int timeout)
373 {
374  try
375  {
376  test_tcp(resolve(host), port, scope, timeout);
377  }
378  catch (exception& ex)
379  {
380  lout << "* connection failed: " << ex.what() << endl;
381  }
382 
383  exit(0);
384 }
385 
386 void server(const string& host, int port, bool udp, unsigned scope)
387 {
388  try
389  {
390  if (udp)
391  {
392  listen_udp(resolve(host, true), port, scope);
393  }
394  else
395  {
396  listen_tcp(resolve(host), port, scope);
397  }
398  }
399  catch (exception& ex)
400  {
401  lout << "* server failed: " << ex.what() << endl;
402  }
403 
404  exit(0);
405 }
406 
407 void client(const string& host, int port, bool udp, unsigned scope)
408 {
409  try
410  {
411  if (udp)
412  {
413  connect_udp(resolve(host, true), port, scope);
414  }
415  else
416  {
417  connect_tcp(resolve(host), port, scope);
418  }
419  }
420  catch (exception& ex)
421  {
422  lout << "* client failed: " << ex.what() << endl;
423  }
424  exit(0);
425 }
426 
427 int main(int argc, char **argv)
428 {
429  option lo[32];
430  memset(&lo[0], 0, 32*sizeof(option));
431  lo[0].name = "help";
432  lo[0].val = '?';
433  lo[1].name = "ifs";
434  lo[1].val = 'I';
435  //lo[1].has_arg = 0;
436  lo[2].name = "addrs";
437  lo[2].val = 'A';
438  lo[3].name = "listen";
439  lo[3].val = 'l';
440  lo[4].name = "udp";
441  lo[4].val = 'u';
442  lo[5].name = "test";
443  lo[5].val = 'T';
444  lo[6].name = "resolve";
445  lo[6].val = 'R';
446  lo[7].name = "scope";
447  lo[7].val = 's';
448  lo[7].has_arg = 1;
449 
450  lout.add_cout();
451 
452  bool usage=false, listen=false, udp=false, test=false, resolve=false;
453  unsigned scope=0;
454  string mcast;
455  while (1)
456  {
457  int opt = getopt_long(argc, argv, "?IAluTRs:", &lo[0], nullptr);
458  if (opt == -1)
459  {
460  break;
461  }
462  switch (opt)
463  {
464  case '?':
465  usage = true;
466  break;
467  case 'I':
468  print_interfaces();
469  exit(0);
470  break;
471  case 'A':
472  print_available_addrs();
473  exit(0);
474  break;
475  case 'l':
476  listen = true;
477  break;
478  case 'u':
479  udp = true;
480  break;
481  case 'T':
482  test = true;
483  break;
484  case 'R':
485  resolve = true;
486  break;
487  case 's':
488  if (!optarg) usage = true;
489  else scope = atoi(optarg);
490  break;
491 
492  default:
493  usage = true;
494  }
495  }
496 
497  int nargs = argc-optind;
498 
499  if (resolve)
500  {
501  if (nargs < 1) usage = true;
502  }
503  else
504  {
505  if (nargs < 2) usage = true;
506  }
507 
508  if (usage)
509  {
510  using std::cerr;
511  cerr << argv[0] << endl;
512  cerr << " scclib net module example" << endl;
513 
514  cerr << " For ipv4 addresses, use ipv4/6 syntax, e.g. ::ffff:192.168.1.1" << endl;
515  cerr << endl;
516  cerr << " Informational:" << endl;
517  cerr << " -I|--ifs print out interfaces and return" << endl;
518  cerr << " -A|--addrs print out addrs and return" << endl;
519  cerr << " -R|--resolve HOST1 HOST2 .. resolve host(s) and return" << endl;
520  cerr << " Test:" << endl;
521  cerr << " -T|--test HOST PORT [secs] perform tcp connection test and return" << endl;
522  cerr << " Client:" << endl;
523  cerr << " HOST PORT connect and send keyboard input" << endl;
524  cerr << " Server:" << endl;
525  cerr << " -l HOST PORT listen and echo" << endl;
526  cerr << " Common params for client/server:" << endl;
527  cerr << " -u|--udp udp mode" << endl;
528  cerr << " -s|--scope <NUM> set scope_id for address" << endl;
529  exit(1);
530  }
531 
532  if (resolve)
533  {
534  while (optind < argc)
535  {
536  lout << "* resolving " << argv[optind] << endl;
537  auto hads = NetIf::host_addrs(argv[optind], udp ? NetIf::SocketType::udp_datagram : NetIf::SocketType::tcp_stream);
538  if (!hads.size())
539  {
540  lout << "* not resolved" << endl;
541  }
542  else for (auto& i : hads)
543  {
544  lout << "* " << i << endl;
545  }
546  optind++;
547  }
548  }
549  else if (test)
550  {
551  test_connection(argv[optind], atoi(argv[optind+1]), scope, optind+2 < argc ? atoi(argv[optind+2]) : -1);
552  }
553  else if (listen)
554  {
555  server(argv[optind], atoi(argv[optind+1]), udp, scope);
556  }
557  else
558  {
559  client(argv[optind], atoi(argv[optind+1]), udp, scope);
560  }
561  return 0;
562 }
Ipv6 internet address.
Definition: inet.h:120
unsigned port() const
Get the port.
Definition: inet.cc:159
void scope_id(uint32_t)
Set the scope id of the address.
Definition: inet.cc:165
virtual std::string host() const
Get host.
Definition: inet.cc:188
Internet transmission control protocol (tcp) socket.
Definition: inet.h:251
InetTcpSock accept()
Accept a connection from an anonymous peer.
Definition: inet.cc:430
Internet user datagram protocol (udp) socket.
Definition: inet.h:295
A network interface.
Definition: net_if.h:90
int fd() const
Return the underlying socket handle.
Definition: socket.h:112
void non_blocking(bool b=true)
Set the socket non-blocking.
Definition: socket.cc:188
void bind(const SockaddrBase &)
Bind an address to the socket.
Definition: socket.cc:313
void reuse_addr(bool r=true)
Set address reusable.
Definition: socket.cc:162
void close()
Close the socket.
Definition: socket.cc:72
void listen(int maxConnections=10)
Set to accept connections.
Definition: socket.cc:334
void connect(SockaddrBase &)
Connect to a socket address.
Definition: socket.cc:397
size_t recv(void *loc, size_t len, SockaddrBase &s)
Receive bytes (a datagram), setting the socket address of the peer.
Definition: socket.cc:453
size_t send(const void *loc, size_t len, const SockaddrBase &d)
Send bytes (a datagram) to a peer address.
Definition: socket.cc:486
size_t recv_next()
Return the number of bytes available to read (the size of the next datagram).
Definition: socket.cc:499
Signaling kernel event counter.
Definition: event.h:79
void write(uint64_t)
Write to (increment) the event counter.
Definition: event.cc:94
Input stream wrapper for reader.
Definition: iostream.h:59
Input/output stream wrapper for reader/writer.
Definition: iostream.h:157
Thread-safe stream logger.
Definition: logger.h:86
void add_cout()
Add std::cout console stream.
Definition: logger.cc:418
Output stream wrapper for writer.
Definition: iostream.h:108
Poller which allows polling of generic file descriptors for various events.
Definition: poller.h:66
void set(int, int)
Add a file desriptor to poller.
Definition: poller.cc:54
int event(int)
Return flags which were polled for this file descriptor.
Definition: poller.cc:171
Signaling kernel event counter.
Internet tcp and udp networking.
Base input/output stream classes.
Thread safe logging.
Internet network interface utility.
Linux kernel i/o event notification (poller).