-
Notifications
You must be signed in to change notification settings - Fork 37
/
bcast.c
431 lines (374 loc) · 13.2 KB
/
bcast.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
/* bcast.c
* This file is part of kplex
* Copyright Keith Young 2012 - 2016
* For copying information see the file COPYING distributed with this software
*
* This is hideous and proves what an abomination IPv4 broadcast is.
* The simple case would be straight forward. Listen for broadcasts. Or
* make them. But oh dear if you make *and* receive them in a multiplexer.
* And even so, do you want to broadcast on all interfaces?
* Code here has some protection. Outgoing broadcast kplex interfaces must
* specify a physical interface and potionally broadcast address to use.
* This address is added to an ignore list and all packets received from
* addresses on that list are dropped. This may cause problems with dynamic
* interfaces if the interface address changes. Workarrounds make this code
* muckier than other interface types.
*/
#include "kplex.h"
#include <netdb.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <arpa/inet.h>
#define DEFBCASTQSIZE 64
/* structures for list of (local outbound) addresses to ignore when receiving */
static struct ignore_addr {
struct sockaddr_in iaddr;
struct ignore_addr *next;
} *ignore;
struct if_bcast {
int fd;
struct sockaddr_in addr; /* Outbound address */
struct sockaddr_in laddr; /* local (bind) address */
};
/* Prevention of re-reading what has been written by a bi-directional interface
* is accomplished here by adding source addresses of outgoing interfaces to a
* list of addresses to ignore. The list is protected by a reader/writer lock but
* this is not supported on some embedded platforms. In fact, list manipulation is
* currently only performed when single threaded so we can forget locking
* completely. This code will be re-introduced when interfaces addition/deletion
* becomes dynamic at which point we'll do something sensible with the
* _POSIX_READER_WRITER_LOCKS macro.
*/
#if 0
/* mutex for shared broadcast structures */
pthread_rwlock_t sysaddr_lock;
/*
*Static initialization of rwlocks is not supported on all platforms, so
* we do it via pthread_once
*/
pthread_once_t bcast_init = PTHREAD_ONCE_INIT;
char bcast_rwlock_initialized=0;
void init_bcast_lock(void)
{
if (pthread_rwlock_init(&sysaddr_lock,NULL) == 0)
bcast_rwlock_initialized=1;
}
#endif
/*
* Duplicate broadcast specific info
* Args: if_bcast to be duplicated (cast to void *)
* Returns: pointer to new if_bcast (cast to void *)
*/
void *ifdup_bcast(void *ifb)
{
struct if_bcast *oldif,*newif;
oldif = (struct if_bcast *)ifb;
if ((newif = (struct if_bcast *) malloc(sizeof(struct if_bcast)))
== (struct if_bcast *) NULL)
return(NULL);
/* Whole new socket so we can bind() it to a different address */
if ((newif->fd = socket(AF_INET,SOCK_DGRAM,0)) < 0) {
logwarn("Could not create duplicate socket: %s",strerror(errno));
free(newif);
return(NULL);
}
(void) memcpy(&newif->addr, &oldif->addr, sizeof(oldif->addr));
/* unfortunately this will need changing and the new address binding. */
(void) memcpy(&newif->laddr, &oldif->laddr, sizeof(oldif->laddr));
return((void *) newif);
}
void cleanup_bcast(iface_t *ifa)
{
struct if_bcast *ifb = (struct if_bcast *) ifa->info;
close(ifb->fd);
/* We could remove outgoing interfaces from the ignore list here, but
* we'd have to check they weren't in use by some other interface */
}
void write_bcast(struct iface *ifa)
{
struct if_bcast *ifb;
senblk_t *sptr;
int data=0;
struct msghdr msgh;
struct iovec iov[2];
ifb = (struct if_bcast *) ifa->info;
msgh.msg_name=(void *)&ifb->addr;
msgh.msg_namelen=sizeof(struct sockaddr_in);
msgh.msg_control=NULL;
msgh.msg_controllen=msgh.msg_flags=0;
msgh.msg_iov=iov;
msgh.msg_iovlen=1;
if (ifa->tagflags) {
if ((iov[0].iov_base=malloc(TAGMAX)) == NULL) {
logerr(errno,"Disabing tag output on interface id %u (%s)",
ifa->id,(ifa->name)?ifa->name:"unlabelled");
ifa->tagflags=0;
} else {
msgh.msg_iovlen=2;
data=1;
}
}
for (;;) {
if ((sptr = next_senblk(ifa->q)) == NULL)
break;
if (senfilter(sptr,ifa->ofilter)) {
senblk_free(sptr,ifa->q);
continue;
}
if (ifa->tagflags)
if ((iov[0].iov_len = gettag(ifa,iov[0].iov_base,sptr)) == 0) {
logerr(errno,"Disabing tag output on interface id %u (%s)",
ifa->id,(ifa->name)?ifa->name:"unlabelled");
ifa->tagflags=0;
msgh.msg_iovlen=1;
data=0;
free(iov[0].iov_base);
}
iov[data].iov_base=sptr->data;
iov[data].iov_len=sptr->len;
if ((sendmsg(ifb->fd,&msgh,0)) < 0)
break;
senblk_free(sptr,ifa->q);
}
if (ifa->tagflags)
free(iov[0].iov_base);
iface_thread_exit(errno);
}
ssize_t read_bcast(struct iface *ifa, char *buf)
{
struct if_bcast *ifb=(struct if_bcast *) ifa->info;
struct sockaddr_in src;
struct ignore_addr *igp;
socklen_t sz = (socklen_t) sizeof(src);
ssize_t nread;
do {
nread = recvfrom(ifb->fd,buf,BUFSIZ,0,(struct sockaddr *) &src,&sz);
/* Probably superfluous check that we got the right size
* structure back */
if (sz != (socklen_t) sizeof(src)) {
sz = (socklen_t) sizeof(src);
continue;
}
/* Compare the source address to the list of interfaces we're
* ignoring */
#if 0
pthread_rwlock_rdlock(&sysaddr_lock);
#endif
for (igp=ignore;igp;igp=igp->next) {
if (igp->iaddr.sin_addr.s_addr == src.sin_addr.s_addr)
break;
}
#if 0
pthread_rwlock_unlock(&sysaddr_lock);
#endif
/* If igp points to anything, we broke out of the above loop
* on a match. Drop the packet and carry on */
if (igp == NULL)
break;
} while (1);
return nread;
}
struct iface *init_bcast(struct iface *ifa)
{
struct if_bcast *ifb;
char *ifname,*bname;
struct in_addr baddr;
int port=0;
int iffound=0;
struct servent *svent;
const int on = 1;
static struct ifaddrs *ifap;
struct ifaddrs *ifp=NULL;
struct ignore_addr **igpp,*newig;
size_t qsize = DEFBCASTQSIZE;
struct kopts *opt;
if ((ifb=malloc(sizeof(struct if_bcast))) == NULL) {
logerr(errno,"Could not allocate memory");
return(NULL);
}
memset(ifb,0,sizeof(struct if_bcast));
#if 0
if (pthread_once(&bcast_init,init_bcast_lock) != 0 ||
bcast_rwlock_initialized != 1) {
logerr(errno,"rwlock initialization failed");
return(NULL);
}
#endif
ifname=bname=NULL;
for(opt=ifa->options;opt;opt=opt->next) {
if (!strcasecmp(opt->var,"device"))
ifname=opt->val;
else if (!strcasecmp(opt->var,"address")) {
bname=opt->val;
if (inet_aton(opt->val,&baddr) == 0) {
logerr(0,"%s is not a valid address",opt->val);
return(NULL);
}
} else if (!strcasecmp(opt->var,"port")) {
if (((port=atoi(opt->val)) <= 0) || (port > 65535)) {
logerr(0,"port %s out of range",opt->val);
return(NULL);
} else
port=htons(port);
} else if (!strcasecmp(opt->var,"qsize")) {
if (!(qsize=atoi(opt->val))) {
logerr(0,"Invalid queue size specified: %s",opt->val);
return(NULL);
}
} else {
logerr(0,"Unknown interface option %s",opt->var);
return(NULL);
}
}
if (!port) {
if ((svent = getservbyname("nmea-0183","udp")) != NULL)
/* This is in network byte order already */
port=svent->s_port;
else
port=htons(DEFPORT);
}
ifb->addr.sin_family = ifb->laddr.sin_family = AF_INET;
if (ifname == NULL) {
if (ifa->direction != IN) {
logerr(0,"Must specify interface for outgoing broadcasts");
return(NULL);
}
if (bname)
ifb->laddr.sin_addr.s_addr = baddr.s_addr;
else
ifb->laddr.sin_addr.s_addr = htonl(INADDR_ANY);
} else {
#if 0
pthread_rwlock_wrlock(&sysaddr_lock);
#endif
if (ifap == NULL)
if (getifaddrs(&ifap) < 0) {
logerr(errno,"Error getting interface info");
return(NULL);
}
#if 0
pthread_rwlock_unlock(&sysaddr_lock);
#endif
for (ifp=ifap;ifp;ifp=ifp->ifa_next) {
if (!strcmp(ifname,ifp->ifa_name)) {
iffound++;
if ((ifp->ifa_addr->sa_family == AF_INET) &&
((bname == NULL) || (baddr.s_addr == 0xffffffff) ||
(baddr.s_addr == ((struct sockaddr_in *) ifp->ifa_broadaddr)->sin_addr.s_addr)))
break;
}
}
if (!ifp) {
if (iffound)
logerr(0,"Invalid broadcast address specified for %s",ifname);
else
logerr(0,"No IPv4 interface %s",ifname);
return(NULL);
}
ifb->addr.sin_addr.s_addr = bname?baddr.s_addr:((struct sockaddr_in *) ifp->ifa_broadaddr)->sin_addr.s_addr;
if (ifa->direction == IN)
ifb->laddr.sin_addr.s_addr=ifb->addr.sin_addr.s_addr;
else
ifb->laddr.sin_addr.s_addr=((struct sockaddr_in *)ifp->ifa_addr)->sin_addr.s_addr;
}
ifb->addr.sin_port=port;
if (ifa->direction != OUT)
ifb->laddr.sin_port=port;
if ((ifb->fd=socket(AF_INET,SOCK_DGRAM,0)) < 0) {
logerr(errno,"Could not create UDP socket");
return(NULL);
}
if ((ifa->direction != IN) &&
(setsockopt(ifb->fd,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on)) < 0)) {
logerr(errno,"Setsockopt failed");
return(NULL);
}
if (setsockopt(ifb->fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) <0) {
logwarn("setsockopt failed: %s",strerror(errno));
}
#ifdef SO_REUSEPORT
if (setsockopt(ifb->fd,SOL_SOCKET,SO_REUSEPORT,&on,sizeof(on)) < 0) {
logerr(errno,"Failed to set SO_REUSEPORT");
return(NULL);
}
#endif
#ifdef linux
struct ifreq ifr;
if (ifp) {
/* This won't work without root priviledges and may be system dependent
* so let's silently ignore if it doesn't work */
strncpy(ifr.ifr_ifrn.ifrn_name,ifp->ifa_name,IF_NAMESIZE);
setsockopt(ifb->fd,SOL_SOCKET,SO_BINDTODEVICE,&ifr,sizeof(ifr));
}
#endif
if (bind(ifb->fd,(const struct sockaddr *) &ifb->laddr,sizeof(ifb->laddr)) < 0) {
logerr(errno,"Bind failed");
return(NULL);
}
if (ifa->direction != IN) {
/* Add the outgoing interface and broadcast port to the list that we
need to ignore */
if ((newig = (struct ignore_addr *) malloc(sizeof(struct ignore_addr)))
== NULL) {
logerr(errno,"Could not allocate memory");
return(NULL);
}
newig->iaddr.sin_family = AF_INET;
/* This is the *local* address and the *outgoing* port */
newig->iaddr.sin_addr.s_addr = ifb->laddr.sin_addr.s_addr;
newig->iaddr.sin_port = ifb->addr.sin_port;
newig->next = NULL;
/* find the end of the linked list, or a structure with the
* ignore address already in it */
#if 0
pthread_rwlock_wrlock(&sysaddr_lock);
#endif
for (igpp=&ignore;*igpp;igpp=&(*igpp)->next)
/* DANGER! Only checks address, not port. Need to change this later
* if port becomes significant */
if ((*igpp)->iaddr.sin_addr.s_addr ==
newig->iaddr.sin_addr.s_addr)
break;
/* Tack on new address if not a duplicate */
if (*igpp == NULL)
*igpp=newig;
#if 0
pthread_rwlock_unlock(&sysaddr_lock);
#endif
/* write queue initialization */
if (init_q(ifa,qsize) < 0) {
logerr(errno,"Could not create queue");
return(NULL);
}
}
ifa->write=write_bcast;
ifa->read=do_read;
ifa->readbuf=read_bcast;
ifa->cleanup=cleanup_bcast;
ifa->info = (void *) ifb;
if (ifa->direction == BOTH) {
if ((ifa->next=ifdup(ifa)) == NULL) {
logerr(0,"Interface duplication failed");
return(NULL);
}
ifa->direction=OUT;
ifa->pair->direction=IN;
ifb = (struct if_bcast *) ifa->pair->info;
ifb->laddr.sin_addr.s_addr=ifb->addr.sin_addr.s_addr;
ifb->laddr.sin_port=ifb->addr.sin_port;
if (setsockopt(ifb->fd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) <0) {
logwarn("setsockopt failed: %s",strerror(errno));
}
#ifdef linux
/* As before, this may be system / privs dependent so let's not stress
* if it doesn't work */
setsockopt(ifb->fd,SOL_SOCKET,SO_BINDTODEVICE,&ifr,sizeof(ifr));
#endif
if (bind(ifb->fd,(const struct sockaddr *) &ifb->laddr,sizeof(ifb->laddr)) < 0) {
logerr(errno,"Duplicate Bind failed");
return(NULL);
}
}
free_options(ifa->options);
return(ifa);
}