/************************************************************************ * NAME: fnet_igmp_leave * * DESCRIPTION: Sends a Leave Group message. *************************************************************************/ void fnet_igmp_leave( fnet_netif_t *netif, fnet_ip4_addr_t group_addr ) { #if FNET_CFG_IGMP_VERSION == 2 fnet_netbuf_t *nb_header; fnet_igmp_header_t *igmp_header; fnet_ip4_addr_t dest_ip = FNET_IP4_ADDR_INIT(224, 0, 0, 2); /* All-routers multicast group.*/ /* Construct IGMP header*/ if((nb_header = fnet_netbuf_new(sizeof(fnet_igmp_header_t), FNET_FALSE)) != 0) { /* * When a host leaves a multicast group, if it was the last host to * reply to a Query with a Membership Report for that group, it SHOULD * send a Leave Group message to the all-routers multicast group (224.0.0.2). */ igmp_header = nb_header->data_ptr; igmp_header->type = IGMP_HEADER_TYPE_LEAVE_GROUP; /* Type.*/ igmp_header->max_resp_time = 0; /* Unused field, zeroed when sent, ignored when received.*/ igmp_header->group_addr = group_addr ; /* Group address field.*/ igmp_header->checksum = 0; igmp_header->checksum = fnet_checksum(nb_header, (int)nb_header->total_length); /* RFC 1112: A Report is sent with an IP destination address equal to the * host group address being reported, and with an IP time-to-live of 1. */ fnet_ip_output(netif, INADDR_ANY, dest_ip /*dest_addr*/, FNET_IP_PROTOCOL_IGMP, FNET_IGMP_TOS, FNET_IGMP_TTL, nb_header, 0, 0, 0); } #endif /* FNET_CFG_IGMP_VERSION */ }
/************************************************************************ * NAME: fnet_igmp_join * * DESCRIPTION: Sends Host Membership Reports. *************************************************************************/ void fnet_igmp_join( fnet_netif_t *netif, fnet_ip4_addr_t group_addr ) { fnet_netbuf_t *nb_header; fnet_igmp_header_t *igmp_header; /* Construct IGMP header*/ if((nb_header = fnet_netbuf_new(sizeof(fnet_igmp_header_t), FNET_FALSE)) != 0) { igmp_header = nb_header->data_ptr; /* Type.*/ #if FNET_CFG_IGMP_VERSION == 1 igmp_header->type = IGMP_HEADER_TYPE_REPORT_V1; #else /* FNET_CFG_IGMP_VERSION == 2 */ igmp_header->type = IGMP_HEADER_TYPE_REPORT_V2; #endif igmp_header->max_resp_time = 0; /* Unused field, zeroed when sent, ignored when received.*/ igmp_header->group_addr = group_addr ; /* Group address field.*/ igmp_header->checksum = 0; igmp_header->checksum = fnet_checksum(nb_header, (int)nb_header->total_length); /* RFC 1112: A Report is sent with an IP destination address equal to the * host group address being reported, and with an IP time-to-live of 1. */ fnet_ip_output(netif, INADDR_ANY, group_addr /*dest_addr*/, FNET_IP_PROTOCOL_IGMP, FNET_IGMP_TOS, FNET_IGMP_TTL, nb_header, 0, 0, 0); } }
/************************************************************************ * NAME: fnet_arp_request * * DESCRIPTION: Sends ARP request. *************************************************************************/ void fnet_arp_request( fnet_netif_t *netif, fnet_ip4_addr_t ipaddr ) { fnet_arp_header_t *arp_hdr; fnet_mac_addr_t sender_addr; fnet_netbuf_t *nb; if((nb = fnet_netbuf_new(sizeof(fnet_arp_header_t), FNET_TRUE)) != 0) { arp_hdr = nb->data_ptr; arp_hdr->hard_type = FNET_HTONS(FNET_ARP_HARD_TYPE); /* The type of hardware address (=1 for Ethernet).*/ arp_hdr->prot_type = FNET_HTONS(FNET_ETH_TYPE_IP4); /* The type of protocol address (=0x0800 for IP). */ arp_hdr->hard_size = FNET_ARP_HARD_SIZE; /* The size in bytes of the hardware address (=6). */ arp_hdr->prot_size = FNET_ARP_PROT_SIZE; /* The size in bytes of the protocol address (=4). */ arp_hdr->op = FNET_HTONS(FNET_ARP_OP_REQUEST); /* Opcode. */ fnet_netif_get_hw_addr(netif, sender_addr, sizeof(fnet_mac_addr_t)); fnet_memcpy(arp_hdr->target_hard_addr, fnet_eth_null_addr, sizeof(fnet_mac_addr_t)); fnet_memcpy(arp_hdr->sender_hard_addr, sender_addr, sizeof(fnet_mac_addr_t)); arp_hdr->targer_prot_addr = ipaddr; /* Protocol address of target of this packet.*/ arp_hdr->sender_prot_addr = netif->ip4_addr.address; /* Protocol address of sender of this packet.*/ fnet_arp_trace("TX", arp_hdr); /* Print ARP header. */ ((fnet_eth_if_t *)(netif->if_ptr))->output(netif, FNET_ETH_TYPE_ARP, fnet_eth_broadcast, nb); } }
/************************************************************************ * NAME: fnet_netbuf_from_buf * * DESCRIPTION: Creates a new net_buf and fills it by a content of * the external data buffer. *************************************************************************/ fnet_netbuf_t *fnet_netbuf_from_buf( void *data_ptr, int len, int drain ) { fnet_netbuf_t *nb; nb = fnet_netbuf_new(len, drain); if(nb) fnet_memcpy(nb->data_ptr, data_ptr, (unsigned int)len); return (nb); }
/************************************************************************ * NAME: fnet_netbuf_from_buf * * DESCRIPTION: Creates a new net_buf and fills it by a content of * the external data buffer. *************************************************************************/ fnet_netbuf_t *fnet_netbuf_from_buf( void *data_ptr, fnet_size_t len, fnet_bool_t drain ) { fnet_netbuf_t *nb; nb = fnet_netbuf_new(len, drain); if(nb) { fnet_memcpy(nb->data_ptr, data_ptr, len); } return (nb); }
/************************************************************************ * NAME: fnet_icmp_error * * DESCRIPTION: Sends ICMP error message. *************************************************************************/ void fnet_icmp_error( fnet_netif_t *netif, fnet_uint8_t type, fnet_uint8_t code, fnet_netbuf_t *nb ) { fnet_ip_header_t *ipheader; fnet_netbuf_t *nb_header; fnet_icmp_err_header_t *icmpheader; fnet_ip4_addr_t source_addr; fnet_ip4_addr_t destination_addr; if(nb) { ipheader = (fnet_ip_header_t *)nb->data_ptr; source_addr = ipheader->source_addr; destination_addr = ipheader->desination_addr; /* Do not send error if not the first fragment of message (RFC1122)*/ if((FNET_IP_HEADER_GET_OFFSET(ipheader) != 0u) || /* Do not send error on ICMP error messages*/ ((ipheader->protocol == FNET_IP_PROTOCOL_ICMP) && (!FNET_ICMP_IS_QUERY_TYPE(((fnet_icmp_header_t *)((fnet_uint8_t *)(nb->data_ptr) + (FNET_IP_HEADER_GET_HEADER_LENGTH(ipheader) << 2)))->type))) /* Do not send error on a datagram whose source address does not define a single * host -- e.g., a zero address, a loopback address, a * broadcast address, a multicast address, or a Class E * address.*/ || (fnet_ip_addr_is_broadcast(source_addr, netif)) || FNET_IP4_ADDR_IS_MULTICAST(source_addr) || FNET_IP4_CLASS_E(source_addr) /* Do not send error on a datagram destined to an IP broadcast or IP multicast address*/ || (fnet_ip_addr_is_broadcast(destination_addr, netif)) || FNET_IP4_ADDR_IS_MULTICAST(destination_addr) /* Do not send error on datagram sent as a link-layer broadcast or multicast.*/ ||((nb->flags & FNET_NETBUF_FLAG_BROADCAST) != 0u) || ((nb->flags & FNET_NETBUF_FLAG_MULTICAST) != 0u) ) { goto FREE_NB; } /* Construct ICMP error header*/ if((nb_header = fnet_netbuf_new((sizeof(fnet_icmp_err_header_t) - sizeof(fnet_ip_header_t)), FNET_FALSE)) == 0) { goto FREE_NB; } icmpheader = (fnet_icmp_err_header_t *)nb_header->data_ptr; icmpheader->fields.unused = 0u; if(type == FNET_ICMP_PARAMPROB) { icmpheader->fields.ptr = fnet_htons((fnet_uint16_t)code); code = 0u; } else if((type == FNET_ICMP_PARAMPROB) && (code == FNET_ICMP_UNREACHABLE_NEEDFRAG) && netif) { icmpheader->fields.mtu = fnet_htons((fnet_uint16_t)netif->mtu); } else {} icmpheader->header.type = type; icmpheader->header.code = code; if((fnet_size_t)((FNET_IP_HEADER_GET_HEADER_LENGTH(ipheader) << 2) + 8u) < nb->total_length) { fnet_netbuf_trim(&nb, (fnet_int32_t)((fnet_size_t)((FNET_IP_HEADER_GET_HEADER_LENGTH(ipheader) << 2) + 8u) - nb->total_length)); } nb = fnet_netbuf_concat(nb_header, nb); fnet_icmp_output(netif, destination_addr, source_addr, nb); return; FREE_NB: fnet_netbuf_free_chain(nb); } }
/************************************************************************ * NAME: fnet_netbuf_cut_center * * DESCRIPTION: Cuts len bytes in net_buf queue starting from offset "offset" * *************************************************************************/ fnet_netbuf_t *fnet_netbuf_cut_center( fnet_netbuf_t ** nb_ptr, fnet_size_t offset, fnet_size_t len ) { fnet_netbuf_t *head_nb, *tmp_nb, *nb; fnet_size_t tot_len; if(len == 0u) { return (0); } nb = (fnet_netbuf_t *) *nb_ptr; if((nb->total_length < (len + offset)) || (nb == 0)) { return (0); } if(offset == 0u) /* The first case - when we cut from the begin of buffer.*/ { fnet_netbuf_trim(&nb, (fnet_int32_t)len); *nb_ptr = nb; return (nb); } head_nb = nb; tmp_nb = nb; tot_len = nb->length; while((nb != 0) && (offset >= tot_len)) { nb = nb->next; /* Run up th the first net_buf, which points */ tot_len += nb->length; /* to the data, which should be erased.*/ if((nb != 0) && (offset >= tot_len)) { tmp_nb = nb; /* To store previous pointer. */ } } if(tot_len - nb->length == offset) /* If we start cut from the begin of buffer. */ { nb->total_length = head_nb->total_length; /* For correct fnet_netbuf_trim execution.*/ fnet_netbuf_trim(&tmp_nb->next, (fnet_int32_t)len); head_nb->total_length -= len; return ((fnet_netbuf_t *) *nb_ptr); } /* If only the middle of one net_buf should be cut.*/ if(tot_len - offset > len) { head_nb->total_length -= len; /* Split one net_uf into two.*/ if(((fnet_uint32_t *)nb->data)[0] == 1u) /* If we can simply erase them (reference_counter == 1).*/ { fnet_memcpy((fnet_uint8_t *)nb->data_ptr + nb->length - tot_len + offset, (fnet_uint8_t *)nb->data_ptr + nb->length - tot_len + offset + len, (fnet_size_t)(tot_len - offset - len)); nb->length -= len; } else { head_nb = fnet_netbuf_new(tot_len - offset - len, FNET_FALSE); if(head_nb == 0) /* If no free memory.*/ { nb = (fnet_netbuf_t *) *nb_ptr; nb->total_length += len; return (0); } fnet_memcpy(head_nb->data_ptr, (fnet_uint8_t *)nb->data_ptr + nb->length - tot_len + offset + len, (fnet_size_t)(tot_len - offset - len)); head_nb->next = nb->next; nb->next = head_nb; nb->length -= tot_len - offset; } return ((fnet_netbuf_t *) *nb_ptr); } if(tot_len - offset == len) /* If we cut from the middle of buffer to the end only.*/ { nb->length -= len; head_nb->total_length -= len; return ((fnet_netbuf_t *) *nb_ptr); } else /* Cut from the middle of net_buf to the end and trim remaining info*/ { /* (tot_len-offset < len)*/ nb->length -= tot_len - offset; nb->next->total_length = head_nb->total_length; /* For correct fnet_netbuf_trim execution. */ fnet_netbuf_trim(&nb->next, (fnet_int32_t)(len - (tot_len - offset))); head_nb->total_length -= len; return ((fnet_netbuf_t *) *nb_ptr); } }
/************************************************************************ * NAME: fnet_icmp6_error * * DESCRIPTION: Sends ICMPv6 error message. *************************************************************************/ void fnet_icmp6_error( fnet_netif_t *netif, unsigned char type, unsigned char code, unsigned long param, fnet_netbuf_t *origin_nb ) { fnet_ip6_header_t *ip6_header; fnet_icmp6_err_header_t *icmp6_err_header; fnet_ip6_addr_t *src_ip; const fnet_ip6_addr_t *dest_ip; fnet_netbuf_t *nb_header; if(origin_nb) { /* Limit to FNET_IP6_DEFAULT_MTU. */ if(origin_nb->total_length > (FNET_IP6_DEFAULT_MTU - (sizeof(fnet_icmp6_err_header_t) + sizeof(fnet_ip6_header_t)) )) fnet_netbuf_trim(&origin_nb, (int)(FNET_IP6_DEFAULT_MTU - (sizeof(fnet_icmp6_err_header_t) + sizeof(fnet_ip6_header_t)) - origin_nb->total_length)); ip6_header = origin_nb->data_ptr; src_ip = &ip6_header->source_addr; dest_ip = &ip6_header->destination_addr; /******************************************************************* * RFC 4443: * (e) An ICMPv6 error message MUST NOT be originated as a result of * receiving the following: *******************************************************************/ /* (e.1) An ICMPv6 error message. */ /* (e.2) An ICMPv6 REDIRECT message [IPv6-DISC].*/ if (ip6_header->next_header == FNET_IP_PROTOCOL_ICMP6) /* TBD Extension header case.*/ { /* Make sure the packet has at least a 'TYPE' field */ if (ip6_header->length == 0) { goto FREE_NB; } icmp6_err_header = (fnet_icmp6_err_header_t *)((unsigned char *)ip6_header + sizeof(fnet_ip6_header_t)); if (FNET_ICMP6_TYPE_IS_ERROR(icmp6_err_header->icmp6_header.type) || (icmp6_err_header->icmp6_header.type == FNET_ICMP6_TYPE_REDIRECT ) ) { goto FREE_NB; } } /* * (e.3) A packet destined to an IPv6 multicast address. (There are * two exceptions to this rule: (1) the Packet Too Big Message * (Section 3.2) to allow Path MTU discovery to work for IPv6 * multicast, and (2) the Parameter Problem Message, Code 2 * (Section 3.4) reporting an unrecognized IPv6 option (see * Section 4.2 of [IPv6]) that has the Option Type highestorder * two bits set to 10). * (e.4) A packet sent as a link-layer multicast (the exceptions * from e.3 apply to this case, too). */ if(FNET_IP6_ADDR_IS_MULTICAST(dest_ip) && !( (type == FNET_ICMP6_TYPE_PACKET_TOOBIG) || ((type == FNET_ICMP6_TYPE_PARAM_PROB) && (code == FNET_ICMP6_CODE_PP_OPTION))) ) { goto FREE_NB; } else { if(FNET_IP6_ADDR_IS_MULTICAST(dest_ip)) { /* We may not use multicast address as source. Get real source address. */ dest_ip = fnet_ip6_select_src_addr(netif, src_ip /*dest*/); } } /* * (e.5) A packet sent as a link-layer broadcast (the exceptions * from e.3 apply to this case, too). TBD */ /* * (e.6) A packet whose source address does not uniquely identify a * single node -- e.g., the IPv6 Unspecified Address, an IPv6 * multicast address, or an address known by the ICMP message * originator to be an IPv6 anycast address. */ if(FNET_IP6_ADDR_IS_MULTICAST(src_ip) || FNET_IP6_ADDR_EQUAL(&fnet_ip6_addr_any, src_ip)) { goto FREE_NB; } /* Construct ICMPv6 error header.*/ if((nb_header = fnet_netbuf_new((sizeof(fnet_icmp6_err_header_t)), FNET_FALSE)) == 0) goto FREE_NB; icmp6_err_header = nb_header->data_ptr; icmp6_err_header->icmp6_header.type = type; icmp6_err_header->icmp6_header.code = code; icmp6_err_header->data = fnet_htonl(param); origin_nb = fnet_netbuf_concat(nb_header, origin_nb); fnet_icmp6_output( netif, dest_ip/*ipsrc*/, src_ip/*ipdest*/, 0, origin_nb); return; FREE_NB: fnet_netbuf_free_chain(origin_nb); } }
/************************************************************************ * NAME: fnet_mld_send * * DESCRIPTION: Sends MLD message defined by type: * FNET_ICMP6_TYPE_MULTICAST_LISTENER_REPORT or FNET_ICMP6_TYPE_MULTICAST_LISTENER_DONE *************************************************************************/ static void fnet_mld_send(fnet_netif_t *netif, fnet_ip6_addr_t *group_addr, fnet_uint8_t type) { fnet_netbuf_t *nb; fnet_netbuf_t *nb_option; fnet_mld_header_t *mld_header; fnet_mld_ra_option_header_t *ra_option_header; FNET_COMP_PACKED_VAR fnet_uint16_t *checksum_p; fnet_ip6_addr_t *ip_src; fnet_ip6_addr_t *ip_dst; /* [RFC2710] EXCLUDING the link-scope all-nodes address and any multicast * addresses of scope 0 (reserved) or 1(node-local).*/ if((FNET_IP6_ADDR_MULTICAST_SCOPE(group_addr) > FNET_IP6_ADDR_SCOPE_INTERFACELOCAL) && !FNET_IP6_ADDR_EQUAL(&fnet_ip6_addr_linklocal_allnodes, group_addr)) { /* Construct Router Alert option + MLD meassage */ if((nb = fnet_netbuf_new(sizeof(fnet_mld_header_t), FNET_FALSE)) != 0) { if((nb_option = fnet_netbuf_new(sizeof(fnet_mld_ra_option_header_t), FNET_FALSE)) != 0) { /* Fill Hop-by_Hop Options header.*/ ra_option_header = (fnet_mld_ra_option_header_t *)(nb_option->data_ptr); fnet_memcpy ((void *)ra_option_header, (void *)(&mld_ra_option), sizeof(mld_ra_option)); /* Fill MLD message. */ mld_header = (fnet_mld_header_t *)(nb->data_ptr); fnet_memset_zero(mld_header, sizeof(fnet_mld_header_t)); mld_header->icmp6_header.type = type; FNET_IP6_ADDR_COPY(group_addr, &mld_header->multicast_addr); /* Checksum calculation.*/ mld_header->icmp6_header.checksum = 0; mld_header->icmp6_header.checksum = fnet_checksum_pseudo_start(nb, FNET_HTONS((fnet_uint16_t)FNET_IP_PROTOCOL_ICMP6), (fnet_uint16_t)nb->total_length); checksum_p = &mld_header->icmp6_header.checksum; /* Concatanate Hop-by_Hop Options with MLD header. */ nb = fnet_netbuf_concat(nb_option, nb); /* Source Address Selection for MLD, by RFC3590.*/ /* [RFC3590] MLD Report and Done messages are sent with a link-local address as * the IPv6 source address, if a valid address is available on the interface.*/ ip_src = fnet_netif_get_ip6_addr_valid_link_local(netif); /* [RFC3590] If a valid link-local address is not available (e.g., one has not been configured), * the message is sent with the unspecified address. */ if(ip_src == FNET_NULL) { ip_src = (fnet_ip6_addr_t *)&fnet_ip6_addr_any; netif->mld_invalid = FNET_TRUE; } else { netif->mld_invalid = FNET_FALSE; } /* When a node ceases to listen to a multicast address on an interface, * it SHOULD send a single Done message to the link-scope all-routers * multicast address (FF02::2)*/ if(type == FNET_ICMP6_TYPE_MULTICAST_LISTENER_DONE) { ip_dst =(fnet_ip6_addr_t *)&fnet_ip6_addr_linklocal_allrouters; } else { ip_dst = group_addr; } /* Send via IPv6*/ fnet_ip6_output(netif, ip_src, ip_dst, FNET_IP6_TYPE_HOP_BY_HOP_OPTIONS, FNET_MLD_HOP_LIMIT, nb, checksum_p); } else { fnet_netbuf_free_chain(nb); } } } }
/************************************************************************ * NAME: fnet_udp_output * * DESCRIPTION: UDP output function *************************************************************************/ static fnet_error_t fnet_udp_output( struct sockaddr *src_addr, const struct sockaddr *dest_addr, fnet_socket_option_t *sockoption, fnet_netbuf_t *nb ) { fnet_netbuf_t *nb_header; fnet_udp_header_t *udp_header; fnet_error_t error = FNET_ERR_OK; FNET_COMP_PACKED_VAR fnet_uint16_t *checksum_p; fnet_netif_t *netif = FNET_NULL; fnet_scope_id_t scope_id = 0u; /* Check Scope ID.*/ if(dest_addr->sa_scope_id) /* Take scope id from destination address.*/ { scope_id = dest_addr->sa_scope_id; } else /* Take scope id from source address.*/ { scope_id = src_addr->sa_scope_id; } netif = (fnet_netif_t *)fnet_netif_get_by_scope_id(scope_id); /* It can be FNET_NULL, in case scope_id is 0.*/ /* Construct UDP header.*/ if((nb_header = fnet_netbuf_new(sizeof(fnet_udp_header_t), FNET_TRUE)) == 0) { fnet_netbuf_free_chain(nb); return (FNET_ERR_NOMEM); } udp_header = (fnet_udp_header_t *)nb_header->data_ptr; udp_header->source_port = src_addr->sa_port; /* Source port number.*/ udp_header->destination_port = dest_addr->sa_port; /* Destination port number.*/ nb = fnet_netbuf_concat(nb_header, nb); udp_header->length = fnet_htons((fnet_uint16_t)nb->total_length); /* Length.*/ /* Checksum calculation.*/ udp_header->checksum = 0u; #if FNET_CFG_UDP_CHECKSUM #if FNET_CFG_CPU_ETH_HW_TX_IP_CHECKSUM if( 0 #if FNET_CFG_IP4 ||( (dest_addr->sa_family == AF_INET) && ((netif = fnet_ip_route(((struct sockaddr_in *)(dest_addr))->sin_addr.s_addr))!= FNET_NULL) && (netif->features & FNET_NETIF_FEATURE_HW_TX_PROTOCOL_CHECKSUM) && (fnet_ip_will_fragment(netif, nb->total_length) == FNET_FALSE) /* Fragmented packets are not inspected.*/ ) #endif #if FNET_CFG_IP6 ||( (dest_addr->sa_family == AF_INET6) && (netif || (((netif = fnet_ip6_route(&((struct sockaddr_in6 *)(src_addr))->sin6_addr.s6_addr, &((struct sockaddr_in6 *)(dest_addr))->sin6_addr.s6_addr)))!= FNET_NULL) ) && (netif->features & FNET_NETIF_FEATURE_HW_TX_PROTOCOL_CHECKSUM) && (fnet_ip6_will_fragment(netif, nb->total_length) == FNET_FALSE) /* Fragmented packets are not inspected.*/ ) #endif ) { nb->flags |= FNET_NETBUF_FLAG_HW_PROTOCOL_CHECKSUM; checksum_p = 0; } else #endif /* FNET_CFG_CPU_ETH_HW_TX_IP_CHECKSUM */ { udp_header->checksum = fnet_checksum_pseudo_start( nb, FNET_HTONS((fnet_uint16_t)FNET_IP_PROTOCOL_UDP), (fnet_uint16_t)nb->total_length ); checksum_p = &udp_header->checksum; } #endif /* FNET_CFG_UDP_CHECKSUM */ #if FNET_CFG_IP4 if(dest_addr->sa_family == AF_INET) { error = fnet_ip_output(netif, ((const struct sockaddr_in *)(src_addr))->sin_addr.s_addr, ((const struct sockaddr_in *)(dest_addr))->sin_addr.s_addr, FNET_IP_PROTOCOL_UDP, sockoption->ip_opt.tos, #if FNET_CFG_MULTICAST (fnet_uint8_t)((FNET_IP4_ADDR_IS_MULTICAST(((const struct sockaddr_in *)(dest_addr))->sin_addr.s_addr)?sockoption->ip_opt.ttl_multicast:sockoption->ip_opt.ttl)), #else sockoption->ip_opt.ttl, #endif /* FNET_CFG_MULTICAST */ nb, FNET_UDP_DF, sockoption->so_dontroute, checksum_p ); } else #endif #if FNET_CFG_IP6 if(dest_addr->sa_family == AF_INET6) { error = fnet_ip6_output( netif, fnet_socket_addr_is_unspecified(src_addr)? FNET_NULL : &((struct sockaddr_in6 *)(src_addr))->sin6_addr.s6_addr, &((const struct sockaddr_in6 *)(dest_addr))->sin6_addr.s6_addr, FNET_IP_PROTOCOL_UDP, FNET_IP6_ADDR_IS_MULTICAST(&((const struct sockaddr_in6 *)(dest_addr))->sin6_addr.s6_addr)?sockoption->ip6_opt.hops_multicast:sockoption->ip6_opt.hops_unicast, nb, checksum_p ); } else #endif {} return (error); }