1 /* $NetBSD: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $ */
2
3 /*-
4 * SPDX-License-Identifier: BSD-2-Clause-NetBSD
5 *
6 * Copyright (c) 2008 Iain Hibbert
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 /* $FreeBSD$ */
31
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: channel.c,v 1.1 2008/08/17 13:20:57 plunky Exp $");
34
35 #include <sys/param.h>
36 #include <sys/ioctl.h>
37
38 #include <libutil.h>
39 #include <unistd.h>
40 #define L2CAP_SOCKET_CHECKED
41 #include "btpand.h"
42
43 static struct chlist channel_list;
44 static int channel_count;
45 static int channel_tick;
46
47 static void channel_start(int, short, void *);
48 static void channel_read(int, short, void *);
49 static void channel_dispatch(packet_t *);
50 static void channel_watchdog(int, short, void *);
51
52 void
channel_init(void)53 channel_init(void)
54 {
55
56 LIST_INIT(&channel_list);
57 }
58
59 channel_t *
channel_alloc(void)60 channel_alloc(void)
61 {
62 channel_t *chan;
63
64 chan = malloc(sizeof(channel_t));
65 if (chan == NULL) {
66 log_err("%s() failed: %m", __func__);
67 return NULL;
68 }
69
70 memset(chan, 0, sizeof(channel_t));
71 STAILQ_INIT(&chan->pktlist);
72 chan->state = CHANNEL_CLOSED;
73 LIST_INSERT_HEAD(&channel_list, chan, next);
74
75 server_update(++channel_count);
76
77 return chan;
78 }
79
80 bool
channel_open(channel_t * chan,int fd)81 channel_open(channel_t *chan, int fd)
82 {
83 int n;
84
85 assert(chan->refcnt == 0);
86 assert(chan->state != CHANNEL_CLOSED);
87
88 if (chan->mtu > 0) {
89 chan->sendbuf = malloc(chan->mtu);
90 if (chan->sendbuf == NULL) {
91 log_err("Could not malloc channel sendbuf: %m");
92 return false;
93 }
94 }
95
96 n = 1;
97 if (ioctl(fd, FIONBIO, &n) == -1) {
98 log_err("Could not set non-blocking IO: %m");
99 return false;
100 }
101
102 event_set(&chan->rd_ev, fd, EV_READ | EV_PERSIST, channel_read, chan);
103 if (event_add(&chan->rd_ev, NULL) == -1) {
104 log_err("Could not add channel read event: %m");
105 return false;
106 }
107
108 event_set(&chan->wr_ev, fd, EV_WRITE, channel_start, chan);
109
110 chan->refcnt++;
111 chan->fd = fd;
112
113 log_debug("(fd#%d)", chan->fd);
114
115 return true;
116 }
117
118 void
channel_close(channel_t * chan)119 channel_close(channel_t *chan)
120 {
121 pkthdr_t *ph;
122
123 assert(chan->state != CHANNEL_CLOSED);
124
125 log_debug("(fd#%d)", chan->fd);
126
127 chan->state = CHANNEL_CLOSED;
128 event_del(&chan->rd_ev);
129 event_del(&chan->wr_ev);
130 close(chan->fd);
131 chan->refcnt--;
132 chan->tick = 0;
133
134 while ((ph = STAILQ_FIRST(&chan->pktlist)) != NULL) {
135 STAILQ_REMOVE_HEAD(&chan->pktlist, next);
136 pkthdr_free(ph);
137 chan->qlen--;
138 }
139
140 if (chan->pfh != NULL) {
141 pidfile_remove(chan->pfh);
142 chan->pfh = NULL;
143 }
144
145 if (chan->refcnt == 0)
146 channel_free(chan);
147 }
148
149 void
channel_free(channel_t * chan)150 channel_free(channel_t *chan)
151 {
152
153 assert(chan->refcnt == 0);
154 assert(chan->state == CHANNEL_CLOSED);
155 assert(chan->qlen == 0);
156 assert(STAILQ_EMPTY(&chan->pktlist));
157
158 LIST_REMOVE(chan, next);
159 free(chan->pfilter);
160 free(chan->mfilter);
161 free(chan->sendbuf);
162 free(chan);
163
164 server_update(--channel_count);
165
166 if (server_limit == 0) {
167 log_info("connection closed, exiting");
168 exit(EXIT_SUCCESS);
169 }
170 }
171
172 static void
channel_start(int fd,short ev,void * arg)173 channel_start(int fd, short ev, void *arg)
174 {
175 channel_t *chan = arg;
176 pkthdr_t *ph;
177
178 chan->oactive = true;
179
180 while (chan->qlen > 0) {
181 ph = STAILQ_FIRST(&chan->pktlist);
182
183 channel_timeout(chan, 10);
184 if (chan->send(chan, ph->data) == false) {
185 if (event_add(&chan->wr_ev, NULL) == -1) {
186 log_err("Could not add channel write event: %m");
187 channel_close(chan);
188 }
189 return;
190 }
191
192 STAILQ_REMOVE_HEAD(&chan->pktlist, next);
193 pkthdr_free(ph);
194 chan->qlen--;
195 }
196
197 channel_timeout(chan, 0);
198 chan->oactive = false;
199 }
200
201 static void
channel_read(int fd,short ev,void * arg)202 channel_read(int fd, short ev, void *arg)
203 {
204 channel_t *chan = arg;
205 packet_t *pkt;
206 ssize_t nr;
207
208 pkt = packet_alloc(chan);
209 if (pkt == NULL) {
210 channel_close(chan);
211 return;
212 }
213
214 nr = read(fd, pkt->buf, chan->mru);
215 if (nr == -1) {
216 log_err("channel read error: %m");
217 packet_free(pkt);
218 channel_close(chan);
219 return;
220 }
221 if (nr == 0) { /* EOF */
222 log_debug("(fd#%d) EOF", fd);
223 packet_free(pkt);
224 channel_close(chan);
225 return;
226 }
227 pkt->len = nr;
228
229 if (chan->recv(pkt) == true)
230 channel_dispatch(pkt);
231
232 packet_free(pkt);
233 }
234
235 static void
channel_dispatch(packet_t * pkt)236 channel_dispatch(packet_t *pkt)
237 {
238 channel_t *chan;
239
240 /*
241 * This is simple routing. I'm not sure if its allowed by
242 * the PAN or BNEP specifications, but it seems logical
243 * to send unicast packets to connected destinations where
244 * possible.
245 */
246 if (!ETHER_IS_MULTICAST(pkt->dst)) {
247 LIST_FOREACH(chan, &channel_list, next) {
248 if (chan == pkt->chan
249 || chan->state != CHANNEL_OPEN)
250 continue;
251
252 if (memcmp(pkt->dst, chan->raddr, ETHER_ADDR_LEN) == 0) {
253 if (chan->qlen > CHANNEL_MAXQLEN)
254 log_notice("Queue overflow");
255 else
256 channel_put(chan, pkt);
257
258 return;
259 }
260 }
261 }
262
263 LIST_FOREACH(chan, &channel_list, next) {
264 if (chan == pkt->chan
265 || chan->state != CHANNEL_OPEN)
266 continue;
267
268 if (chan->qlen > CHANNEL_MAXQLEN) {
269 log_notice("Queue overflow");
270 continue;
271 }
272
273 channel_put(chan, pkt);
274 }
275 }
276
277 void
channel_put(channel_t * chan,packet_t * pkt)278 channel_put(channel_t *chan, packet_t *pkt)
279 {
280 pkthdr_t *ph;
281
282 ph = pkthdr_alloc(pkt);
283 if (ph == NULL)
284 return;
285
286 chan->qlen++;
287 STAILQ_INSERT_TAIL(&chan->pktlist, ph, next);
288
289 if (!chan->oactive)
290 channel_start(chan->fd, EV_WRITE, chan);
291 }
292
293 /*
294 * Simple watchdog timer, only ticks when it is required and
295 * closes the channel down if it times out.
296 */
297 void
channel_timeout(channel_t * chan,int to)298 channel_timeout(channel_t *chan, int to)
299 {
300 static struct event ev;
301
302 if (to == 0)
303 chan->tick = 0;
304 else
305 chan->tick = (channel_tick + to) % 60;
306
307 if (channel_tick == 0) {
308 evtimer_set(&ev, channel_watchdog, &ev);
309 channel_watchdog(0, 0, &ev);
310 }
311 }
312
313 static void
channel_watchdog(int fd,short ev,void * arg)314 channel_watchdog(int fd, short ev, void *arg)
315 {
316 static struct timeval tv = { .tv_sec = 1 };
317 channel_t *chan, *next;
318 int tick;
319
320 tick = (channel_tick % 60) + 1;
321 channel_tick = 0;
322
323 next = LIST_FIRST(&channel_list);
324 while ((chan = next) != NULL) {
325 next = LIST_NEXT(chan, next);
326
327 if (chan->tick == tick)
328 channel_close(chan);
329 else if (chan->tick != 0)
330 channel_tick = tick;
331 }
332
333 if (channel_tick != 0 && evtimer_add(arg, &tv) < 0) {
334 log_err("Could not add watchdog event: %m");
335 exit(EXIT_FAILURE);
336 }
337 }
338