Annotation of embedaddon/istgt/src/istgt_sock.c, revision 1.1

1.1     ! misho       1: /*
        !             2:  * Copyright (C) 2008-2010 Daisuke Aoyama <aoyama@peach.ne.jp>.
        !             3:  * All rights reserved.
        !             4:  *
        !             5:  * Redistribution and use in source and binary forms, with or without
        !             6:  * modification, are permitted provided that the following conditions
        !             7:  * are met:
        !             8:  * 1. Redistributions of source code must retain the above copyright
        !             9:  *    notice, this list of conditions and the following disclaimer.
        !            10:  * 2. Redistributions in binary form must reproduce the above copyright
        !            11:  *    notice, this list of conditions and the following disclaimer in the
        !            12:  *    documentation and/or other materials provided with the distribution.
        !            13:  *
        !            14:  * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
        !            15:  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        !            16:  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
        !            17:  * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
        !            18:  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
        !            19:  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
        !            20:  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
        !            21:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
        !            22:  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
        !            23:  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
        !            24:  * SUCH DAMAGE.
        !            25:  *
        !            26:  */
        !            27: 
        !            28: #ifdef HAVE_CONFIG_H
        !            29: #include "config.h"
        !            30: #endif
        !            31: 
        !            32: #include <errno.h>
        !            33: #include <stdio.h>
        !            34: #include <string.h>
        !            35: #include <unistd.h>
        !            36: #include <poll.h>
        !            37: #include <sys/types.h>
        !            38: #include <sys/socket.h>
        !            39: #include <netdb.h>
        !            40: #include <netinet/in.h>
        !            41: #include <netinet/tcp.h>
        !            42: 
        !            43: #include "istgt.h"
        !            44: #include "istgt_sock.h"
        !            45: #include "istgt_misc.h"
        !            46: 
        !            47: //#define USE_POLLWAIT
        !            48: #undef USE_POLLWAIT
        !            49: #define TIMEOUT_RW 60
        !            50: #define POLLWAIT 1000
        !            51: #define PORTNUMLEN 32
        !            52: 
        !            53: #ifndef AI_NUMERICSERV
        !            54: #define AI_NUMERICSERV 0
        !            55: #endif
        !            56: 
        !            57: int
        !            58: istgt_getaddr(int sock, char *saddr, int slen, char *caddr, int clen)
        !            59: {
        !            60:        struct sockaddr_storage sa;
        !            61:        socklen_t salen;
        !            62:        int rc;
        !            63: 
        !            64:        memset(&sa, 0, sizeof sa);
        !            65:        salen = sizeof sa;
        !            66:        rc = getsockname(sock, (struct sockaddr *) &sa, &salen);
        !            67:        if (rc != 0) {
        !            68:                return -1;
        !            69:        }
        !            70:        rc = getnameinfo((struct sockaddr *) &sa, salen,
        !            71:                                         saddr, slen, NULL, 0, NI_NUMERICHOST);
        !            72:        if (rc != 0) {
        !            73:                return -1;
        !            74:        }
        !            75: 
        !            76:        memset(&sa, 0, sizeof sa);
        !            77:        salen = sizeof sa;
        !            78:        rc = getpeername(sock, (struct sockaddr *) &sa, &salen);
        !            79:        if (rc != 0) {
        !            80:                return -1;
        !            81:        }
        !            82:        rc = getnameinfo((struct sockaddr *) &sa, salen,
        !            83:                                         caddr, clen, NULL, 0, NI_NUMERICHOST);
        !            84:        if (rc != 0) {
        !            85:                return -1;
        !            86:        }
        !            87: 
        !            88:        return 0;
        !            89: }
        !            90: 
        !            91: int
        !            92: istgt_listen(const char *ip, int port)
        !            93: {
        !            94:        char buf[MAX_TMPBUF];
        !            95:        char portnum[PORTNUMLEN];
        !            96:        char *p;
        !            97:        struct addrinfo hints, *res, *res0;
        !            98:        int sock;
        !            99:        int val = 1;
        !           100:        int rc;
        !           101: 
        !           102:        if (ip == NULL)
        !           103:                return -1;
        !           104:        if (ip[0] == '[') {
        !           105:                strlcpy(buf, ip + 1, sizeof buf);
        !           106:                p = strchr(buf, ']');
        !           107:                if (p != NULL)
        !           108:                        *p = '\0';
        !           109:                ip = (const char *) &buf[0];
        !           110:                if (strcasecmp(ip, "*") == 0) {
        !           111:                        strlcpy(buf, "::", sizeof buf);
        !           112:                        ip = (const char *) &buf[0];
        !           113:                }
        !           114:        } else {
        !           115:                if (strcasecmp(ip, "*") == 0) {
        !           116:                        strlcpy(buf, "0.0.0.0", sizeof buf);
        !           117:                        ip = (const char *) &buf[0];
        !           118:                }
        !           119:        }
        !           120:        snprintf(portnum, sizeof portnum, "%d", port);
        !           121:        memset(&hints, 0, sizeof hints);
        !           122:        hints.ai_family = PF_UNSPEC;
        !           123:        hints.ai_socktype = SOCK_STREAM;
        !           124:        hints.ai_flags = AI_NUMERICSERV;
        !           125:        hints.ai_flags |= AI_PASSIVE;
        !           126:        hints.ai_flags |= AI_NUMERICHOST;
        !           127:        rc = getaddrinfo(ip, portnum, &hints, &res0);
        !           128:        if (rc != 0) {
        !           129:                return -1;
        !           130:        }
        !           131: 
        !           132:        /* try listen */
        !           133:        sock = -1;
        !           134:        for (res = res0; res != NULL; res = res->ai_next) {
        !           135:        retry:
        !           136:                sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        !           137:                if (sock < 0) {
        !           138:                        /* error */
        !           139:                        continue;
        !           140:                }
        !           141:                rc = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof val);
        !           142:                if (rc != 0) {
        !           143:                        /* error */
        !           144:                        continue;
        !           145:                }
        !           146:                rc = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof val);
        !           147:                if (rc != 0) {
        !           148:                        /* error */
        !           149:                        continue;
        !           150:                }
        !           151:                rc = bind(sock, res->ai_addr, res->ai_addrlen);
        !           152:                if (rc == -1 && errno == EINTR) {
        !           153:                        /* interrupted? */
        !           154:                        close(sock);
        !           155:                        sock = -1;
        !           156:                        goto retry;
        !           157:                }
        !           158:                if (rc != 0) {
        !           159:                        /* try next family */
        !           160:                        close(sock);
        !           161:                        sock = -1;
        !           162:                        continue;
        !           163:                }
        !           164:                /* bind OK */
        !           165:                rc = listen(sock, 2);
        !           166:                if (rc != 0) {
        !           167:                        close(sock);
        !           168:                        sock = -1;
        !           169:                        break;
        !           170:                }
        !           171:                break;
        !           172:        }
        !           173:        freeaddrinfo(res0);
        !           174: 
        !           175:        if (sock < 0) {
        !           176:                return -1;
        !           177:        }
        !           178:        return sock;
        !           179: }
        !           180: 
        !           181: int
        !           182: istgt_connect(const char *host, int port)
        !           183: {
        !           184:        char buf[MAX_TMPBUF];
        !           185:        char portnum[PORTNUMLEN];
        !           186:        char *p;
        !           187:        struct addrinfo hints, *res, *res0;
        !           188:        int sock;
        !           189:        int val = 1;
        !           190:        int rc;
        !           191: 
        !           192:        if (host == NULL)
        !           193:                return -1;
        !           194:        if (host[0] == '[') {
        !           195:                strlcpy(buf, host + 1, sizeof buf);
        !           196:                p = strchr(buf, ']');
        !           197:                if (p != NULL)
        !           198:                        *p = '\0';
        !           199:                host = (const char *) &buf[0];
        !           200:                if (strcasecmp(host, "*") == 0) {
        !           201:                        strlcpy(buf, "::", sizeof buf);
        !           202:                        host = (const char *) &buf[0];
        !           203:                }
        !           204:        } else {
        !           205:                if (strcasecmp(host, "*") == 0) {
        !           206:                        strlcpy(buf, "0.0.0.0", sizeof buf);
        !           207:                        host = (const char *) &buf[0];
        !           208:                }
        !           209:        }
        !           210:        snprintf(portnum, sizeof portnum, "%d", port);
        !           211:        memset(&hints, 0, sizeof hints);
        !           212:        hints.ai_family = PF_UNSPEC;
        !           213:        hints.ai_socktype = SOCK_STREAM;
        !           214:        hints.ai_flags = AI_NUMERICSERV;
        !           215:        rc = getaddrinfo(host, portnum, &hints, &res0);
        !           216:        if (rc != 0) {
        !           217:                return -1;
        !           218:        }
        !           219: 
        !           220:        /* try connect */
        !           221:        sock = -1;
        !           222:        for (res = res0; res != NULL; res = res->ai_next) {
        !           223:        retry:
        !           224:                sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
        !           225:                if (sock < 0) {
        !           226:                        /* error */
        !           227:                        continue;
        !           228:                }
        !           229:                rc = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof val);
        !           230:                if (rc != 0) {
        !           231:                        /* error */
        !           232:                        continue;
        !           233:                }
        !           234:                rc = connect(sock, res->ai_addr, res->ai_addrlen);
        !           235:                if (rc == -1 && errno == EINTR) {
        !           236:                        /* interrupted? */
        !           237:                        close(sock);
        !           238:                        sock = -1;
        !           239:                        goto retry;
        !           240:                }
        !           241:                if (rc != 0) {
        !           242:                        /* try next family */
        !           243:                        close(sock);
        !           244:                        sock = -1;
        !           245:                        continue;
        !           246:                }
        !           247:                /* connect OK */
        !           248:                break;
        !           249:        }
        !           250:        freeaddrinfo(res0);
        !           251: 
        !           252:        if (sock < 0) {
        !           253:                return -1;
        !           254:        }
        !           255:        return sock;
        !           256: }
        !           257: 
        !           258: int
        !           259: istgt_set_recvtimeout(int s, int msec)
        !           260: {
        !           261:        struct timeval tv;
        !           262:        int rc;
        !           263: 
        !           264:        memset(&tv, 0, sizeof tv);
        !           265:        tv.tv_sec = msec / 1000;
        !           266:        tv.tv_usec = (msec % 1000) * 1000;
        !           267:        rc = setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
        !           268:        if (rc != 0)
        !           269:                return -1;
        !           270:        return 0;
        !           271: }
        !           272: 
        !           273: int
        !           274: istgt_set_sendtimeout(int s, int msec)
        !           275: {
        !           276:        struct timeval tv;
        !           277:        int rc;
        !           278: 
        !           279:        memset(&tv, 0, sizeof tv);
        !           280:        tv.tv_sec = msec / 1000;
        !           281:        tv.tv_usec = (msec % 1000) * 1000;
        !           282:        rc = setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
        !           283:        if (rc != 0)
        !           284:                return -1;
        !           285:        return 0;
        !           286: }
        !           287: 
        !           288: #ifdef USE_POLLWAIT
        !           289: static int
        !           290: can_read_socket(int s, int msec)
        !           291: {
        !           292:        struct pollfd fds[1];
        !           293:        int rc;
        !           294: 
        !           295:        fds[0].fd = s;
        !           296:        fds[0].events = POLLIN;
        !           297:  retry:
        !           298:        do {
        !           299:                rc = poll(fds, 1, msec);
        !           300:        } while (rc == -1 && errno == EINTR);
        !           301:        if (rc == -1 && errno == EAGAIN) {
        !           302:                goto retry;
        !           303:        }
        !           304:        if (rc < 0) {
        !           305:                /* error */
        !           306:                return -1;
        !           307:        }
        !           308:        if (fds[0].revents & POLLIN) {
        !           309:                /* read OK */
        !           310:                return 1;
        !           311:        }
        !           312:        return 0;
        !           313: }
        !           314: 
        !           315: static int
        !           316: can_write_socket(int s, int msec)
        !           317: {
        !           318:        struct pollfd fds[1];
        !           319:        int rc;
        !           320: 
        !           321:        fds[0].fd = s;
        !           322:        fds[0].events = POLLOUT;
        !           323:  retry:
        !           324:        do {
        !           325:                rc = poll(fds, 1, msec);
        !           326:        } while (rc == -1 && errno == EINTR);
        !           327:        if (rc == -1 && errno == EAGAIN) {
        !           328:                goto retry;
        !           329:        }
        !           330:        if (rc < 0) {
        !           331:                /* error */
        !           332:                return -1;
        !           333:        }
        !           334:        if (fds[0].revents & POLLOUT) {
        !           335:                /* write OK */
        !           336:                return 1;
        !           337:        }
        !           338:        return 0;
        !           339: }
        !           340: #endif /* USE_POLLWAIT */
        !           341: 
        !           342: ssize_t
        !           343: istgt_read_socket(int s, void *buf, size_t nbytes, int timeout)
        !           344: {
        !           345:        ssize_t n;
        !           346: #ifdef USE_POLLWAIT
        !           347:        int msec = POLLWAIT;
        !           348:        int rc;
        !           349: #endif /* USE_POLLWAIT */
        !           350: 
        !           351:        if (nbytes == 0)
        !           352:                return 0;
        !           353: 
        !           354: #ifdef USE_POLLWAIT
        !           355:        msec = timeout * 1000;
        !           356:        rc = can_read_socket(s, msec);
        !           357:        if (rc < 0) {
        !           358:                return -1;
        !           359:        }
        !           360:        if (rc == 0) {
        !           361:                /* TIMEOUT */
        !           362:                return -2;
        !           363:        }
        !           364:  retry:
        !           365:        do {
        !           366:                n = read(s, buf, nbytes);
        !           367:        } while (n == -1 && errno == EINTR);
        !           368:        if (n == -1 && errno == EAGAIN) {
        !           369:                goto retry;
        !           370:        }
        !           371:        if (n < 0) {
        !           372:                return -1;
        !           373:        }
        !           374: #else
        !           375:        do {
        !           376:                n = recv(s, buf, nbytes, 0);
        !           377:        } while (n == -1 && errno == EINTR);
        !           378:        if (n == -1 && errno == EAGAIN) {
        !           379:                /* TIMEOUT */
        !           380:                return -2;
        !           381:        }
        !           382:        if (n == -1) {
        !           383:                return -1;
        !           384:        }
        !           385: #endif /* USE_POLLWAIT */
        !           386:        return n;
        !           387: }
        !           388: 
        !           389: ssize_t
        !           390: istgt_write_socket(int s, const void *buf, size_t nbytes, int timeout)
        !           391: {
        !           392:        ssize_t n;
        !           393: #ifdef USE_POLLWAIT
        !           394:        int msec = POLLWAIT;
        !           395:        int rc;
        !           396: #endif /* USE_POLLWAIT */
        !           397: 
        !           398:        if (nbytes == 0)
        !           399:                return 0;
        !           400: 
        !           401: #ifdef USE_POLLWAIT
        !           402:        msec = timeout * 1000;
        !           403:        rc = can_write_socket(s, msec);
        !           404:        if (rc < 0) {
        !           405:                return -1;
        !           406:        }
        !           407:        if (rc == 0) {
        !           408:                /* TIMEOUT */
        !           409:                return -2;
        !           410:        }
        !           411:  retry:
        !           412:        do {
        !           413:                n = write(s, buf, nbytes);
        !           414:        } while (n == -1 && errno == EINTR);
        !           415:        if (n == -1 && errno == EAGAIN) {
        !           416:                goto retry;
        !           417:        }
        !           418:        if (n < 0) {
        !           419:                return -1;
        !           420:        }
        !           421: #else
        !           422:        do {
        !           423:                n = send(s, buf, nbytes, 0);
        !           424:        } while (n == -1 && (errno == EINTR || errno == EAGAIN));
        !           425:        if (n == -1) {
        !           426:                return -1;
        !           427:        }
        !           428: #endif /* USE_POLLWAIT */
        !           429:        return n;
        !           430: }
        !           431: 
        !           432: ssize_t
        !           433: istgt_readline_socket(int sock, char *buf, size_t size, char *tmp, size_t tmpsize, int *tmpidx, int *tmpcnt, int timeout)
        !           434: {
        !           435:        unsigned char *up, *utp;
        !           436:        ssize_t maxsize;
        !           437:        ssize_t total;
        !           438:        ssize_t n;
        !           439:        int got_cr;
        !           440:        int idx, cnt;
        !           441:        int ch;
        !           442: 
        !           443:        if (size < 2) {
        !           444:                return -1;
        !           445:        }
        !           446: 
        !           447:        up = (unsigned char *) buf;
        !           448:        utp = (unsigned char *) tmp;
        !           449:        maxsize = size - 2; /* LF + NUL */
        !           450:        total = 0;
        !           451:        idx = *tmpidx;
        !           452:        cnt = *tmpcnt;
        !           453:        got_cr = 0;
        !           454: 
        !           455:        /* receive with LF */
        !           456:        while (total < maxsize) {
        !           457:                /* fill temporary buffer */
        !           458:                if (idx == cnt) {
        !           459:                        *tmpidx = idx;
        !           460:                        up[total] = '\0';
        !           461:                        n = istgt_read_socket(sock, tmp, tmpsize, timeout);
        !           462:                        if (n < 0) {
        !           463:                                if (total != 0) {
        !           464:                                        up[total] = '\0';
        !           465:                                        return total;
        !           466:                                }
        !           467:                                return -1;
        !           468:                        }
        !           469:                        if (n == 0) {
        !           470:                                /* EOF */
        !           471:                                up[total] = '\0';
        !           472:                                return total;
        !           473:                        }
        !           474:                        /* got n bytes */
        !           475:                        cnt = *tmpcnt = n;
        !           476:                        idx = 0;
        !           477:                }
        !           478: 
        !           479:                /* copy from temporary until LF */
        !           480:                ch = utp[idx++];
        !           481:                if (got_cr && ch != '\n') {
        !           482:                        /* CR only */
        !           483:                        /* back to temporary */
        !           484:                        idx--;
        !           485:                        /* remove CR */
        !           486:                        total--;
        !           487:                        break;
        !           488:                } else if (ch == '\n') {
        !           489:                        if (got_cr) {
        !           490:                                /* CRLF */
        !           491:                                /* remove CR */
        !           492:                                total--;
        !           493:                        } else {
        !           494:                                /* LF only */
        !           495:                        }
        !           496:                        break;
        !           497:                } else if (ch == '\r') {
        !           498:                        got_cr = 1;
        !           499:                }
        !           500:                up[total++] = ch;
        !           501:        }
        !           502:        *tmpidx = idx;
        !           503:        /* always append LF + NUL */
        !           504:        up[total++] = '\n';
        !           505:        up[total] = '\0';
        !           506:        return total;
        !           507: }
        !           508: 
        !           509: static ssize_t
        !           510: istgt_allwrite_socket(int s, const void *buf, size_t nbytes, int timeout)
        !           511: {
        !           512:        const uint8_t *cp;
        !           513:        size_t total;
        !           514:        ssize_t n;
        !           515: 
        !           516:        total = 0;
        !           517:        cp = (const uint8_t *) buf;
        !           518:        do {
        !           519:                n = istgt_write_socket(s, cp + total, (nbytes - total), timeout);
        !           520:                if (n < 0) {
        !           521:                        return n;
        !           522:                }
        !           523:                total += n;
        !           524:        } while (total < nbytes);
        !           525:        return total;
        !           526: }
        !           527: 
        !           528: ssize_t
        !           529: istgt_writeline_socket(int sock, const char *buf, int timeout)
        !           530: {
        !           531:        const unsigned char *up;
        !           532:        ssize_t total;
        !           533:        ssize_t n;
        !           534:        int idx;
        !           535:        int ch;
        !           536: 
        !           537:        up = (const unsigned char *) buf;
        !           538:        total = 0;
        !           539:        idx = 0;
        !           540: 
        !           541:        if (up[0] == '\0') {
        !           542:                /* empty string */
        !           543:                n = istgt_allwrite_socket(sock, "\r\n", 2, timeout);
        !           544:                if (n < 0) {
        !           545:                        return -1;
        !           546:                }
        !           547:                if (n != 2) {
        !           548:                        return -1;
        !           549:                }
        !           550:                total = n;
        !           551:                return total;
        !           552:        }
        !           553: 
        !           554:        /* send with CRLF */
        !           555:        while ((ch = up[idx]) != '\0') {
        !           556:                if (ch == '\r') {
        !           557:                        if (up[idx + 1] == '\n') {
        !           558:                                /* CRLF */
        !           559:                                n = istgt_allwrite_socket(sock, up, idx + 2, timeout);
        !           560:                                if (n < 0) {
        !           561:                                        return -1;
        !           562:                                }
        !           563:                                if (n != idx + 2) {
        !           564:                                        return -1;
        !           565:                                }
        !           566:                                idx += 2;
        !           567:                        } else {
        !           568:                                /* CR Only */
        !           569:                                n = istgt_allwrite_socket(sock, up, idx, timeout);
        !           570:                                if (n < 0) {
        !           571:                                        return -1;
        !           572:                                }
        !           573:                                if (n != idx) {
        !           574:                                        return -1;
        !           575:                                }
        !           576:                                idx += 1;
        !           577:                                n = istgt_allwrite_socket(sock, "\r\n", 2, timeout);
        !           578:                                if (n < 0) {
        !           579:                                        return -1;
        !           580:                                }
        !           581:                                if (n != 2) {
        !           582:                                        return -1;
        !           583:                                }
        !           584:                        }
        !           585:                } else if (ch == '\n') {
        !           586:                        /* LF Only */
        !           587:                        n = istgt_allwrite_socket(sock, up, idx, timeout);
        !           588:                        if (n < 0) {
        !           589:                                return -1;
        !           590:                        }
        !           591:                        if (n != idx) {
        !           592:                                return -1;
        !           593:                        }
        !           594:                        idx += 1;
        !           595:                        n = istgt_allwrite_socket(sock, "\r\n", 2, timeout);
        !           596:                        if (n < 0) {
        !           597:                                return -1;
        !           598:                        }
        !           599:                        if (n != 2) {
        !           600:                                return -1;
        !           601:                        }
        !           602:                } else {
        !           603:                        idx++;
        !           604:                        continue;
        !           605:                }
        !           606:                up += idx;
        !           607:                total += idx;
        !           608:                idx = 0;
        !           609:        }
        !           610: 
        !           611:        if (idx != 0) {
        !           612:                /* no CRLF string */
        !           613:                n = istgt_allwrite_socket(sock, up, idx, timeout);
        !           614:                if (n < 0) {
        !           615:                        return -1;
        !           616:                }
        !           617:                if (n != idx) {
        !           618:                        return -1;
        !           619:                }
        !           620:                n = istgt_allwrite_socket(sock, "\r\n", 2, timeout);
        !           621:                if (n < 0) {
        !           622:                        return -1;
        !           623:                }
        !           624:                if (n != 2) {
        !           625:                        return -1;
        !           626:                }
        !           627:                up += idx;
        !           628:                total += idx + 2;
        !           629:                idx = 0;
        !           630:        }
        !           631: 
        !           632:        return total;
        !           633: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>