xref: /freebsd/usr.sbin/bluetooth/btpand/channel.c (revision 3c4ba5f55438f7afd4f4b0b56f88f2bb505fd6a6)
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
53 channel_init(void)
54 {
55 
56 	LIST_INIT(&channel_list);
57 }
58 
59 channel_t *
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
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
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
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
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
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
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
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
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
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