-
Notifications
You must be signed in to change notification settings - Fork 0
/
net.c
7434 lines (5915 loc) · 232 KB
/
net.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* libslack - https://libslack.org
*
* Copyright (C) 1999-2004, 2010, 2020-2023 raf <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* 20230824 raf <[email protected]>
*/
/*
=head1 NAME
I<libslack(net)> - network module
=head1 SYNOPSIS
#define _GNU_SOURCE
#include <slack/std.h>
#include <slack/net.h>
typedef struct sockaddr sockaddr_t;
typedef unsigned short sockport_t;
typedef struct sockopt_t sockopt_t;
typedef union sockaddr_any_t sockaddr_any_t;
typedef struct sockaddr_un sockaddr_un_t;
typedef struct sockaddr_in sockaddr_in_t;
typedef struct sockaddr_in6 sockaddr_in6_t;
typedef struct net_interface_t net_interface_t;
typedef struct rudp_t rudp_t;
struct sockopt_t
{
int level;
int optname;
const void *optval;
int optlen;
};
union sockaddr_any_t
{
sockaddr_t any;
sockaddr_un_t un;
sockaddr_in_t in;
sockaddr_in6_t in6;
};
struct net_interface_t
{
char name[IFNAMSIZ];
unsigned int index;
short flags;
int mtu;
sockaddr_any_t *addr;
sockaddr_any_t *brdaddr;
sockaddr_any_t *dstaddr;
sockaddr_any_t *hwaddr;
};
int net_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize);
int net_client(const char *host, const char *service, sockport_t port, long timeout, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize);
int net_udp_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize);
int net_udp_client(const char *host, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize);
int net_create_server(const char *interface, const char *service, sockport_t port, int type, int protocol, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize);
int net_create_client(const char *host, const char *service, sockport_t port, sockport_t localport, int type, int protocol, long timeout, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize);
int net_multicast_sender(const char *group, const char *service, sockport_t port, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize, const char *ifname, unsigned int ifindex, int ttl, unsigned int noloopback);
int net_multicast_receiver(const char *group, const char *service, sockport_t port, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize, const char *ifname, unsigned int ifindex);
int net_multicast_join(int sockfd, const sockaddr_t *addr, size_t addrsize, const char *ifname, unsigned int ifindex);
int net_multicast_leave(int sockfd, const sockaddr_t *addr, size_t addrsize, const char *ifname, unsigned int ifindex);
int net_multicast_set_interface(int sockfd, const char *ifname, unsigned int ifindex);
int net_multicast_get_interface(int sockfd);
int net_multicast_set_loopback(int sockfd, unsigned int loopback);
int net_multicast_get_loopback(int sockfd);
int net_multicast_set_ttl(int sockfd, int ttl);
int net_multicast_get_ttl(int sockfd);
int net_tos_lowdelay(int sockfd);
int net_tos_throughput(int sockfd);
int net_tos_reliability(int sockfd);
int net_tos_lowcost(int sockfd);
int net_tos_normal(int sockfd);
struct hostent *net_gethostbyname(const char *name, struct hostent *hostbuf, void **buf, size_t *size, int *herrno);
struct servent *net_getservbyname(const char *name, const char *proto, struct servent *servbuf, void **buf, size_t *size);
int net_options(int sockfd, sockopt_t *sockopts);
List *net_interfaces(void);
List *net_interfaces_with_locker(Locker *locker);
List *net_interfaces_by_family(int family);
List *net_interfaces_by_family_with_locker(int family, Locker *locker);
rudp_t *rudp_create(void);
void rudp_release(rudp_t *rudp);
void *rudp_destroy(rudp_t **rudp);
ssize_t net_rudp_transact(int sockfd, rudp_t *rudp, const void *obuf, size_t osize, void *ibuf, size_t isize);
ssize_t net_rudp_transactwith(int sockfd, rudp_t *rudp, const void *obuf, size_t osize, int oflags, void *ibuf, size_t isize, int iflags, sockaddr_any_t *addr, size_t addrsize);
ssize_t net_pack(int sockfd, long timeout, int flags, const char *format, ...);
ssize_t net_vpack(int sockfd, long timeout, int flags, const char *format, va_list args);
ssize_t net_packto(int sockfd, long timeout, int flags, const sockaddr_t *to, size_t tosize, const char *format, ...);
ssize_t net_vpackto(int sockfd, long timeout, int flags, const sockaddr_t *to, size_t tosize, const char *format, va_list args);
ssize_t net_unpack(int sockfd, long timeout, int flags, const char *format, ...);
ssize_t net_vunpack(int sockfd, long timeout, int flags, const char *format, va_list args);
ssize_t net_unpackfrom(int sockfd, long timeout, int flags, sockaddr_t *from, size_t *fromsize, const char *format, ...);
ssize_t net_vunpackfrom(int sockfd, long timeout, int flags, sockaddr_t *from, size_t *fromsize, const char *format, va_list args);
ssize_t pack(void *buf, size_t size, const char *format, ...);
ssize_t vpack(void *buf, size_t size, const char *format, va_list args);
ssize_t unpack(void *buf, size_t size, const char *format, ...);
ssize_t vunpack(void *buf, size_t size, const char *format, va_list args);
ssize_t net_read(int sockfd, long timeout, char *buf, size_t count);
ssize_t net_write(int sockfd, long timeout, const char *buf, size_t count);
ssize_t net_expect(int sockfd, long timeout, const char *format, ...);
ssize_t net_vexpect(int sockfd, long timeout, const char *format, va_list args);
ssize_t net_send(int sockfd, long timeout, const char *format, ...);
ssize_t net_vsend(int sockfd, long timeout, const char *format, va_list args);
ssize_t sendfd(int sockfd, const void *buf, size_t nbytes, int flags, int fd);
ssize_t recvfd(int sockfd, void *buf, size_t nbytes, int flags, int *fd);
#ifdef SO_PASSCRED
#ifdef SCM_CREDENTIALS
ssize_t recvcred(int sockfd, void *buf, size_t nbytes, int flags, struct ucred *cred);
ssize_t recvfromcred(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *src_addr, socklen_t *src_addrlen, struct ucred *cred);
#endif
#endif
int mail(const char *server, const char *sender, const char *recipients, const char *subject, const char *message);
=head1 DESCRIPTION
This module provides functions that create client and server sockets (IPv4,
IPv6, and UNIX domain sockets, stream or datagram), that expect and send
text dialogues/protocols, and that pack and unpack packets according to
templates. IPv4 and IPv6 multicasting is supported. Reliability over UDP is
provided. There are also a function to send mail, and functions to send and
receive open file descriptors via UNIX domain sockets from one process to
another, and functions to send and receive user credentials via UNIX domain
sockets (if supported by the operating system).
=over 4
=cut
*/
#include "config.h"
#ifndef NO_POSIX_SOURCE
#define NO_POSIX_SOURCE /* For EINPROGRESS, EPROTONOSUPPORT, ETIMEDOUT on FreeBSD-8.0 */
#endif
#ifndef _BSD_SOURCE
#define _BSD_SOURCE /* For gethostbyname_r() on Linux */
#endif
#ifndef _DEFAULT_SOURCE
#define _DEFAULT_SOURCE /* New name for _BSD_SOURCE */
#endif
#ifndef __BSD_VISIBLE
#define __BSD_VISIBLE 1 /* For htons(), htonl(), ntohl() on FreeBSD-8.0 */
#endif
#ifndef _NETBSD_SOURCE
#define _NETBSD_SOURCE /* So <netinet/ip.h> won't be broken on NetBSD-5.0.2 */
#endif
#ifndef _XOPEN_SOURCE_EXTENDED
#define _XOPEN_SOURCE_EXTENDED 1 /* For msghdr.msg_control[len], CMSG_FIRSTHDR, CMSG_DATA on Solaris-10 10/09 and OpenSolaris 200906 */
#endif
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* For receiving user credentials over UNIX domain sockets */
#endif
#include "std.h"
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <netdb.h>
#include <net/if.h>
#define BSD_COMP /* for SIOCGIF... on Solaris */
#include <sys/ioctl.h>
#include <netinet/in_systm.h>
#include <netinet/in.h> /* needed by <netinet/ip.h> under OpenBSD */
#include <netinet/ip.h>
#include "net.h"
#include "err.h"
#include "str.h"
#include "fio.h"
#include "mem.h"
#ifndef HAVE_SNPRINTF
#include "snprintf.h"
#endif
#ifndef HAVE_VSSCANF
#include "vsscanf.h"
#endif
#ifdef SOCKS
#include "socks.h"
#endif
#ifndef MSG_SIZE
#define MSG_SIZE 8192
#endif
#ifndef EPROTO /* Mac OS X doesn't have EPROTO */
#define EPROTO EPROTOTYPE
#endif
#ifndef AF_LOCAL /* Solaris 2.6 doesn't have AF_LOCAL */
#define AF_LOCAL AF_UNIX
#endif
#ifndef TEST
#ifndef HAVE_IFREQ_IFR_IFINDEX
#define ifr_ifindex ifr_index
#endif
#ifndef HAVE_IFREQ_IFR_MTU
#ifndef ifr_mtu
#define ifr_mtu ifr_ifindex /* ? */
#endif
#endif
struct rudp_t
{
double rtt; /* most recent round trip time in seconds */
double srtt; /* smoothed round trip time estimator in seconds */
double rttvar; /* smoothed mean deviation in seconds */
double rto; /* current retransmission timeout in seconds */
int nrexmt; /* number of times retransmitted */
uint32_t base; /* number of seconds since epoch at start */
uint32_t sequence; /* sequence number */
};
#ifndef RUDP_RXTMIN
#define RUDP_RXTMIN 2 /* minimum retransmission timeout in seconds */
#endif
#ifndef RUDP_RXTMAX
#define RUDP_RXTMAX 60 /* maximum retransmission timeout in seconds */
#endif
#ifndef RUDP_MAXNREXMT
#define RUDP_MAXNREXMT 3 /* maximum number of times to retransmit */
#endif
/*
=item C<int net_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)>
Creates a TCP server socket ready to I<accept(2)> connections on
C<interface> (as determined by I<gethostbyname(3)>).
If C<interface> is C<null>, connections will be accepted on all local
network interfaces. Otherwise, connections will only be accepted on the
specified interface (as determined by I<gethostbyname(3)>).
If C<service> is non-C<null>, and is either numeric, or is a service name
(as determined by I<getservbyname(3)>), the specified port is used.
Otherwise, C<port> (which must be in host byte order) is used.
If C<interface> is equal to C<"/unix"> and C<service> is an absolute file
system path, the server socket created will be a I<UNIX domain stream
socket>. Otherwise, a TCP server socket is created. If the C<RES_OPTIONS>
environment variable exists and contains the string C<"inet6">, or the
C</etc/resolv.conf> file contains the C<inet6> option, the TCP socket will
be an IPv6 socket. Otherwise, it will be an IPv4 socket.
If C<rcvbufsz> is non-zero, the socket's receive buffer size is set to this
size. Note that you may not get the size you request. If this is important,
use I<getsockopt(2)> to obtain the actual receive buffer size.
If C<sndbufsz> is non-zero, the socket's send buffer size is set to this
size. Note that you may not get the size you ask for. If this is important,
use I<getsockopt(2)> to obtain the actual send buffer size.
If C<addr> and C<addrsize> are not C<null>, the address bound to is stored
in the buffer pointed to by C<addr>. C<*addrsize> specifies the size of the
buffer pointed to by C<addr>. If there is insufficient space, the bound
address is not stored in C<addr>. If C<addrsize> is not C<null>, the length
of the address is stored there.
On success, returns the new socket descriptor. On error, returns C<-1> with
C<errno> set appropriately.
=cut
*/
static sockopt_t *build_sockopts(sockopt_t *sockopts, int *rcvbufsz, int *sndbufsz)
{
size_t so = 0;
if (*rcvbufsz)
{
sockopts[so].level = SOL_SOCKET;
sockopts[so].optname = SO_RCVBUF;
sockopts[so].optval = rcvbufsz;
sockopts[so].optlen = sizeof(int);
so++;
}
if (*sndbufsz)
{
sockopts[so].level = SOL_SOCKET;
sockopts[so].optname = SO_SNDBUF;
sockopts[so].optval = sndbufsz;
sockopts[so].optlen = sizeof(int);
so++;
}
sockopts[so].optval = NULL;
return sockopts;
}
int net_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)
{
sockopt_t sockopts[3];
build_sockopts(sockopts, &rcvbufsz, &sndbufsz);
return net_create_server(interface, service, port, SOCK_STREAM, 0, sockopts, addr, addrsize);
}
/*
=item C<int net_client(const char *host, const char *service, sockport_t port, long timeout, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)>
Creates a TCP client socket and connects to the server listening at C<host>
(as determined by I<gethostbyname(3)>) on the port number specified by
C<service>. C<service> must either be numeric, or a service name as
determined by I<getservbyname(3)>. Otherwise, the port number to connect to
is given by C<port> (which must be in host byte order). If C<host> is
C<null>, the client socket connects to the loopback address.
If C<host> is equal to C<"/unix"> and C<service> is an absolute file system
path, the client socket created will be a I<UNIX domain stream socket>.
Otherwise, a TCP client socket is created. If the C<RES_OPTIONS> environment
variable exists and contains the string C<"inet6">, or the
C</etc/resolv.conf> file contains the C<inet6> option, the TCP socket will
be an IPv6 socket. Otherwise, it will be an IPv4 socket.
If C<timeout> is non-zero, it specifies the number of seconds after which to
timeout the attempt to connect to the specified server. This can be useful
if the client may attempt to connect to a service that is blocked by a
firewall that drops its packets or if the host that you are connecting to
does not protect itself from SYN floods. The native TCP timeouts are very
long (usually minutes) when faced with an unresponsive network and you may
not want your programs or their users to wait that long.
If C<rcvbufsz> is non-zero, the socket's receive buffer size is set to this
size. Note that you may not get the size you request. If this is important,
use I<getsockopt(2)> to obtain the actual receive buffer size.
If C<sndbufsz> is non-zero, the socket's send buffer size is set to this
size. Note that you may not get the size you ask for. If this is important,
use I<getsockopt(2)> to obtain the actual send buffer size.
If C<addr> and C<addrsize> are not C<null>, the address of the peer is
stored in the buffer pointed to by C<addr>. C<*addrsize> specifies the size
of the buffer pointed to by C<addr>. If there is insufficient space, the
peer's address is not stored in C<addr>. If C<addrsize> is not C<null>, the
size of the peer's address is stored there.
On success, returns the new socket descriptor. On error, returns C<-1> with
C<errno> set appropriately.
=cut
*/
int net_client(const char *host, const char *service, sockport_t port, long timeout, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)
{
sockopt_t sockopts[3];
build_sockopts(sockopts, &rcvbufsz, &sndbufsz);
return net_create_client(host, service, port, 0, SOCK_STREAM, 0, timeout, sockopts, addr, addrsize);
}
/*
=item C<int net_udp_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)>
Equivalent to I<net_server(3)> except that a UDP server is socket is
created. If C<interface> is equal to C<"/unix"> and C<service> is an
absolute file system path, the server socket created will be a I<UNIX domain
datagram socket>. On success, returns the new socket's file descriptor. On
error, returns C<-1> with C<errno> set appropriately.
=cut
*/
int net_udp_server(const char *interface, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)
{
sockopt_t sockopts[3];
build_sockopts(sockopts, &rcvbufsz, &sndbufsz);
return net_create_server(interface, service, port, SOCK_DGRAM, 0, sockopts, addr, addrsize);
}
/*
=item C<int net_udp_client(const char *host, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)>
Equivalent to I<net_client(3)> except that a UDP client socket is created.
If C<interface> is equal to C<"/unix"> and C<service> is an absolute file
system path, the server socket created will be a I<UNIX domain datagram
socket>. On success, returns the new socket's file descriptor. On error,
returns C<-1> with C<errno> set appropriately.
=cut
*/
int net_udp_client(const char *host, const char *service, sockport_t port, int rcvbufsz, int sndbufsz, sockaddr_t *addr, size_t *addrsize)
{
sockopt_t sockopts[3];
build_sockopts(sockopts, &rcvbufsz, &sndbufsz);
return net_create_client(host, service, port, 0, SOCK_DGRAM, 0, 0, sockopts, addr, addrsize);
}
/*
=item C<int net_create_server(const char *interface, const char *service, sockport_t port, int type, int protocol, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize)>
Equivalent to I<net_server(3)> and I<net_udp_server(3)> only more general.
The type of socket is specified by C<type> (e.g. C<SOCK_STREAM> or
C<SOCK_DGRAM>) and C<protocol> (usually zero). If C<sockopts> is not
C<null>, the socket options specified are set before calling I<bind(2)>. On
success, returns the new socket's file descriptor. On error, returns C<-1>
with C<errno> set appropriately.
=cut
*/
static sockaddr_t *net_unaddr(sockaddr_un_t *un, size_t family, const char *path)
{
memset(un, 0, sizeof(sockaddr_un_t));
un->sun_family = family;
strlcpy(un->sun_path, path, sizeof(un->sun_path));
return (sockaddr_t *)un;
}
static sockaddr_t *net_inaddr(sockaddr_in_t *in, size_t family, const void *addr, size_t addrsize, sockport_t port)
{
memset(in, 0, sizeof(sockaddr_in_t));
in->sin_family = family;
memcpy(&in->sin_addr, addr, addrsize);
in->sin_port = port;
return (sockaddr_t *)in;
}
#ifdef AF_INET6
static sockaddr_t *net_in6addr(sockaddr_in6_t *in6, size_t family, const void *addr, size_t addrsize, sockport_t port)
{
memset(in6, 0, sizeof(sockaddr_in6_t));
in6->sin6_family = family;
memcpy(&in6->sin6_addr, addr, addrsize);
in6->sin6_port = port;
return (sockaddr_t *)in6;
}
#endif
#ifdef AF_INET6
static int inet6_required(void)
{
char *res_options;
FILE *resolv_conf;
if ((res_options = getenv("RES_OPTIONS")) && strstr(res_options, "inet6"))
return 1;
if ((resolv_conf = fopen("/etc/resolv.conf", "r")))
{
char line[BUFSIZ];
while (fgets(line, BUFSIZ, resolv_conf))
{
if (!strncmp(line, "options", 7) && strstr(line + 8, "inet6"))
{
fclose(resolv_conf);
return 1;
}
}
fclose(resolv_conf);
}
return 0;
}
#endif
static const char *getprotonamebysocktype(int socktype)
{
switch (socktype)
{
case SOCK_STREAM: return "tcp";
case SOCK_DGRAM: return "udp";
default: return NULL;
}
}
static sockport_t getservportbynameandtype(const char *name, int type)
{
struct servent servbuf[1];
struct servent *serv;
void *buf = NULL;
size_t size = 0;
sockport_t port = 0;
const char *proto;
proto = getprotonamebysocktype(type);
if ((serv = net_getservbyname(name, proto, servbuf, &buf, &size)))
port = serv->s_port;
free(buf);
return port;
}
static int service_number(const char *service)
{
char *endptr = NULL;
unsigned long val = strtoul(service, &endptr, 10);
if ((val = strtoul(service, &endptr, 10)) > USHRT_MAX)
return set_errno(ERANGE);
if (endptr == service || *endptr != '\0')
return set_errno(EDOM);
return (int)val;
}
static sockport_t service_port(const char *service, int type, int port)
{
if (service)
{
int ret;
if ((ret = service_number(service)) != -1)
return htons((sockport_t)ret);
if ((ret = getservportbynameandtype(service, type)) != 0)
return ret;
}
return htons(port);
}
static int is_multicast(sockaddr_t *address)
{
sockaddr_any_t *addr = (sockaddr_any_t *)address;
long *longptr;
switch (addr->any.sa_family)
{
case AF_INET:
/* Avoid dereferencing type-punned pointer to avoid gcc warning */
/*return IN_MULTICAST(ntohl(*(long *)&addr->in.sin_addr));*/
longptr = (long *)&addr->in.sin_addr;
return IN_MULTICAST(ntohl(*longptr));
#ifdef AF_INET6
case AF_INET6:
return IN6_IS_ADDR_MULTICAST(&addr->in6.sin6_addr);
#endif
}
return 0;
}
int net_create_server(const char *interface, const char *service, sockport_t port, int type, int protocol, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize)
{
int sockfd;
sockaddr_any_t localany;
sockaddr_t *localaddr;
size_t localsize;
struct hostent *hostent;
int reuse_addr = 1;
/* Check for UNIX domain socket specification */
if (interface && !strcmp(interface, "/unix"))
{
if (!service || *service != '/' || !service[1] || strlen(service) >= sizeof localany.un.sun_path)
return set_errno(EINVAL);
localaddr = net_unaddr(&localany.un, AF_LOCAL, service);
localsize = sizeof localany.un;
unlink(localany.un.sun_path);
}
else /* IPv4 or IPv6 */
{
/* Set port to service's port number if possible */
port = service_port(service, type, port);
/* Set localaddr and localsize to the specified interface, or any */
if (interface)
{
struct hostent hostbuf[1];
void *buf = NULL;
size_t size = 0;
int herrno;
if (!(hostent = net_gethostbyname(interface, hostbuf, &buf, &size, &herrno)))
{
free(buf);
return set_errno(ENOENT);
}
if (hostent->h_addrtype == AF_INET)
{
localaddr = net_inaddr(&localany.in, hostent->h_addrtype, hostent->h_addr_list[0], hostent->h_length, port);
localsize = sizeof localany.in;
}
#ifdef AF_INET6
else if (hostent->h_addrtype == AF_INET6)
{
localaddr = net_in6addr(&localany.in6, hostent->h_addrtype, hostent->h_addr_list[0], hostent->h_length, port);
localsize = sizeof localany.in6;
}
#endif
else
{
free(buf);
return set_errno(ENOSYS);
}
free(buf);
}
else /* wildcard */
{
#ifdef AF_INET6
if (inet6_required())
{
localaddr = net_in6addr(&localany.in6, AF_INET6, &in6addr_any, sizeof in6addr_any, port);
localsize = sizeof localany.in6;
}
else
#endif
{
unsigned long inaddr_any = htonl(INADDR_ANY);
localaddr = net_inaddr(&localany.in, AF_INET, &inaddr_any, sizeof inaddr_any, port);
localsize = sizeof localany.in;
}
}
}
/* Create the socket */
if ((sockfd = socket(localaddr->sa_family, type, protocol)) == -1)
return -1;
/* Set reuseaddr for tcp servers and udp multicast receivers */
if ((type == SOCK_STREAM && localaddr->sa_family != AF_LOCAL) || (type == SOCK_DGRAM && is_multicast(localaddr)))
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&reuse_addr, sizeof reuse_addr) == -1)
return close(sockfd), -1;
/* Set any user supplied socket options */
if (sockopts && net_options(sockfd, sockopts) == -1)
return close(sockfd), -1;
/* bind to localaddr */
if (bind(sockfd, localaddr, localsize) == -1)
return close(sockfd), -1;
/* If connection-oriented, listen */
if (type == SOCK_STREAM && listen(sockfd, 1024) == -1)
return close(sockfd), -1;
/* Return sockfd, localaddr and localsize */
if (addr && addrsize && *addrsize >= localsize)
memcpy(addr, localaddr, localsize);
if (addrsize)
*addrsize = localsize;
return sockfd;
}
/*
=item C<int net_create_client(const char *host, const char *service, sockport_t port, sockport_t localport, int type, int protocol, long timeout, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize)>
Equivalent to I<net_client(3)> and I<net_udp_client(3)> only more general.
The type of socket is specified by C<type> (e.g. C<SOCK_STREAM> or
C<SOCK_DGRAM>) and C<protocol> (usually zero). If C<localport> is not zero,
it is the port (in host byte order) that the local endpoint binds to. If
C<sockopts> is not C<null>, the socket options specified are set before
calling I<bind(2)>. On success, returns the new socket's file descriptor. On
error, returns C<-1> with C<errno> set appropriately.
=cut
*/
static int net_client_connect(sockaddr_t *remoteaddr, size_t remotesize, sockport_t localport, int type, int protocol, int timeout, sockopt_t *sockopts)
{
int sockfd;
int rc;
/* Create the socket */
if ((sockfd = socket(remoteaddr->sa_family, type, protocol)) == -1)
return -1;
/* Set any user specified socket options */
if (sockopts && net_options(sockfd, sockopts) == -1)
return close(sockfd), -1;
/* If connectionless (or requested), bind (not always needed) */
if (type == SOCK_DGRAM && (localport || remoteaddr->sa_family == AF_LOCAL))
{
sockaddr_any_t localany;
sockaddr_t *localaddr;
size_t localsize;
if (remoteaddr->sa_family == AF_LOCAL)
{
#if HAVE_UNIX_DOMAIN_WILDCARD
localaddr = net_unaddr(&localany.un, AF_LOCAL, "");
#else
/*
** There is a race condition here. Between the time the path is
** constructed and bind() creates the inode, another process
** might create a file with the same path. However, since bind()
** fails if the path already exists, there's no security risk.
** Please correct me if I'm wrong. There are bugs, though.
** bind() will fail when another process creates a file with the
** same path and the number of possible pathnames is limited by
** tmpnam(). Fortunately, it's a very large limit.
** Unfortunately, there's no way around this on some systems
** (e.g. Solaris). Another annoyance is that the path to which
** we bind the socket must be unlinked by the application. To
** get the name, the application must use getsockname() and then
** unlink() the path when finished with the socket.
**
** Linux doesn't have this problem since it lets us bind to ""
** (the AF_LOCAL equivalent of INADDR_ANY).
**
** The easy, elegant, portable solution is to never use UNIX
** domain datagram sockets. Always use stream sockets instead.
*/
char path[L_tmpnam];
if (!tmpnam(path))
return close(sockfd), -1;
localaddr = net_unaddr(&localany.un, AF_LOCAL, path);
#endif
localsize = sizeof localany.un;
}
else
{
#ifdef AF_INET6
if (inet6_required())
{
localaddr = net_in6addr(&localany.in6, AF_INET6, &in6addr_any, sizeof in6addr_any, htons(localport));
localsize = sizeof localany.in6;
}
else
#endif
{
unsigned long inaddr_any = htonl(INADDR_ANY);
localaddr = net_inaddr(&localany.in, AF_INET, &inaddr_any, sizeof inaddr_any, htons(localport));
localsize = sizeof localany.in;
}
}
if (bind(sockfd, localaddr, localsize) == -1)
return close(sockfd), -1;
}
/* Connect to remoteaddr (possibly with a timeout) */
if (timeout && nonblock_on(sockfd) == -1)
return close(sockfd), -1;
if ((rc = connect(sockfd, remoteaddr, remotesize)) == -1 && errno != EINPROGRESS)
{
int saved_errno = errno;
close(sockfd);
return set_errno(saved_errno);
}
if (rc == -1)
{
int access, err = 0;
size_t size = sizeof err;
if ((access = rw_timeout(sockfd, timeout, 0)) == -1)
return close(sockfd), -1;
if (!(access & R_OK) && !(access & W_OK))
return close(sockfd), -1;
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&err, (void *)&size) == -1)
return close(sockfd), -1;
if (err)
return close(sockfd), set_errno(err);
}
if (timeout && nonblock_off(sockfd) == -1)
return close(sockfd), -1;
return sockfd;
}
int net_create_client(const char *host, const char *service, sockport_t port, sockport_t localport, int type, int protocol, long timeout, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize)
{
int sockfd;
sockaddr_any_t remoteany;
sockaddr_t *remoteaddr;
size_t remotesize;
struct hostent *hostent = NULL;
struct hostent hostbuf[1];
void *buf = NULL;
size_t size = 0;
int herrno;
size_t h = 0;
/* Check for UNIX domain socket specification */
if (host && !strcmp(host, "/unix"))
{
if (!service || *service != '/' || !service[1] || strlen(service) >= sizeof remoteany.un.sun_path)
return set_errno(EINVAL);
remoteaddr = net_unaddr(&remoteany.un, AF_LOCAL, service);
remotesize = sizeof remoteany.un;
}
else /* IPv4 or IPv6 */
{
/* Set port to service's port number if possible */
port = service_port(service, type, port);
/* Set remoteaddr and remotesize to the specified host address, or loopback */
if (host)
{
if (!(hostent = net_gethostbyname(host, hostbuf, &buf, &size, &herrno)))
{
free(buf);
return set_errno(ENOENT);
}
if (hostent->h_addrtype == AF_INET)
{
remoteaddr = net_inaddr(&remoteany.in, hostent->h_addrtype, hostent->h_addr_list[0], hostent->h_length, port);
remotesize = sizeof remoteany.in;
}
#ifdef AF_INET6
else if (hostent->h_addrtype == AF_INET6)
{
remoteaddr = net_in6addr(&remoteany.in6, hostent->h_addrtype, hostent->h_addr_list[0], hostent->h_length, port);
remotesize = sizeof remoteany.in6;
}
#endif
else
{
free(buf);
return set_errno(ENOSYS);
}
}
else /* loopback */
{
#ifdef AF_INET6
if (inet6_required())
{
remoteaddr = net_in6addr(&remoteany.in6, AF_INET6, &in6addr_loopback, sizeof in6addr_loopback, port);
remotesize = sizeof remoteany.in6;
}
else
#endif
{
unsigned long inaddr_loopback = htonl(INADDR_LOOPBACK);
remoteaddr = net_inaddr(&remoteany.in, AF_INET, &inaddr_loopback, sizeof inaddr_loopback, port);
remotesize = sizeof remoteany.in;
}
}
}
/* Try to connect to all available addresses */
for (;;)
{
if ((sockfd = net_client_connect(remoteaddr, remotesize, localport, type, protocol, timeout, sockopts)) != -1)
break;
/* Try the next address in h_addr_list, if any */
if (!hostent || !hostent->h_addr_list[++h])
break;
if (hostent->h_addrtype == AF_INET)
{
remoteaddr = net_inaddr(&remoteany.in, hostent->h_addrtype, hostent->h_addr_list[h], hostent->h_length, port);
remotesize = sizeof remoteany.in;
}
#ifdef AF_INET6
else if (hostent->h_addrtype == AF_INET6)
{
remoteaddr = net_in6addr(&remoteany.in6, hostent->h_addrtype, hostent->h_addr_list[h], hostent->h_length, port);
remotesize = sizeof remoteany.in6;
}
#endif
}
free(buf);
/* None succeeded */
if (sockfd == -1)
return -1;
/* Return sockfd, remoteaddr and remotesize */
if (addr && addrsize && *addrsize >= remotesize)
memcpy(addr, remoteaddr, remotesize);
if (addrsize)
*addrsize = remotesize;
return sockfd;
}
/*
=item C<int net_multicast_sender(const char *group, const char *service, sockport_t port, sockopt_t *sockopts, sockaddr_t *addr, size_t *addrsize, const char *ifname, unsigned int ifindex, int ttl, unsigned int noloopback)>
Creates a UDP multicast sender socket. C<group> specifies the multicast
group that packets will be sent to.
If the C<RES_OPTIONS> environment variable exists and contains the string
C<"inet6"> or the C</etc/resolv.conf> file contains the C<inet6> option, the
multicast sender will be an IPv6 socket. Otherwise, it will be an IPv4
socket.
C<service> must specify a service name or a numeric port number to use.
Otherwise, C<port> (which must be in host byte order) specifies the port
number to use.
C<sockopts> may contain extra socket options to set.
If C<addr> and C<addrsize> are not C<null>, the multicast group's address is
stored in the buffer pointed to by C<addr>. C<*addrsize> specifies the size
of the buffer pointed to by C<addr>. If there is insufficient space, the
address is not stored in C<addr>. If C<addrsize> is not C<null>, the size of
the address is stored there.
If I<ifname> is not C<null>, it specifies the name of the interface on which
to send the multicast packets. Otherwise, if C<ifindex> is not zero, it
specifies the index of the interface on which to send multicast packets.
Otherwise, the kernel will choose the interface on which to send multicast
packets based on the routing table (which is the default behaviour).
If C<ttl> is greater than C<1>, it specifies the multicast packets' TTL. By
default the TTL is C<1>. See the Multicast-HOWTO for details on the scoping
semantics of the TTL field in multicast packets.
If C<noloopback> is not zero, multicast loopback is disabled. This would
prevent any process on the sending host from receiving the multicast packets
sent via this socket. Multicast loopback is enabled by default.
The socket is connected to the specified multicast group address so that