blob: 3cd5f7d39896ccbda2716ccb061f46774d560325 [file] [log] [blame]
/**
* 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