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