| /** |
| * Copyright (c) 2012 Quantenna Communications, Inc. |
| * All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License |
| * as published by the Free Software Foundation; either version 2 |
| * of the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| **/ |
| |
| #include <compat.h> |
| #include <net/ip.h> |
| #ifdef CONFIG_IPV6 |
| #include <net/ipv6.h> |
| #endif |
| |
| #include <qtn/iputil.h> |
| #include <asm/board/platform.h> |
| |
| #ifdef CONFIG_IPV6 |
| /* optimisation of the ipv6_skip_exthdr() kernel function */ |
| int __sram_text |
| iputil_v6_skip_exthdr(const struct ipv6hdr *ipv6h, int start, uint8_t *nexthdrp, |
| int total_len, __be32 *ip_id, uint8_t *more_frags) |
| { |
| uint8_t nexthdr = ipv6h->nexthdr; |
| struct frag_hdr *frag_hdrp; |
| struct ipv6_opt_hdr *hp; |
| int hdrlen; |
| |
| while ((start < total_len) && ipv6_ext_hdr(nexthdr) && (nexthdr != NEXTHDR_NONE)) { |
| hp = (struct ipv6_opt_hdr *)((char *)ipv6h + start); |
| |
| if (unlikely(nexthdr == NEXTHDR_FRAGMENT)) { |
| frag_hdrp = (struct frag_hdr *)hp; |
| |
| if (ip_id != NULL) { |
| *ip_id = ntohl(get_unaligned(&frag_hdrp->identification)); |
| } |
| |
| KASSERT((((int)frag_hdrp) & 0x1) == 0, |
| ("iputil: frag hdr is not on 2-octet boundary")); |
| |
| if (more_frags != NULL) { |
| *more_frags = IPUTIL_V6_FRAG_MF(frag_hdrp); |
| } |
| if (IPUTIL_V6_FRAG_OFFSET(frag_hdrp)) { |
| /* not start of packet - does not contain protocol hdr */ |
| break; |
| } |
| hdrlen = 8; |
| } else if (unlikely(nexthdr == NEXTHDR_AUTH)) { |
| hdrlen = (hp->hdrlen + 2) << 2; |
| } else { |
| hdrlen = ipv6_optlen(hp); |
| } |
| |
| if ((start + hdrlen) > total_len) { |
| nexthdr = NEXTHDR_NONE; |
| break; |
| } |
| nexthdr = hp->nexthdr; |
| start += hdrlen; |
| } |
| |
| *nexthdrp = nexthdr; |
| |
| return start; |
| } |
| EXPORT_SYMBOL(iputil_v6_skip_exthdr); |
| |
| int iputil_v6_ntop(char *buf, const struct in6_addr *addr) |
| { |
| char *p = buf; |
| int i = 0; |
| int zstart = 0; |
| int bestzstart = 0; |
| int bestzend = 0; |
| int bestzlen = 0; |
| int inz = 0; |
| int donez = 0; |
| const int addr16len = (int)(sizeof(struct in6_addr) / sizeof(uint16_t)); |
| |
| /* parse address, looking for longest substring of 0s */ |
| for (i = 0; i < addr16len; i++) { |
| if (addr->s6_addr16[i] == 0) { |
| if (!inz) { |
| zstart = i; |
| } else { |
| int zlen; |
| int zend; |
| zend = i; |
| zlen = zend - zstart + 1; |
| if (zlen > bestzlen) { |
| bestzlen = zlen; |
| bestzstart = zstart; |
| bestzend = i; |
| } |
| } |
| inz = 1; |
| } else { |
| inz = 0; |
| } |
| } |
| |
| /* when only the last 32 bits contain an address, format as an ::ipv4 */ |
| if (bestzstart == 0 && bestzlen == 6) { |
| p += sprintf(p, "::%d.%d.%d.%d", |
| addr->s6_addr[12], addr->s6_addr[13], |
| addr->s6_addr[14], addr->s6_addr[15]); |
| return p - buf; |
| } |
| |
| /* otherwise format as normal ipv6 */ |
| for (i = 0; i < addr16len; i++) { |
| uint16_t s = ntohs(addr->s6_addr16[i]); |
| if ((bestzlen == 0) || (i < bestzstart) || (i > bestzend) || s) { |
| const char *colon; |
| if ((i == (addr16len - 1)) || (i == (bestzstart - 1))) { |
| colon = ""; |
| } else { |
| colon = ":"; |
| } |
| p += sprintf(p, "%x%s", s, colon); |
| } else if (bestzlen && (i == bestzstart)) { |
| inz = 1; |
| } else if (bestzlen && (i == bestzend)) { |
| p += sprintf(p, "::"); |
| inz = 0; |
| donez = 1; |
| } else if (inz) { |
| } else { |
| WARN_ONCE(1, "%s: implementation error", __FUNCTION__); |
| } |
| } |
| |
| return p - buf; |
| } |
| EXPORT_SYMBOL(iputil_v6_ntop); |
| |
| int iputil_v6_ntop_port(char *buf, const struct in6_addr *addr, __be16 port) |
| { |
| char *p = buf; |
| p += sprintf(p, "["); |
| p += iputil_v6_ntop(p, addr); |
| p += sprintf(p, "]:%u", ntohs(port)); |
| return p - buf; |
| } |
| EXPORT_SYMBOL(iputil_v6_ntop_port); |
| |
| int iputil_eth_is_v6_mld(void *iphdr, uint32_t data_len) |
| { |
| struct ipv6hdr *ip6hdr_p = iphdr; |
| uint8_t nexthdr; |
| int nhdr_off; |
| struct icmp6hdr *icmp6hdr; |
| int is_ipv6_mld = 0; |
| |
| nhdr_off = iputil_v6_skip_exthdr(ip6hdr_p, sizeof(struct ipv6hdr), |
| &nexthdr, data_len, NULL, NULL); |
| |
| if (unlikely(nexthdr == IPPROTO_ICMPV6)) { |
| icmp6hdr = (struct icmp6hdr*)((__u8 *)ip6hdr_p + nhdr_off); |
| |
| if (icmp6hdr->icmp6_type == ICMPV6_MGM_QUERY || |
| icmp6hdr->icmp6_type == ICMPV6_MGM_REPORT || |
| icmp6hdr->icmp6_type == ICMPV6_MGM_REDUCTION || |
| icmp6hdr->icmp6_type == ICMPV6_MLD2_REPORT) { |
| is_ipv6_mld = 1; |
| } |
| } |
| |
| return is_ipv6_mld; |
| } |
| EXPORT_SYMBOL(iputil_eth_is_v6_mld); |
| #endif /* CONFIG_IPV6 */ |
| |
| /* |
| * Return IP packet protocol information |
| */ |
| uint8_t __sram_text |
| iputil_proto_info(void *iph, void *data, void **proto_data, uint32_t *ip_id, uint8_t *more_frags) |
| { |
| const struct iphdr *ipv4h = iph; |
| uint16_t frag_off; |
| #ifdef CONFIG_IPV6 |
| u_int8_t nexthdr; |
| int start; |
| uint32_t data_len; |
| #endif |
| struct sk_buff *skb = data; |
| |
| if (ipv4h->version == 4) { |
| if (skb->len < (ipv4h->ihl << 2)) |
| return 0; |
| frag_off = ntohs(get_unaligned((u16 *)&ipv4h->frag_off)); |
| if (ip_id && (frag_off & (IP_OFFSET | IP_MF)) != 0) { |
| *ip_id = (uint32_t)ntohs(ipv4h->id); |
| } |
| if (more_frags) { |
| *more_frags = !!(frag_off & IP_MF); |
| } |
| *proto_data = (char *)iph + (ipv4h->ihl << 2); |
| |
| if ((frag_off & IP_OFFSET) != 0) { |
| /* not start of packet - does not contain protocol hdr */ |
| return NEXTHDR_FRAGMENT; |
| } |
| |
| return ipv4h->protocol; |
| } |
| |
| #ifdef CONFIG_IPV6 |
| if (ipv4h->version == 6) { |
| data_len = skb->len - ((uint8_t*)iph - (skb->data)); |
| start = iputil_v6_skip_exthdr((struct ipv6hdr *)iph, sizeof(struct ipv6hdr), |
| &nexthdr, data_len, ip_id, more_frags); |
| *proto_data = (char *)iph + start; |
| |
| return nexthdr; |
| } |
| #endif |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(iputil_proto_info); |
| |
| int iputil_v4_ntop_port(char *buf, __be32 addr, __be16 port) |
| { |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| return sprintf(buf, "%pI4:%u", &addr, ntohs(port)); |
| #else |
| return sprintf(buf, NIPQUAD_FMT ":%u", NIPQUAD(addr), ntohs(port)); |
| #endif |
| } |
| EXPORT_SYMBOL(iputil_v4_ntop_port); |
| |
| int iputil_v4_pton(const char *ip_str, __be32 *ipaddr) |
| { |
| int i; |
| uint32_t tmp_array[IPUTIL_V4_ADDR_LEN]; |
| uint8_t *ipaddr_p = (uint8_t *)ipaddr; |
| |
| if (ip_str == NULL) |
| return -1; |
| |
| if (sscanf(ip_str, "%d.%d.%d.%d", |
| &tmp_array[0], |
| &tmp_array[1], |
| &tmp_array[2], |
| &tmp_array[3]) != 4) { |
| return -1; |
| } |
| |
| for (i = 0; i < IPUTIL_V4_ADDR_LEN; i++) { |
| if (tmp_array[i] > 0xff) { |
| return -1; |
| } |
| } |
| |
| ipaddr_p[0] = tmp_array[0]; |
| ipaddr_p[1] = tmp_array[1]; |
| ipaddr_p[2] = tmp_array[2]; |
| ipaddr_p[3] = tmp_array[3]; |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(iputil_v4_pton); |
| |
| #ifdef CONFIG_IPV6 |
| int iputil_ipv6_is_neigh_msg(struct ipv6hdr *ipv6, struct icmp6hdr *icmpv6) |
| { |
| if (ipv6->hop_limit != 255) { |
| return 0; |
| } |
| |
| if (icmpv6->icmp6_code != 0) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| EXPORT_SYMBOL(iputil_ipv6_is_neigh_msg); |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| int iputil_ipv6_is_neigh_sol_msg(uint8_t dup_addr_detect, const struct in6_addr *target, const struct in6_addr *daddr) |
| #else |
| int iputil_ipv6_is_neigh_sol_msg(uint8_t dup_addr_detect, struct nd_msg *msg, struct ipv6hdr *ipv6) |
| #endif |
| { |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4,7,0) |
| const struct in6_addr *daddr = &ipv6->daddr; |
| #endif |
| |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,7,0) |
| if (ipv6_addr_is_multicast(target)) { |
| #else |
| if (ipv6_addr_is_multicast(&msg->target)) { |
| #endif |
| |
| return 0; |
| } |
| |
| if (dup_addr_detect && !(daddr->s6_addr32[0] == htonl(0xff020000) && |
| daddr->s6_addr32[1] == htonl(0x00000000) && |
| daddr->s6_addr32[2] == htonl(0x00000001) && |
| daddr->s6_addr [12] == 0xff )) { |
| return 0; |
| } |
| |
| return 1; |
| } |
| EXPORT_SYMBOL(iputil_ipv6_is_neigh_sol_msg); |
| #endif |