1 /* Copyright (C) 2002 J.F. Dockes
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU Lesser General Public License as published by
4 * the Free Software Foundation; either version 2.1 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU Lesser General Public License for more details.
11 *
12 * You should have received a copy of the GNU Lesser General Public License
13 * along with this program; if not, write to the
14 * Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16 */
17
18 // Wrapper classes for the socket interface
19
20 #ifdef BUILDING_RECOLL
21 #include "autoconfig.h"
22 #else
23 #include "config.h"
24 #endif
25
26 #include "netcon.h"
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <errno.h>
32 #include <stdint.h>
33
34 #ifdef _AIX
35 #include <strings.h>
36 #endif // _AIX
37
38 #include <unistd.h>
39 #include <fcntl.h>
40 #include <sys/socket.h>
41 #include <sys/un.h>
42 #include <netinet/in.h>
43 #include <netinet/tcp.h>
44 #include <arpa/inet.h>
45 #include <netdb.h>
46 #ifdef HAVE_KQUEUE
47 #include <sys/types.h>
48 #include <sys/event.h>
49 #include <vector>
50 #endif
51
52 #include <map>
53 #include <algorithm>
54
55 #ifdef MDU_INCLUDE_LOG
56 #include MDU_INCLUDE_LOG
57 #else
58 #include "log.h"
59 #endif
60
61 using namespace std;
62
63 #ifndef PRETEND_USE
64 #define PRETEND_USE(expr) ((void)(expr))
65 #endif
66
67 #ifndef SOCKLEN_T
68 #define SOCKLEN_T socklen_t
69 #endif
70
71 // Size of path buffer in sockaddr_un (AF_UNIX socket
72 // addr). Mysteriously it is 108 (explicit value) under linux, no
73 // define accessible. Let's take a little margin as it appears that
74 // some systems use 92. I believe we could also malloc a variable size
75 // struct but why bother.
76 #ifndef UNIX_PATH_MAX
77 #define UNIX_PATH_MAX 90
78 #endif
79
80 // Need &one, &zero for setsockopt...
81 static const int one = 1;
82 static const int zero = 0;
83
84 #ifndef LOGSYSERR
85 #define LOGSYSERR(who, call, spar) \
86 LOGERR(who << ": " << call << "(" << spar << ") errno " << \
87 errno << " (" << strerror(errno) << ")\n")
88 #endif
89
90 #ifndef freeZ
91 #define freeZ(X) if (X) {free(X);X=0;}
92 #endif
93
94 #define MILLIS(OLD, NEW) ( (uint64_t((NEW).tv_sec) - (OLD).tv_sec) * 1000 + \
95 ((NEW).tv_usec - (OLD).tv_usec) / 1000 )
96
97 // Static method
98 // Simplified interface to 'select()'. Only use one fd, for either
99 // reading or writing. This is only used when not using the
100 // selectloop() style of network i/o.
101 // Note that timeo == 0 does NOT mean wait forever but no wait at all.
select1(int fd,int timeo,int write)102 int Netcon::select1(int fd, int timeo, int write)
103 {
104 int ret;
105 struct timeval tv;
106 fd_set rd;
107 tv.tv_sec = timeo;
108 tv.tv_usec = 0;
109 FD_ZERO(&rd);
110 FD_SET(fd, &rd);
111 if (write) {
112 ret = select(fd + 1, 0, &rd, 0, &tv);
113 } else {
114 ret = select(fd + 1, &rd, 0, 0, &tv);
115 }
116 if (!FD_ISSET(fd, &rd)) {
117 LOGDEB2("Netcon::select1: fd " << fd << " timeout\n");
118 }
119 return ret;
120 }
121
122
123 ///////////////////////////////////////////
124 // SelectLoop
125
126 class SelectLoop::Internal {
127 public:
Internal()128 Internal() {
129 #ifdef HAVE_KQUEUE
130 if ((kq = kqueue()) == -1) {
131 LOGSYSERR("Netcon::selectloop", "kqueue", "");
132 }
133 #endif
134 }
135
~Internal()136 ~Internal() {
137 #ifdef HAVE_KQUEUE
138 if (kq >= 0)
139 close(kq);
140 #endif
141 }
142
143 // Set by client callback to tell selectloop to return.
144 bool selectloopDoReturn{false};
145 int selectloopReturnValue{0};
146 int placetostart{0};
147
148 // Map of NetconP indexed by fd
149 map<int, NetconP> polldata;
150 #ifdef HAVE_KQUEUE
151 int kq{-1};
152 #endif
153 // The last time we did the periodic thing. Initialized by setperiodic()
154 struct timeval lasthdlcall;
155
156 // The call back function and its parameter
157 int (*periodichandler)(void *){0};
158 void *periodicparam{0};
159 // The periodic interval
160 int periodicmillis{0};
161
162 void periodictimeout(struct timeval *tv);
163 void periodictimeout(struct timespec *ts);
164 int maybecallperiodic();
165 int setselevents(int fd, int events);
166 int setselevents(NetconP& con, int events);
167 };
168
SelectLoop()169 SelectLoop::SelectLoop()
170 {
171 m = new Internal;
172 }
173
~SelectLoop()174 SelectLoop::~SelectLoop()
175 {
176 delete m;
177 }
178
loopReturn(int value)179 void SelectLoop::loopReturn(int value)
180 {
181 m->selectloopDoReturn = true;
182 m->selectloopReturnValue = value;
183 }
184
setperiodichandler(int (* handler)(void *),void * p,int ms)185 void SelectLoop::setperiodichandler(int (*handler)(void *), void *p, int ms)
186 {
187 m->periodichandler = handler;
188 m->periodicparam = p;
189 m->periodicmillis = ms;
190 if (m->periodicmillis > 0) {
191 gettimeofday(&m->lasthdlcall, 0);
192 }
193 }
194
195 // Compute the appropriate timeout so that the select call returns in
196 // time to call the periodic routine.
periodictimeout(struct timeval * tv)197 void SelectLoop::Internal::periodictimeout(struct timeval *tv)
198 {
199 // If periodic not set, the select call times out and we loop
200 // after a very long time (we'd need to pass NULL to select for an
201 // infinite wait, and I'm too lazy to handle it)
202 if (periodicmillis <= 0) {
203 tv->tv_sec = 10000;
204 tv->tv_usec = 0;
205 return;
206 }
207
208 struct timeval mtv;
209 gettimeofday(&mtv, 0);
210 int millis = periodicmillis - MILLIS(lasthdlcall, mtv);
211
212 // millis <= 0 means we should have already done the thing. *dont* set the
213 // tv to 0, which means no timeout at all !
214 if (millis <= 0) {
215 millis = 1;
216 }
217 tv->tv_sec = millis / 1000;
218 tv->tv_usec = (millis % 1000) * 1000;
219 }
220
periodictimeout(struct timespec * ts)221 void SelectLoop::Internal::periodictimeout(struct timespec *ts)
222 {
223 struct timeval tv;
224 periodictimeout(&tv);
225 ts->tv_sec = tv.tv_sec;
226 ts->tv_nsec = tv.tv_usec * 1000;
227 }
228
229
230 // Check if it's time to call the handler. selectloop will return to
231 // caller if either we or the handler return 0
maybecallperiodic()232 int SelectLoop::Internal::maybecallperiodic()
233 {
234 if (periodicmillis <= 0) {
235 return 1;
236 }
237
238 struct timeval mtv;
239 gettimeofday(&mtv, 0);
240 int millis = periodicmillis - MILLIS(lasthdlcall, mtv);
241
242 if (millis <= 0) {
243 lasthdlcall = mtv;
244 if (periodichandler) {
245 return periodichandler(periodicparam);
246 } else {
247 return 0;
248 }
249 }
250 return 1;
251 }
252
253 #ifndef HAVE_KQUEUE
254
doLoop()255 int SelectLoop::doLoop()
256 {
257 for (;;) {
258 if (m->selectloopDoReturn) {
259 m->selectloopDoReturn = false;
260 LOGDEB("Netcon::selectloop: returning on request\n");
261 return m->selectloopReturnValue;
262 }
263
264 int nfds;
265 fd_set rd, wd;
266 FD_ZERO(&rd);
267 FD_ZERO(&wd);
268
269 // Walk the netcon map and set up the read and write fd_sets
270 // for select()
271 nfds = 0;
272 for (auto& entry : m->polldata) {
273 NetconP& pll = entry.second;
274 int fd = entry.first;
275 LOGDEB2("Selectloop: fd " << fd << " flags 0x" <<
276 pll->m_wantedEvents << "\n");
277 if (pll->m_wantedEvents & Netcon::NETCONPOLL_READ) {
278 FD_SET(fd, &rd);
279 nfds = std::max(nfds, fd + 1);
280 }
281 if (pll->m_wantedEvents & Netcon::NETCONPOLL_WRITE) {
282 FD_SET(fd, &wd);
283 nfds = std::max(nfds, fd + 1);
284 }
285 }
286
287 if (nfds == 0) {
288 // This should never happen in a server as we should at least
289 // always monitor the main listening server socket. For a
290 // client, it's up to client code to avoid or process this
291 // condition.
292
293 // Just in case there would still be open fds in there
294 // (with no r/w flags set). Should not be needed, but safer
295 m->polldata.clear();
296 LOGDEB1("Netcon::selectloop: no fds\n");
297 return 0;
298 }
299
300 LOGDEB2("Netcon::selectloop: selecting, nfds = " << nfds << "\n");
301
302 // Compute the next timeout according to what might need to be
303 // done apart from waiting for data
304 struct timeval tv;
305 m->periodictimeout(&tv);
306 // Wait for something to happen
307 int ret = select(nfds, &rd, &wd, 0, &tv);
308 LOGDEB2("Netcon::selectloop: nfds " << nfds <<
309 " select returns " << ret << "\n");
310 if (ret < 0) {
311 LOGSYSERR("Netcon::selectloop", "select", "");
312 return -1;
313 }
314 if (m->periodicmillis > 0 && m->maybecallperiodic() <= 0) {
315 return 1;
316 }
317
318 // Timeout, do it again.
319 if (ret == 0) {
320 continue;
321 }
322
323 // Select returned > 0: at least one fd must be ready. Sweep the fd
324 // table and act on the ready ones.
325 // We don't start the fd sweep at 0, else some fds would be advantaged.
326 // Note that we do an fd sweep, not a map sweep. This is
327 // inefficient because the fd array may be very sparse. Otoh, the
328 // map may change between 2 sweeps, so that we'd have to be smart
329 // with the iterator. As the cost per unused fd is low (just 2 bit
330 // flag tests), we keep it like this for now
331 if (m->placetostart >= nfds) {
332 m->placetostart = 0;
333 }
334 int i, fd;
335 int activefds = 0;
336 for (i = 0, fd = m->placetostart; i < nfds; i++, fd++) {
337 if (fd >= nfds) {
338 fd = 0;
339 }
340
341 int canread = FD_ISSET(fd, &rd);
342 int canwrite = FD_ISSET(fd, &wd);
343 bool none = !canread && !canwrite;
344 LOGDEB2("Netcon::selectloop: fd " << fd << " " <<
345 (none ? "blocked" : "can") << " " <<
346 (canread ? "read" : "") << " " <<
347 (canwrite ? "write" : "") << "\n");
348 if (none) {
349 continue;
350 }
351
352 auto it = m->polldata.find(fd);
353 if (it == m->polldata.end()) {
354 // This should never happen, because we only set our
355 // own fds in the mask !
356 LOGERR("Netcon::selectloop: fd " << fd << " not found\n");
357 continue;
358 }
359 activefds++;
360 // Next start will be one beyond last serviced (modulo nfds)
361 m->placetostart = fd + 1;
362
363 NetconP& pll = it->second;
364 if (canread && pll->cando(Netcon::NETCONPOLL_READ) <= 0) {
365 pll->m_wantedEvents &= ~Netcon::NETCONPOLL_READ;
366 }
367 if (canwrite && pll->cando(Netcon::NETCONPOLL_WRITE) <= 0) {
368 pll->m_wantedEvents &= ~Netcon::NETCONPOLL_WRITE;
369 }
370 if (!(pll->m_wantedEvents &
371 (Netcon::NETCONPOLL_WRITE | Netcon::NETCONPOLL_READ))) {
372 LOGDEB0("Netcon::selectloop: fd " << it->first << " has 0x"
373 << it->second->m_wantedEvents << " mask, erasing\n");
374 m->polldata.erase(it);
375 }
376 } // fd sweep
377
378 if (ret > 0 && activefds != ret) {
379 LOGERR("Select returned " << ret << " not equal to " <<
380 activefds << " active fd found\n");
381 return -1;
382 }
383 } // forever loop
384 LOGERR("SelectLoop::doLoop: got out of loop !\n");
385 return -1;
386 }
387
388 #else // -> Using kqueue: use select()
389
doLoop()390 int SelectLoop::doLoop()
391 {
392 for (;;) {
393 if (m->selectloopDoReturn) {
394 m->selectloopDoReturn = false;
395 LOGDEB("Netcon::selectloop: returning on request\n");
396 return m->selectloopReturnValue;
397 }
398
399 // Check that we do have something to wait for.
400 int nfds = 0;
401 for (auto& entry : m->polldata) {
402 NetconP& pll = entry.second;
403 if (pll->m_wantedEvents & Netcon::NETCONPOLL_READ) {
404 nfds++;
405 } else if (pll->m_wantedEvents & Netcon::NETCONPOLL_WRITE) {
406 nfds++;
407 }
408 }
409 if (nfds == 0) {
410 // This should never happen in a server as we should at least
411 // always monitor the main listening server socket. For a
412 // client, it's up to client code to avoid or process this
413 // condition.
414
415 // Just in case there would still be open fds in there
416 // (with no r/w flags set). Should not be needed, but safer
417 m->polldata.clear();
418 LOGDEB1("Netcon::selectloop: no fds\n");
419 return 0;
420 }
421
422 // Compute the next timeout according to what might need to be
423 // done apart from waiting for data
424 struct timespec ts;
425 m->periodictimeout(&ts);
426 // Wait for something to happen
427 vector<struct kevent> events;
428 events.resize(nfds);
429 LOGDEB1("Netcon::selectloop: kevent(), nfds = " << nfds << "\n");
430 int ret = kevent(m->kq, 0, 0, &events[0], events.size(), &ts);
431 LOGDEB1("Netcon::selectloop: nfds " << nfds <<
432 " kevent returns " << ret << "\n");
433 if (ret < 0) {
434 LOGSYSERR("Netcon::selectloop", "kevent", "");
435 return -1;
436 }
437 if (m->periodicmillis > 0 && m->maybecallperiodic() <= 0) {
438 return 1;
439 }
440 if (ret == 0) {
441 // Timeout, do it again.
442 continue;
443 }
444
445 for (int i = 0; i < ret; i++) {
446 struct kevent& ev = events[i];
447 if (ev.flags & EV_ERROR) {
448 LOGSYSERR("Netcon::selectLoop", "kevent", "");
449 LOGERR("Netcon::selectLoop: event error: " <<
450 strerror(ev.data));
451 return -1;
452 }
453 int canread = ev.filter == EVFILT_READ;
454 int canwrite = ev.filter == EVFILT_WRITE;
455 bool none = !canread && !canwrite;
456 LOGDEB1("Netcon::selectloop: fd " << int(ev.ident) << " " <<
457 (none ? "blocked" : "can") << " " <<
458 (canread ? "read" : "") << " " <<
459 (canwrite ? "write" : "") << "\n");
460 if (none) {
461 LOGERR("Kevent returned unknown filter " << ev.filter <<endl);
462 continue;
463 }
464
465 auto it = m->polldata.find(int(ev.ident));
466 if (it == m->polldata.end()) {
467 LOGERR("Netcon::selectloop: fd " << int(ev.ident) <<
468 " not found\n");
469 continue;
470 }
471 NetconP& pll = it->second;
472 if (canread && pll->cando(Netcon::NETCONPOLL_READ) <= 0) {
473 pll->setselevents(pll->getselevents() &
474 ~Netcon::NETCONPOLL_READ);
475 }
476 if (canwrite && pll->cando(Netcon::NETCONPOLL_WRITE) <= 0) {
477 pll->setselevents(pll->getselevents() &
478 ~Netcon::NETCONPOLL_WRITE);
479 }
480 if (!(pll->getselevents() &
481 (Netcon::NETCONPOLL_WRITE | Netcon::NETCONPOLL_READ))) {
482 LOGDEB0("Netcon::selectloop: fd " << it->first << " has 0x"
483 << it->second->getselevents() << " mask, erasing\n");
484 m->polldata.erase(it);
485 }
486 } // fd sweep
487
488 } // forever loop
489 LOGERR("SelectLoop::doLoop: got out of loop !\n");
490 return -1;
491 }
492
493 #endif // kqueue version
494
setselevents(int fd,int events)495 int SelectLoop::Internal::setselevents(int fd, int events)
496 {
497 #ifdef HAVE_KQUEUE
498 auto it = polldata.find(fd);
499 if (it == polldata.end()) {
500 return -1;
501 }
502 return setselevents(it->second, events);
503 #endif
504 return 0;
505 }
506
setselevents(NetconP & con,int events)507 int SelectLoop::Internal::setselevents(NetconP& con, int events)
508 {
509 #ifdef HAVE_KQUEUE
510 struct kevent event;
511 if (events & Netcon::NETCONPOLL_READ) {
512 EV_SET(&event, con->m_fd, EVFILT_READ, EV_ADD, 0, 0, 0);
513 if(kevent(kq, &event, 1, 0, 0, 0) < 0) {
514 LOGSYSERR("SelectLoop::addselcon", "kevent", "");
515 return -1;
516 }
517 } else {
518 EV_SET(&event, con->m_fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
519 kevent(kq, &event, 1, 0, 0, 0);
520 }
521 if (events & Netcon::NETCONPOLL_WRITE) {
522 EV_SET(&event, con->m_fd, EVFILT_WRITE, EV_ADD, 0, 0, 0);
523 if(kevent(kq, &event, 1, 0, 0, 0) < 0) {
524 LOGSYSERR("SelectLoop::addselcon", "kevent", "");
525 return -1;
526 }
527 } else {
528 EV_SET(&event, con->m_fd, EVFILT_WRITE, EV_DELETE, 0, 0, 0);
529 kevent(kq, &event, 1, 0, 0, 0);
530 }
531 #endif
532 return 0;
533 }
534
535 // Add a connection to the monitored set. This can be used to change
536 // the event flags too (won't add duplicates)
addselcon(NetconP con,int events)537 int SelectLoop::addselcon(NetconP con, int events)
538 {
539 if (!con) {
540 return -1;
541 }
542 LOGDEB1("Netcon::addselcon: fd " << con->m_fd << "\n");
543 con->set_nonblock(1);
544 con->m_wantedEvents = events;
545 m->polldata[con->m_fd] = con;
546 con->setloop(this);
547 return m->setselevents(con, events);
548 }
549
550 // Remove a connection from the monitored set.
remselcon(NetconP con)551 int SelectLoop::remselcon(NetconP con)
552 {
553 if (!con) {
554 return -1;
555 }
556 LOGDEB1("Netcon::remselcon: fd " << con->m_fd << "\n");
557 m->setselevents(con, 0);
558 auto it = m->polldata.find(con->m_fd);
559 if (it == m->polldata.end()) {
560 LOGDEB1("Netcon::remselcon: con not found for fd " <<
561 con->m_fd << "\n");
562 return -1;
563 }
564 con->setloop(0);
565 m->polldata.erase(it);
566 return 0;
567 }
568
569 //////////////////////////////////////////////////////////
570 // Base class (Netcon) methods
~Netcon()571 Netcon::~Netcon()
572 {
573 closeconn();
574 if (m_peer) {
575 free(m_peer);
576 m_peer = 0;
577 }
578 }
579
closeconn()580 void Netcon::closeconn()
581 {
582 if (m_ownfd && m_fd >= 0) {
583 close(m_fd);
584 }
585 m_fd = -1;
586 m_ownfd = true;
587 }
588
sterror()589 char *Netcon::sterror()
590 {
591 return strerror(errno);
592 }
593
setpeer(const char * hostname)594 void Netcon::setpeer(const char *hostname)
595 {
596 if (m_peer) {
597 free(m_peer);
598 }
599 m_peer = strdup(hostname);
600 }
601
settcpnodelay(int on)602 int Netcon::settcpnodelay(int on)
603 {
604 LOGDEB2("Netcon::settcpnodelay\n");
605 if (m_fd < 0) {
606 LOGERR("Netcon::settcpnodelay: connection not opened\n");
607 return -1;
608 }
609 char *cp = on ? (char *)&one : (char *)&zero;
610 if (setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, cp, sizeof(one)) < 0) {
611 LOGSYSERR("NetconCli::settcpnodelay", "setsockopt", "TCP_NODELAY");
612 return -1;
613 }
614 return 0;
615 }
616
617
618 // Set/reset non-blocking flag on fd
set_nonblock(int onoff)619 int Netcon::set_nonblock(int onoff)
620 {
621 int flags = fcntl(m_fd, F_GETFL, 0);
622 if (flags != -1) {
623 int newflags = onoff ? flags | O_NONBLOCK : flags & ~O_NONBLOCK;
624 if (newflags != flags)
625 if (fcntl(m_fd, F_SETFL, newflags) < 0) {
626 return -1;
627 }
628 }
629 return flags;
630 }
631
setselevents(int events)632 int Netcon::setselevents(int events)
633 {
634 m_wantedEvents = events;
635 if (m_loop) {
636 m_loop->m->setselevents(m_fd, events);
637 }
638 return m_wantedEvents;
639 }
640
641 /////////////////////////////////////////////////////////////////////
642 // Data socket (NetconData) methods
643
NetconData(bool cancellable)644 NetconData::NetconData(bool cancellable)
645 : m_buf(0), m_bufbase(0), m_bufbytes(0), m_bufsize(0), m_wkfds{-1,-1}
646 {
647 if (cancellable) {
648 if (pipe(m_wkfds) < 0) {
649 LOGSYSERR("NetconData::NetconData", "pipe", "");
650 m_wkfds[0] = m_wkfds[1] = -1;
651 }
652 LOGDEB2("NetconData:: m_wkfds[0] " << m_wkfds[0] << " m_wkfds[1] " <<
653 m_wkfds[1] << endl);
654 for (int i = 0; i < 2; i++) {
655 int flags = fcntl(m_wkfds[i], F_GETFL, 0);
656 fcntl(m_wkfds[i], F_SETFL, flags | O_NONBLOCK);
657 }
658 }
659 }
660
~NetconData()661 NetconData::~NetconData()
662 {
663 freeZ(m_buf);
664 m_bufbase = 0;
665 m_bufbytes = m_bufsize = 0;
666 for (int i = 0; i < 2; i++) {
667 if (m_wkfds[i] >= 0) {
668 close(m_wkfds[i]);
669 }
670 }
671 }
672
send(const char * buf,int cnt,int expedited)673 int NetconData::send(const char *buf, int cnt, int expedited)
674 {
675 LOGDEB2("NetconData::send: fd " << m_fd << " cnt " << cnt <<
676 " expe " << expedited << "\n");
677 int flag = 0;
678 if (m_fd < 0) {
679 LOGERR("NetconData::send: connection not opened\n");
680 return -1;
681 }
682 if (expedited) {
683 LOGDEB2("NetconData::send: expedited data, count " <<cnt << " bytes\n");
684 flag = MSG_OOB;
685 }
686 int ret;
687 // There is a bug in the uthread version of sendto() in FreeBSD at
688 // least up to 2.2.7, so avoid using it when possible
689 if (flag) {
690 ret = ::send(m_fd, buf, cnt, flag);
691 } else {
692 ret = ::write(m_fd, buf, cnt);
693 }
694
695 // Note: byte count may be different from cnt if fd is non-blocking
696 if (ret < 0) {
697 char fdcbuf[20];
698 sprintf(fdcbuf, "%d", m_fd);
699 LOGSYSERR("NetconData::send", "send", fdcbuf);
700 }
701 return ret;
702 }
703
cancelReceive()704 void NetconData::cancelReceive()
705 {
706 if (m_wkfds[1] >= 0) {
707 LOGDEB2("NetconData::cancelReceive: writing to " << m_wkfds[1] << endl);
708 // We can't do a thing about the ::write return value, the
709 // following nonsense is for cancelling warnings
710 int ret = ::write(m_wkfds[1], "!", 1);
711 PRETEND_USE(ret);
712 }
713 }
714
715 // Receive at most cnt bytes (maybe less)
receive(char * buf,int cnt,int timeo)716 int NetconData::receive(char *buf, int cnt, int timeo)
717 {
718 LOGDEB2("NetconData::receive: cnt " << cnt << " timeo " << timeo <<
719 " m_buf 0x" << m_buf << " m_bufbytes " << m_bufbytes << "\n");
720
721 if (m_fd < 0) {
722 LOGERR("NetconData::receive: connection not opened\n");
723 return -1;
724 }
725
726 int fromibuf = 0;
727 // Get whatever might have been left in the buffer by a previous
728 // getline, except if we're called to fill the buffer of course
729 if (m_buf && m_bufbytes > 0 && (buf < m_buf || buf > m_buf + m_bufsize)) {
730 fromibuf = std::min(m_bufbytes, cnt);
731 memcpy(buf, m_bufbase, fromibuf);
732 m_bufbytes -= fromibuf;
733 m_bufbase += fromibuf;
734 cnt -= fromibuf;
735 LOGDEB2("NetconData::receive: got " << fromibuf << " from mbuf\n");
736 if (cnt <= 0) {
737 return fromibuf;
738 }
739 }
740
741 if (timeo > 0) {
742 struct timeval tv;
743 tv.tv_sec = timeo;
744 tv.tv_usec = 0;
745 fd_set rd;
746 FD_ZERO(&rd);
747 FD_SET(m_fd, &rd);
748 bool cancellable = (m_wkfds[0] >= 0);
749 if (cancellable) {
750 LOGDEB2("NetconData::receive: cancel fd " << m_wkfds[0] << endl);
751 FD_SET(m_wkfds[0], &rd);
752 }
753 int nfds = std::max(m_fd, m_wkfds[0]) + 1;
754
755 int ret = select(nfds, &rd, 0, 0, &tv);
756 LOGDEB2("NetconData::receive: select returned " << ret << endl);
757
758 if (cancellable && FD_ISSET(m_wkfds[0], &rd)) {
759 char b[100];
760 // We can't do a thing about the return value, the
761 // following nonsense is for cancelling warnings
762 int ret = ::read(m_wkfds[0], b, 100);
763 PRETEND_USE(ret);
764 return Cancelled;
765 }
766
767 if (!FD_ISSET(m_fd, &rd)) {
768 m_didtimo = 1;
769 return TimeoutOrError;
770 }
771
772 if (ret < 0) {
773 LOGSYSERR("NetconData::receive", "select", "");
774 m_didtimo = 0;
775 return TimeoutOrError;
776 }
777 }
778
779 m_didtimo = 0;
780 if ((cnt = read(m_fd, buf + fromibuf, cnt)) < 0) {
781 LOGSYSERR("NetconData::receive", "read", m_fd);
782 return -1;
783 }
784 LOGDEB2("NetconData::receive: normal return, fromibuf " << fromibuf <<
785 " cnt " << cnt << "\n");
786 return fromibuf + cnt;
787 }
788
789 // Receive exactly cnt bytes (except for timeout)
doreceive(char * buf,int cnt,int timeo)790 int NetconData::doreceive(char *buf, int cnt, int timeo)
791 {
792 int got, cur;
793 LOGDEB2("Netcon::doreceive: cnt " << cnt << ", timeo " << timeo << "\n");
794 cur = 0;
795 while (cnt > cur) {
796 got = receive(buf, cnt - cur, timeo);
797 LOGDEB2("Netcon::doreceive: got " << got << "\n");
798 if (got < 0) {
799 return got;
800 }
801 if (got == 0) {
802 return cur;
803 }
804 cur += got;
805 buf += got;
806 }
807 return cur;
808 }
809
810 // Read data until cnt-1 characters are read or a newline is found. Add
811 // null char at end of buffer and return.
812 // As we don't know where the newline will be and it would be inefficient to
813 // read a character at a time, we use a buffer
814 // Unlike fgets, we return an integer status:
815 // >0: number of characters returned, not including the final 0
816 // 0: EOF reached, no chars transferred
817 // -1: error
818 static const int defbufsize = 200;
getline(char * buf,int cnt,int timeo)819 int NetconData::getline(char *buf, int cnt, int timeo)
820 {
821 LOGDEB2("NetconData::getline: cnt " << cnt << ", timeo " <<
822 timeo << "\n");
823 if (m_buf == 0) {
824 if ((m_buf = (char *)malloc(defbufsize)) == 0) {
825 LOGSYSERR("NetconData::getline: Out of mem", "malloc", "");
826 return -1;
827 }
828 m_bufsize = defbufsize;
829 m_bufbase = m_buf;
830 m_bufbytes = 0;
831 }
832
833 char *cp = buf;
834 for (;;) {
835 // Transfer from buffer. Have to take a lot of care to keep counts and
836 // pointers consistant in all end cases
837 int maxtransf = std::min(m_bufbytes, cnt - 1);
838 int nn = maxtransf;
839 LOGDEB2("Before loop, bufbytes " << m_bufbytes << ", maxtransf " <<
840 maxtransf << ", nn: " << nn << "\n");
841 for (nn = maxtransf; nn > 0;) {
842 // This is not pretty but we want nn to be decremented for
843 // each byte copied (even newline), and not become -1 if
844 // we go to the end. Better ways welcome!
845 nn--;
846 if ((*cp++ = *m_bufbase++) == '\n') {
847 break;
848 }
849 }
850 // Update counts
851 maxtransf -= nn; // Actual count transferred
852 m_bufbytes -= maxtransf;
853 cnt -= maxtransf;
854 LOGDEB2("After transfer: actual transf " << maxtransf << " cnt " <<
855 cnt << ", m_bufbytes " << m_bufbytes << "\n");
856
857 // Finished ?
858 if (cnt <= 1 || (cp > buf && cp[-1] == '\n')) {
859 *cp = 0;
860 return cp - buf;
861 }
862
863 // Transfer from net
864 m_bufbase = m_buf;
865 m_bufbytes = receive(m_buf, m_bufsize, timeo);
866 if (m_bufbytes == 0) {
867 // EOF
868 *cp = 0;
869 return cp - buf;
870 }
871 if (m_bufbytes < 0) {
872 m_bufbytes = 0;
873 *cp = 0;
874 return -1;
875 }
876 }
877 }
878
879 // Called when selectloop detects that data can be read or written on
880 // the connection. The user callback would normally have been set
881 // up. If it is, call it and return. Else, perform housecleaning: read
882 // and discard.
cando(Netcon::Event reason)883 int NetconData::cando(Netcon::Event reason)
884 {
885 LOGDEB2("NetconData::cando\n");
886 if (m_user) {
887 return m_user->data(this, reason);
888 }
889
890 // No user callback. Clean up by ourselves
891 if (reason & NETCONPOLL_READ) {
892 #define BS 200
893 char buf[BS];
894 int n;
895 if ((n = receive(buf, BS)) < 0) {
896 LOGSYSERR("NetconData::cando", "receive", "");
897 return -1;
898 }
899 if (n == 0) {
900 // EOF
901 return 0;
902 }
903 }
904 m_wantedEvents &= ~NETCONPOLL_WRITE;
905 return 1;
906 }
907
908 ///////////////////////////////////////////////////////////////////////
909 // Methods for a client connection (NetconCli)
openconn(const char * host,unsigned int port,int timeo)910 int NetconCli::openconn(const char *host, unsigned int port, int timeo)
911 {
912 int ret = -1;
913 LOGDEB2("Netconcli::openconn: host " << host << ", port " << port << "\n");
914
915 closeconn();
916
917 struct sockaddr *saddr;
918 socklen_t addrsize;
919
920 struct sockaddr_in ip_addr;
921 struct sockaddr_un unix_addr;
922 if (host[0] != '/') {
923 memset(&ip_addr, 0, sizeof(ip_addr));
924 ip_addr.sin_family = AF_INET;
925 ip_addr.sin_port = htons(port);
926
927 // Server name may be host name or IP address
928 int addr;
929 if ((addr = inet_addr(host)) != -1) {
930 memcpy(&ip_addr.sin_addr, &addr, sizeof(addr));
931 } else {
932 struct hostent *hp;
933 if ((hp = gethostbyname(host)) == 0) {
934 LOGERR("NetconCli::openconn: gethostbyname(" << host <<
935 ") failed\n");
936 return -1;
937 }
938 memcpy(&ip_addr.sin_addr, hp->h_addr, hp->h_length);
939 }
940
941 if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
942 LOGSYSERR("NetconCli::openconn", "socket", "");
943 return -1;
944 }
945 addrsize = sizeof(ip_addr);
946 saddr = (sockaddr*)&ip_addr;
947 } else {
948 memset(&unix_addr, 0, sizeof(unix_addr));
949 unix_addr.sun_family = AF_UNIX;
950 if (strlen(host) > UNIX_PATH_MAX - 1) {
951 LOGERR("NetconCli::openconn: name too long: " << host << "\n");
952 return -1;
953 }
954 strcpy(unix_addr.sun_path, host);
955
956 if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
957 LOGSYSERR("NetconCli::openconn", "socket", "");
958 return -1;
959 }
960 addrsize = sizeof(unix_addr);
961 saddr = (sockaddr*)&unix_addr;
962 }
963 if (timeo > 0) {
964 set_nonblock(1);
965 }
966
967 if (connect(m_fd, saddr, addrsize) < 0) {
968 if (timeo > 0) {
969 if (errno != EINPROGRESS) {
970 goto out;
971 }
972 if (select1(m_fd, timeo, 1) == 1) {
973 goto connectok;
974 }
975 }
976 if (m_silentconnectfailure == 0) {
977 LOGSYSERR("NetconCli", "connect", "");
978 }
979 goto out;
980 }
981 connectok:
982 if (timeo > 0) {
983 set_nonblock(0);
984 }
985
986 LOGDEB2("NetconCli::connect: setting keepalive\n");
987 if (setsockopt(m_fd, SOL_SOCKET, SO_KEEPALIVE,
988 (char *)&one, sizeof(one)) < 0) {
989 LOGSYSERR("NetconCli::connect", "setsockopt", "KEEPALIVE");
990 }
991 setpeer(host);
992 LOGDEB2("NetconCli::openconn: connection opened ok\n");
993 ret = 0;
994 out:
995 if (ret < 0) {
996 closeconn();
997 }
998 return ret;
999 }
1000
1001 // Same as previous, but get the port number from services
openconn(const char * host,const char * serv,int timeo)1002 int NetconCli::openconn(const char *host, const char *serv, int timeo)
1003 {
1004 LOGDEB2("Netconcli::openconn: host " << host << ", serv " << serv << "\n");
1005
1006 if (host[0] != '/') {
1007 struct servent *sp;
1008 if ((sp = getservbyname(serv, "tcp")) == 0) {
1009 LOGERR("NetconCli::openconn: getservbyname failed for " << serv
1010 << "\n");
1011 return -1;
1012 }
1013 // Callee expects the port number in host byte order
1014 return openconn(host, ntohs(sp->s_port), timeo);
1015 } else {
1016 return openconn(host, (unsigned int)0, timeo);
1017 }
1018 }
1019
1020
setconn(int fd)1021 int NetconCli::setconn(int fd)
1022 {
1023 LOGDEB2("Netconcli::setconn: fd " << fd << "\n");
1024 closeconn();
1025
1026 m_fd = fd;
1027 m_ownfd = false;
1028 setpeer("");
1029
1030 return 0;
1031 }
1032
1033 ///////////////////////////////////////////////////////////////////////
1034 // Methods for the main (listening) server connection
1035
~NetconServLis()1036 NetconServLis::~NetconServLis()
1037 {
1038 #ifdef NETCON_ACCESSCONTROL
1039 freeZ(okaddrs.intarray);
1040 freeZ(okmasks.intarray);
1041 #endif
1042 }
1043
1044 #if 0
1045 // code for dumping a struct servent
1046 static void dump_servent(struct servent *servp)
1047 {
1048 fprintf(stderr, "Official name %s\n", servp->s_name);
1049 for (char **cpp = servp->s_aliases; *cpp; cpp++) {
1050 fprintf(stderr, "Nickname %s\n", *cpp);
1051 }
1052 fprintf(stderr, "Port %d\n", (int)ntohs((short)servp->s_port));
1053 fprintf(stderr, "Proto %s\n", servp->s_proto);
1054 }
1055 #endif
1056
1057 // Set up service.
openservice(const char * serv,int backlog)1058 int NetconServLis::openservice(const char *serv, int backlog)
1059 {
1060 int port;
1061 struct servent *servp;
1062 if (!serv) {
1063 LOGERR("NetconServLis::openservice: null serv??\n");
1064 return -1;
1065 }
1066 LOGDEB1("NetconServLis::openservice: serv " << serv << "\n");
1067 #ifdef NETCON_ACCESSCONTROL
1068 if (initperms(serv) < 0) {
1069 return -1;
1070 }
1071 #endif
1072
1073 m_serv = serv;
1074 if (serv[0] != '/') {
1075 if ((servp = getservbyname(serv, "tcp")) == 0) {
1076 LOGERR("NetconServLis::openservice: getservbyname failed for " <<
1077 serv << "\n");
1078 return -1;
1079 }
1080 port = (int)ntohs((short)servp->s_port);
1081 return openservice(port, backlog);
1082 } else {
1083 if (strlen(serv) > UNIX_PATH_MAX - 1) {
1084 LOGERR("NetconServLis::openservice: too long for AF_UNIX: " <<
1085 serv << "\n");
1086 return -1;
1087 }
1088 int ret = -1;
1089 struct sockaddr_un addr;
1090 if ((m_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
1091 LOGSYSERR("NetconServLis", "socket", "");
1092 return -1;
1093 }
1094 memset(&addr, 0, sizeof(addr));
1095 addr.sun_family = AF_UNIX;
1096 strcpy(addr.sun_path, serv);
1097
1098 if (::bind(m_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
1099 LOGSYSERR("NetconServLis", "bind", "");
1100 goto out;
1101 }
1102 if (listen(m_fd, backlog) < 0) {
1103 LOGSYSERR("NetconServLis", "listen", "");
1104 goto out;
1105 }
1106
1107 LOGDEB1("NetconServLis::openservice: service opened ok\n");
1108 ret = 0;
1109 out:
1110 if (ret < 0 && m_fd >= 0) {
1111 close(m_fd);
1112 m_fd = -1;
1113 }
1114 return ret;
1115 }
1116 }
1117
1118 // Port is a natural host integer value
openservice(int port,int backlog)1119 int NetconServLis::openservice(int port, int backlog)
1120 {
1121 LOGDEB1("NetconServLis::openservice: port " << port << "\n");
1122 #ifdef NETCON_ACCESSCONTROL
1123 if (initperms(port) < 0) {
1124 return -1;
1125 }
1126 #endif
1127 int ret = -1;
1128 struct sockaddr_in ipaddr;
1129 if ((m_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
1130 LOGSYSERR("NetconServLis", "socket", "");
1131 return -1;
1132 }
1133 (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
1134 #ifdef SO_REUSEPORT
1135 (void) setsockopt(m_fd, SOL_SOCKET, SO_REUSEPORT, (char *)&one, sizeof(one));
1136 #endif /*SO_REUSEPORT*/
1137 memset(&ipaddr, 0, sizeof(ipaddr));
1138 ipaddr.sin_family = AF_INET;
1139 ipaddr.sin_addr.s_addr = htonl(INADDR_ANY);
1140 ipaddr.sin_port = htons((short)port);
1141 if (::bind(m_fd, (struct sockaddr *)&ipaddr, sizeof(ipaddr)) < 0) {
1142 LOGSYSERR("NetconServLis", "bind", "");
1143 goto out;
1144 }
1145 if (listen(m_fd, backlog) < 0) {
1146 LOGSYSERR("NetconServLis", "listen", "");
1147 goto out;
1148 }
1149
1150 LOGDEB1("NetconServLis::openservice: service opened ok\n");
1151 ret = 0;
1152 out:
1153 if (ret < 0 && m_fd >= 0) {
1154 close(m_fd);
1155 m_fd = -1;
1156 }
1157 return ret;
1158 }
1159
1160 #ifdef NETCON_ACCESSCONTROL
initperms(int port)1161 int NetconServLis::initperms(int port)
1162 {
1163 if (permsinit) {
1164 return 0;
1165 }
1166
1167 char sport[30];
1168 sprintf(sport, "%d", port);
1169 return initperms(sport);
1170 }
1171
1172 // Get authorized address lists from parameter file. This is disabled for now
initperms(const char * serv)1173 int NetconServLis::initperms(const char *serv)
1174 {
1175 if (permsinit) {
1176 return 0;
1177 }
1178
1179 if (serv == 0 || *serv == 0 || strlen(serv) > 80) {
1180 LOGERR("NetconServLis::initperms: bad service name " << serv << "\n");
1181 return -1;
1182 }
1183
1184 char keyname[100];
1185 sprintf(keyname, "%s_okaddrs", serv);
1186 if (genparams->getparam(keyname, &okaddrs, 1) < 0) {
1187 serv = "default";
1188 sprintf(keyname, "%s_okaddrs", serv);
1189 if (genparams->getparam(keyname, &okaddrs) < 0) {
1190 LOGERR("NetconServLis::initperms: no okaddrs found in config file\n");
1191 return -1;
1192 }
1193 }
1194 sprintf(keyname, "%s_okmasks", serv);
1195 if (genparams->getparam(keyname, &okmasks)) {
1196 LOGERR("NetconServLis::initperms: okmasks not found\n");
1197 return -1;
1198 }
1199 if (okaddrs.len == 0 || okmasks.len == 0) {
1200 LOGERR("NetconServLis::initperms: len 0 for okmasks or okaddrs\n");
1201 return -1;
1202 }
1203
1204 permsinit = 1;
1205 return 0;
1206 }
1207 #endif /* NETCON_ACCESSCONTROL */
1208
1209 // Sample cando routine for server master connection: delete newly
1210 // accepted connection. What else ?
1211 // This is to be overriden by a derived class method for an application
1212 // using the selectloop thing
cando(Netcon::Event reason)1213 int NetconServLis::cando(Netcon::Event reason)
1214 {
1215 delete accept();
1216 return 1;
1217 }
1218
1219 NetconServCon *
accept(int timeo)1220 NetconServLis::accept(int timeo)
1221 {
1222 LOGDEB("NetconServLis::accept\n");
1223
1224 if (timeo > 0) {
1225 int ret = select1(m_fd, timeo);
1226 if (ret == 0) {
1227 LOGDEB2("NetconServLis::accept timed out\n");
1228 m_didtimo = 1;
1229 return 0;
1230 }
1231 if (ret < 0) {
1232 LOGSYSERR("NetconServLis::accept", "select", "");
1233 return 0;
1234 }
1235 }
1236 m_didtimo = 0;
1237
1238 NetconServCon *con = 0;
1239 int newfd = -1;
1240 struct sockaddr_in who;
1241 struct sockaddr_un uwho;
1242 if (m_serv.empty() || m_serv[0] != '/') {
1243 SOCKLEN_T clilen = (SOCKLEN_T)sizeof(who);
1244 if ((newfd = ::accept(m_fd, (struct sockaddr *)&who, &clilen)) < 0) {
1245 LOGSYSERR("NetconServCon::accept", "accept", "");
1246 goto out;
1247 }
1248 #ifdef NETCON_ACCESSCONTROL
1249 if (checkperms(&who, clilen) < 0) {
1250 goto out;
1251 }
1252 #endif
1253 } else {
1254 SOCKLEN_T clilen = (SOCKLEN_T)sizeof(uwho);
1255 if ((newfd = ::accept(m_fd, (struct sockaddr *)&uwho, &clilen)) < 0) {
1256 LOGSYSERR("NetconServCon::accept", "accept", "");
1257 goto out;
1258 }
1259 }
1260
1261 con = new NetconServCon(newfd);
1262 if (con == 0) {
1263 LOGERR("NetconServLis::accept: new NetconServCon failed\n");
1264 goto out;
1265 }
1266
1267 // Retrieve peer's host name. Errors are non fatal
1268 if (m_serv.empty() || m_serv[0] != '/') {
1269 struct hostent *hp;
1270 if ((hp = gethostbyaddr((char *) & (who.sin_addr),
1271 sizeof(struct in_addr), AF_INET)) == 0) {
1272 LOGERR("NetconServLis::accept: gethostbyaddr failed for addr 0x" <<
1273 who.sin_addr.s_addr << "\n");
1274 con->setpeer(inet_ntoa(who.sin_addr));
1275 } else {
1276 con->setpeer(hp->h_name);
1277 }
1278 } else {
1279 con->setpeer(m_serv.c_str());
1280 }
1281
1282 LOGDEB2("NetconServLis::accept: setting keepalive\n");
1283 if (setsockopt(newfd, SOL_SOCKET, SO_KEEPALIVE,
1284 (char *)&one, sizeof(one)) < 0) {
1285 LOGSYSERR("NetconServLis::accept", "setsockopt", "KEEPALIVE");
1286 }
1287 LOGDEB2("NetconServLis::accept: got connect from " << con->getpeer() <<
1288 "\n");
1289
1290 out:
1291 if (con == 0 && newfd >= 0) {
1292 close(newfd);
1293 }
1294 return con;
1295 }
1296
1297 #ifdef NETCON_ACCESSCONTROL
1298 int
checkperms(void * cl,int)1299 NetconServLis::checkperms(void *cl, int)
1300 {
1301 // If okmasks and addrs were not initialized, the default is allow to all
1302 if (okmasks.len <= 0 || okaddrs.len <= 0) {
1303 return 0;
1304 }
1305
1306 struct sockaddr *addr = (struct sockaddr *)cl;
1307 unsigned long ip_addr;
1308
1309 if (addr->sa_family != AF_INET) {
1310 LOGERR("NetconServLis::checkperms: connection from non-INET addr !\n");
1311 return -1;
1312 }
1313
1314 ip_addr = ntohl(((struct sockaddr_in *)addr)->sin_addr.s_addr);
1315 LOGDEB2("checkperms: ip_addr: 0x" << ip_addr << "\n");
1316 for (int i = 0; i < okaddrs.len; i++) {
1317 unsigned int mask;
1318 if (i < okmasks.len) {
1319 mask = okmasks.intarray[i];
1320 } else {
1321 mask = okmasks.intarray[okmasks.len - 1];
1322 }
1323 LOGDEB2("checkperms: trying okaddr 0x" << okaddrs.intarray[i] <<
1324 ", mask 0x" << mask << "\n");
1325 if ((ip_addr & mask) == (okaddrs.intarray[i] & mask)) {
1326 return (0);
1327 }
1328 }
1329 LOGERR("NetconServLis::checkperm: connection from bad address 0x" <<
1330 ip_addr << "\n");
1331 return -1;
1332 }
1333 #endif /* NETCON_ACCESSCONTROL */
1334