File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / mtr / packet / construct_unix.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Mon Oct 21 14:25:31 2019 UTC (4 years, 8 months ago) by misho
Branches: mtr, MAIN
CVS tags: v0_92, HEAD
mtr ver 0.92

    1: /*
    2:     mtr  --  a network diagnostic tool
    3:     Copyright (C) 2016  Matt Kimball
    4: 
    5:     This program is free software; you can redistribute it and/or modify
    6:     it under the terms of the GNU General Public License version 2 as
    7:     published by the Free Software Foundation.
    8: 
    9:     This program is distributed in the hope that it will be useful,
   10:     but WITHOUT ANY WARRANTY; without even the implied warranty of
   11:     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12:     GNU General Public License for more details.
   13: 
   14:     You should have received a copy of the GNU General Public License
   15:     along with this program; if not, write to the Free Software
   16:     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   17: */
   18: 
   19: #include "construct_unix.h"
   20: 
   21: #include <errno.h>
   22: #include <stdio.h>
   23: #include <string.h>
   24: #include <sys/socket.h>
   25: #include <unistd.h>
   26: 
   27: #include "protocols.h"
   28: 
   29: /*  A source of data for computing a checksum  */
   30: struct checksum_source_t {
   31:     const void *data;
   32:     size_t size;
   33: };
   34: 
   35: /*  Compute the IP checksum (or ICMP checksum) of a packet.  */
   36: static
   37: uint16_t compute_checksum(
   38:     const void *packet,
   39:     int size)
   40: {
   41:     const uint8_t *packet_bytes = (uint8_t *) packet;
   42:     uint32_t sum = 0;
   43:     int i;
   44: 
   45:     for (i = 0; i < size; i++) {
   46:         if ((i & 1) == 0) {
   47:             sum += packet_bytes[i] << 8;
   48:         } else {
   49:             sum += packet_bytes[i];
   50:         }
   51:     }
   52: 
   53:     /*
   54:        Sums which overflow a 16-bit value have the high bits
   55:        added back into the low 16 bits.
   56:      */
   57:     while (sum >> 16) {
   58:         sum = (sum >> 16) + (sum & 0xffff);
   59:     }
   60: 
   61:     /*
   62:        The value stored is the one's complement of the
   63:        mathematical sum.
   64:      */
   65:     return (~sum & 0xffff);
   66: }
   67: 
   68: /*  Encode the IP header length field in the order required by the OS.  */
   69: static
   70: uint16_t length_byte_swap(
   71:     const struct net_state_t *net_state,
   72:     uint16_t length)
   73: {
   74:     if (net_state->platform.ip_length_host_order) {
   75:         return length;
   76:     } else {
   77:         return htons(length);
   78:     }
   79: }
   80: 
   81: /*  Construct a combined sockaddr from a source address and source port  */
   82: static
   83: void construct_addr_port(
   84:     struct sockaddr_storage *addr_with_port,
   85:     const struct sockaddr_storage *addr,
   86:     int port)
   87: {
   88:     struct sockaddr_in *addr4;
   89:     struct sockaddr_in6 *addr6;
   90: 
   91:     memcpy(addr_with_port, addr, sizeof(struct sockaddr_storage));
   92: 
   93:     if (addr->ss_family == AF_INET6) {
   94:         addr6 = (struct sockaddr_in6 *) addr_with_port;
   95:         addr6->sin6_port = htons(port);
   96:     } else {
   97:         addr4 = (struct sockaddr_in *) addr_with_port;
   98:         addr4->sin_port = htons(port);
   99:     }
  100: }
  101: 
  102: /*  Construct a header for IP version 4  */
  103: static
  104: void construct_ip4_header(
  105:     const struct net_state_t *net_state,
  106:     char *packet_buffer,
  107:     int packet_size,
  108:     const struct sockaddr_storage *srcaddr,
  109:     const struct sockaddr_storage *destaddr,
  110:     const struct probe_param_t *param)
  111: {
  112:     struct IPHeader *ip;
  113:     struct sockaddr_in *srcaddr4 = (struct sockaddr_in *) srcaddr;
  114:     struct sockaddr_in *destaddr4 = (struct sockaddr_in *) destaddr;
  115: 
  116:     ip = (struct IPHeader *) &packet_buffer[0];
  117: 
  118:     memset(ip, 0, sizeof(struct IPHeader));
  119: 
  120:     ip->version = 0x45;
  121:     ip->tos = param->type_of_service;
  122:     ip->len = length_byte_swap(net_state, packet_size);
  123:     ip->ttl = param->ttl;
  124:     ip->protocol = param->protocol;
  125:     memcpy(&ip->saddr, &srcaddr4->sin_addr, sizeof(uint32_t));
  126:     memcpy(&ip->daddr, &destaddr4->sin_addr, sizeof(uint32_t));
  127: }
  128: 
  129: /*  Construct an ICMP header for IPv4  */
  130: static
  131: void construct_icmp4_header(
  132:     const struct net_state_t *net_state,
  133:     int sequence,
  134:     char *packet_buffer,
  135:     int packet_size,
  136:     const struct probe_param_t *param)
  137: {
  138:     struct ICMPHeader *icmp;
  139:     int icmp_size;
  140: 
  141:     icmp = (struct ICMPHeader *) &packet_buffer[sizeof(struct IPHeader)];
  142:     icmp_size = packet_size - sizeof(struct IPHeader);
  143: 
  144:     memset(icmp, 0, sizeof(struct ICMPHeader));
  145: 
  146:     icmp->type = ICMP_ECHO;
  147:     icmp->id = htons(getpid());
  148:     icmp->sequence = htons(sequence);
  149:     icmp->checksum = htons(compute_checksum(icmp, icmp_size));
  150: }
  151: 
  152: /*  Construct an ICMP header for IPv6  */
  153: static
  154: int construct_icmp6_packet(
  155:     const struct net_state_t *net_state,
  156:     int sequence,
  157:     char *packet_buffer,
  158:     int packet_size,
  159:     const struct probe_param_t *param)
  160: {
  161:     struct ICMPHeader *icmp;
  162: 
  163:     icmp = (struct ICMPHeader *) packet_buffer;
  164: 
  165:     memset(icmp, 0, sizeof(struct ICMPHeader));
  166: 
  167:     icmp->type = ICMP6_ECHO;
  168:     icmp->id = htons(getpid());
  169:     icmp->sequence = htons(sequence);
  170: 
  171:     return 0;
  172: }
  173: 
  174: /*
  175:     Set the port numbers for an outgoing UDP probe.
  176:     There is limited space in the header for a sequence number
  177:     to identify the probe upon return.
  178: 
  179:     We store the sequence number in the destination port, the local
  180:     port, or the checksum.  The location chosen depends upon which
  181:     probe parameters have been requested.
  182: */
  183: static
  184: void set_udp_ports(
  185:     struct UDPHeader *udp,
  186:     int sequence,
  187:     const struct probe_param_t *param)
  188: {
  189:     if (param->dest_port) {
  190:         udp->dstport = htons(param->dest_port);
  191: 
  192:         if (param->local_port) {
  193:             udp->srcport = htons(param->local_port);
  194:             udp->checksum = htons(sequence);
  195:         } else {
  196:             udp->srcport = htons(sequence);
  197:             udp->checksum = 0;
  198:         }
  199:     } else {
  200:         udp->dstport = htons(sequence);
  201: 
  202:         if (param->local_port) {
  203:             udp->srcport = htons(param->local_port);
  204:         } else {
  205:             udp->srcport = htons(getpid());
  206:         }
  207: 
  208:         udp->checksum = 0;
  209:     }
  210: }
  211: 
  212: /*
  213:     Construct a header for UDP probes, using the port number associated
  214:     with the probe.
  215: */
  216: static
  217: void construct_udp4_header(
  218:     const struct net_state_t *net_state,
  219:     int sequence,
  220:     char *packet_buffer,
  221:     int packet_size,
  222:     const struct probe_param_t *param)
  223: {
  224:     struct UDPHeader *udp;
  225:     int udp_size;
  226: 
  227:     udp = (struct UDPHeader *) &packet_buffer[sizeof(struct IPHeader)];
  228:     udp_size = packet_size - sizeof(struct IPHeader);
  229: 
  230:     memset(udp, 0, sizeof(struct UDPHeader));
  231: 
  232:     set_udp_ports(udp, sequence, param);
  233:     udp->length = htons(udp_size);
  234: }
  235: 
  236: /*  Construct a header for UDPv6 probes  */
  237: static
  238: int construct_udp6_packet(
  239:     const struct net_state_t *net_state,
  240:     int sequence,
  241:     char *packet_buffer,
  242:     int packet_size,
  243:     const struct probe_param_t *param)
  244: {
  245:     int udp_socket = net_state->platform.udp6_send_socket;
  246:     struct UDPHeader *udp;
  247:     int udp_size;
  248: 
  249:     udp = (struct UDPHeader *) packet_buffer;
  250:     udp_size = packet_size;
  251: 
  252:     memset(udp, 0, sizeof(struct UDPHeader));
  253: 
  254:     set_udp_ports(udp, sequence, param);
  255:     udp->length = htons(udp_size);
  256: 
  257:     /*
  258:        Instruct the kernel to put the pseudoheader checksum into the
  259:        UDP header.
  260:      */
  261:     int chksum_offset = (char *) &udp->checksum - (char *) udp;
  262:     if (setsockopt(udp_socket, IPPROTO_IPV6,
  263:                    IPV6_CHECKSUM, &chksum_offset, sizeof(int))) {
  264:         return -1;
  265:     }
  266: 
  267:     return 0;
  268: }
  269: 
  270: /*
  271:     Set the socket options for an outgoing stream protocol socket based on
  272:     the packet parameters.
  273: */
  274: static
  275: int set_stream_socket_options(
  276:     int stream_socket,
  277:     const struct probe_param_t *param)
  278: {
  279:     int level;
  280:     int opt;
  281:     int reuse = 1;
  282: 
  283:     /*  Allow binding to a local port previously in use  */
  284: #ifdef SO_REUSEPORT
  285:     /*
  286:        FreeBSD wants SO_REUSEPORT in addition to SO_REUSEADDR to
  287:        bind to the same port
  288:      */
  289:     if (setsockopt(stream_socket, SOL_SOCKET, SO_REUSEPORT,
  290:                    &reuse, sizeof(int)) == -1) {
  291: 
  292:         return -1;
  293:     }
  294: #endif
  295: 
  296:     if (setsockopt(stream_socket, SOL_SOCKET, SO_REUSEADDR,
  297:                    &reuse, sizeof(int)) == -1) {
  298: 
  299:         return -1;
  300:     }
  301: 
  302:     /*  Set the number of hops the probe will transit across  */
  303:     if (param->ip_version == 6) {
  304:         level = IPPROTO_IPV6;
  305:         opt = IPV6_UNICAST_HOPS;
  306:     } else {
  307:         level = IPPROTO_IP;
  308:         opt = IP_TTL;
  309:     }
  310: 
  311:     if (setsockopt(stream_socket, level, opt, &param->ttl, sizeof(int)) ==
  312:         -1) {
  313: 
  314:         return -1;
  315:     }
  316: 
  317:     /*  Set the "type of service" field of the IP header  */
  318:     if (param->ip_version == 6) {
  319:         level = IPPROTO_IPV6;
  320:         opt = IPV6_TCLASS;
  321:     } else {
  322:         level = IPPROTO_IP;
  323:         opt = IP_TOS;
  324:     }
  325: 
  326:     if (setsockopt(stream_socket, level, opt,
  327:                    &param->type_of_service, sizeof(int)) == -1) {
  328: 
  329:         return -1;
  330:     }
  331: #ifdef SO_MARK
  332:     if (param->routing_mark) {
  333:         if (setsockopt(stream_socket, SOL_SOCKET,
  334:                        SO_MARK, &param->routing_mark, sizeof(int))) {
  335:             return -1;
  336:         }
  337:     }
  338: #endif
  339: 
  340:     return 0;
  341: }
  342: 
  343: /*
  344:     Open a TCP or SCTP socket, respecting the probe paramters as much as
  345:     we can, and use it as an outgoing probe.
  346: */
  347: static
  348: int open_stream_socket(
  349:     const struct net_state_t *net_state,
  350:     int protocol,
  351:     int port,
  352:     const struct sockaddr_storage *src_sockaddr,
  353:     const struct sockaddr_storage *dest_sockaddr,
  354:     const struct probe_param_t *param)
  355: {
  356:     int stream_socket;
  357:     int addr_len;
  358:     int dest_port;
  359:     struct sockaddr_storage dest_port_addr;
  360:     struct sockaddr_storage src_port_addr;
  361: 
  362:     if (param->ip_version == 6) {
  363:         stream_socket = socket(AF_INET6, SOCK_STREAM, protocol);
  364:         addr_len = sizeof(struct sockaddr_in6);
  365:     } else if (param->ip_version == 4) {
  366:         stream_socket = socket(AF_INET, SOCK_STREAM, protocol);
  367:         addr_len = sizeof(struct sockaddr_in);
  368:     } else {
  369:         errno = EINVAL;
  370:         return -1;
  371:     }
  372: 
  373:     if (stream_socket == -1) {
  374:         return -1;
  375:     }
  376: 
  377:     set_socket_nonblocking(stream_socket);
  378: 
  379:     if (set_stream_socket_options(stream_socket, param)) {
  380:         close(stream_socket);
  381:         return -1;
  382:     }
  383: 
  384:     /*
  385:        Bind to a known local port so we can identify which probe
  386:        causes a TTL expiration.
  387:      */
  388:     construct_addr_port(&src_port_addr, src_sockaddr, port);
  389:     if (bind(stream_socket, (struct sockaddr *) &src_port_addr, addr_len)) {
  390:         close(stream_socket);
  391:         return -1;
  392:     }
  393: 
  394:     if (param->dest_port) {
  395:         dest_port = param->dest_port;
  396:     } else {
  397:         /*  Use http if no port is specified  */
  398:         dest_port = HTTP_PORT;
  399:     }
  400: 
  401:     /*  Attempt a connection  */
  402:     construct_addr_port(&dest_port_addr, dest_sockaddr, dest_port);
  403:     if (connect
  404:         (stream_socket, (struct sockaddr *) &dest_port_addr, addr_len)) {
  405: 
  406:         /*  EINPROGRESS simply means the connection is in progress  */
  407:         if (errno != EINPROGRESS) {
  408:             close(stream_socket);
  409:             return -1;
  410:         }
  411:     }
  412: 
  413:     return stream_socket;
  414: }
  415: 
  416: /*
  417:     Determine the size of the constructed packet based on the packet
  418:     parameters.  This is the amount of space the packet *we* construct
  419:     uses, and doesn't include any headers the operating system tacks
  420:     onto the packet.  (Such as the IPv6 header on non-Linux operating
  421:     systems.)
  422: */
  423: static
  424: int compute_packet_size(
  425:     const struct net_state_t *net_state,
  426:     const struct probe_param_t *param)
  427: {
  428:     int packet_size;
  429: 
  430:     if (param->protocol == IPPROTO_TCP) {
  431:         return 0;
  432:     }
  433: #ifdef IPPROTO_SCTP
  434:     if (param->protocol == IPPROTO_SCTP) {
  435:         return 0;
  436:     }
  437: #endif
  438: 
  439:     /*  Start by determining the full size, including omitted headers  */
  440:     if (param->ip_version == 6) {
  441:         packet_size = sizeof(struct IP6Header);
  442:     } else if (param->ip_version == 4) {
  443:         packet_size = sizeof(struct IPHeader);
  444:     } else {
  445:         errno = EINVAL;
  446:         return -1;
  447:     }
  448: 
  449:     if (param->protocol == IPPROTO_ICMP) {
  450:         packet_size += sizeof(struct ICMPHeader);
  451:     } else if (param->protocol == IPPROTO_UDP) {
  452:         packet_size += sizeof(struct UDPHeader);
  453: 
  454:         /*  We may need to put the sequence number in the payload  */
  455:         packet_size += sizeof(int);
  456:     } else {
  457:         errno = EINVAL;
  458:         return -1;
  459:     }
  460: 
  461:     /*
  462:        If the requested size from send-probe is greater, extend the
  463:        packet size.
  464:      */
  465:     if (param->packet_size > packet_size) {
  466:         packet_size = param->packet_size;
  467:     }
  468: 
  469:     /*
  470:        Since we don't explicitly construct the IPv6 header, we
  471:        need to account for it in our transmitted size.
  472:      */
  473:     if (param->ip_version == 6) {
  474:         packet_size -= sizeof(struct IP6Header);
  475:     }
  476: 
  477:     return packet_size;
  478: }
  479: 
  480: /*  Construct a packet for an IPv4 probe  */
  481: static
  482: int construct_ip4_packet(
  483:     const struct net_state_t *net_state,
  484:     int *packet_socket,
  485:     int sequence,
  486:     char *packet_buffer,
  487:     int packet_size,
  488:     const struct sockaddr_storage *src_sockaddr,
  489:     const struct sockaddr_storage *dest_sockaddr,
  490:     const struct probe_param_t *param)
  491: {
  492:     int send_socket = net_state->platform.ip4_send_socket;
  493:     bool is_stream_protocol = false;
  494: 
  495:     if (param->protocol == IPPROTO_TCP) {
  496:         is_stream_protocol = true;
  497: #ifdef IPPROTO_SCTP
  498:     } else if (param->protocol == IPPROTO_SCTP) {
  499:         is_stream_protocol = true;
  500: #endif
  501:     } else {
  502:         construct_ip4_header(net_state, packet_buffer, packet_size,
  503:                              src_sockaddr, dest_sockaddr, param);
  504: 
  505:         if (param->protocol == IPPROTO_ICMP) {
  506:             construct_icmp4_header(net_state, sequence, packet_buffer,
  507:                                    packet_size, param);
  508:         } else if (param->protocol == IPPROTO_UDP) {
  509:             construct_udp4_header(net_state, sequence, packet_buffer,
  510:                                   packet_size, param);
  511:         } else {
  512:             errno = EINVAL;
  513:             return -1;
  514:         }
  515:     }
  516: 
  517:     if (is_stream_protocol) {
  518:         send_socket =
  519:             open_stream_socket(net_state, param->protocol, sequence,
  520:                                src_sockaddr, dest_sockaddr, param);
  521: 
  522:         if (send_socket == -1) {
  523:             return -1;
  524:         }
  525: 
  526:         *packet_socket = send_socket;
  527:         return 0;
  528:     }
  529: 
  530:     /*
  531:        The routing mark requires CAP_NET_ADMIN, as opposed to the
  532:        CAP_NET_RAW which we are sometimes explicitly given.
  533:        If we don't have CAP_NET_ADMIN, this will fail, so we'll 
  534:        only set the mark if the user has explicitly requested it.
  535: 
  536:        Unfortunately, this means that once the mark is set, it won't
  537:        be set on the socket again until a new mark is explicitly
  538:        specified.
  539:      */
  540: #ifdef SO_MARK
  541:     if (param->routing_mark) {
  542:         if (setsockopt(send_socket, SOL_SOCKET,
  543:                        SO_MARK, &param->routing_mark, sizeof(int))) {
  544:             return -1;
  545:         }
  546:     }
  547: #endif
  548: 
  549:     return 0;
  550: }
  551: 
  552: /*  Construct a packet for an IPv6 probe  */
  553: static
  554: int construct_ip6_packet(
  555:     const struct net_state_t *net_state,
  556:     int *packet_socket,
  557:     int sequence,
  558:     char *packet_buffer,
  559:     int packet_size,
  560:     const struct sockaddr_storage *src_sockaddr,
  561:     const struct sockaddr_storage *dest_sockaddr,
  562:     const struct probe_param_t *param)
  563: {
  564:     int send_socket;
  565:     bool is_stream_protocol = false;
  566:     bool bind_send_socket = true;
  567:     struct sockaddr_storage current_sockaddr;
  568:     int current_sockaddr_len;
  569: 
  570:     if (param->protocol == IPPROTO_TCP) {
  571:         is_stream_protocol = true;
  572: #ifdef IPPROTO_SCTP
  573:     } else if (param->protocol == IPPROTO_SCTP) {
  574:         is_stream_protocol = true;
  575: #endif
  576:     } else if (param->protocol == IPPROTO_ICMP) {
  577:         send_socket = net_state->platform.icmp6_send_socket;
  578: 
  579:         if (construct_icmp6_packet
  580:             (net_state, sequence, packet_buffer, packet_size, param)) {
  581:             return -1;
  582:         }
  583:     } else if (param->protocol == IPPROTO_UDP) {
  584:         send_socket = net_state->platform.udp6_send_socket;
  585: 
  586:         if (construct_udp6_packet
  587:             (net_state, sequence, packet_buffer, packet_size, param)) {
  588:             return -1;
  589:         }
  590:     } else {
  591:         errno = EINVAL;
  592:         return -1;
  593:     }
  594: 
  595:     if (is_stream_protocol) {
  596:         send_socket =
  597:             open_stream_socket(net_state, param->protocol, sequence,
  598:                                src_sockaddr, dest_sockaddr, param);
  599: 
  600:         if (send_socket == -1) {
  601:             return -1;
  602:         }
  603: 
  604:         *packet_socket = send_socket;
  605:         return 0;
  606:     }
  607: 
  608:     /*
  609:        Check the current socket address, and if it is the same
  610:        as the source address we intend, we will skip the bind.
  611:        This is to accomodate Solaris, which, as of Solaris 11.3,
  612:        will return an EINVAL error on bind if the socket is already
  613:        bound, even if the same address is used.
  614:      */
  615:     current_sockaddr_len = sizeof(struct sockaddr_in6);
  616:     if (getsockname(send_socket, (struct sockaddr *) &current_sockaddr,
  617:                     &current_sockaddr_len) == 0) {
  618: 
  619:         if (memcmp(&current_sockaddr,
  620:                    src_sockaddr, sizeof(struct sockaddr_in6)) == 0) {
  621:             bind_send_socket = false;
  622:         }
  623:     }
  624: 
  625:     /*  Bind to our local address  */
  626:     if (bind_send_socket) {
  627:         if (bind(send_socket, (struct sockaddr *) src_sockaddr,
  628:                  sizeof(struct sockaddr_in6))) {
  629:             return -1;
  630:         }
  631:     }
  632: 
  633:     /*  The traffic class in IPv6 is analagous to ToS in IPv4  */
  634:     if (setsockopt(send_socket, IPPROTO_IPV6,
  635:                    IPV6_TCLASS, &param->type_of_service, sizeof(int))) {
  636:         return -1;
  637:     }
  638: 
  639:     /*  Set the time-to-live  */
  640:     if (setsockopt(send_socket, IPPROTO_IPV6,
  641:                    IPV6_UNICAST_HOPS, &param->ttl, sizeof(int))) {
  642:         return -1;
  643:     }
  644: #ifdef SO_MARK
  645:     if (param->routing_mark) {
  646:         if (setsockopt(send_socket,
  647:                        SOL_SOCKET, SO_MARK, &param->routing_mark,
  648:                        sizeof(int))) {
  649:             return -1;
  650:         }
  651:     }
  652: #endif
  653: 
  654:     return 0;
  655: }
  656: 
  657: /*  Construct a probe packet based on the probe parameters  */
  658: int construct_packet(
  659:     const struct net_state_t *net_state,
  660:     int *packet_socket,
  661:     int sequence,
  662:     char *packet_buffer,
  663:     int packet_buffer_size,
  664:     const struct sockaddr_storage *dest_sockaddr,
  665:     const struct sockaddr_storage *src_sockaddr,
  666:     const struct probe_param_t *param)
  667: {
  668:     int packet_size;
  669: 
  670:     packet_size = compute_packet_size(net_state, param);
  671:     if (packet_size < 0) {
  672:         return -1;
  673:     }
  674: 
  675:     if (packet_buffer_size < packet_size) {
  676:         errno = EINVAL;
  677:         return -1;
  678:     }
  679: 
  680:     memset(packet_buffer, param->bit_pattern, packet_size);
  681: 
  682:     if (param->ip_version == 6) {
  683:         if (construct_ip6_packet(net_state, packet_socket, sequence,
  684:                                  packet_buffer, packet_size,
  685:                                  src_sockaddr, dest_sockaddr, param)) {
  686:             return -1;
  687:         }
  688:     } else if (param->ip_version == 4) {
  689:         if (construct_ip4_packet(net_state, packet_socket, sequence,
  690:                                  packet_buffer, packet_size,
  691:                                  src_sockaddr, dest_sockaddr, param)) {
  692:             return -1;
  693:         }
  694:     } else {
  695:         errno = EINVAL;
  696:         return -1;
  697:     }
  698: 
  699:     return packet_size;
  700: }

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