/* * Copyright (C) 2019 Samsung Electronics. * * This program has been developed as a CLAT module running at kernel, * and it is based on android-clat written by * * Copyright (c) 2010-2012, Daniel Drown * * This file is dual licensed. It may be redistributed and/or modified * under the terms of the Apache 2.0 License OR version 2 of the GNU * General Public License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "modem_prj.h" #include "modem_utils.h" #include "modem_klat.h" //#define SUPPORT_KLAT_ICMP //#define SUPPORT_KLAT_TX #define KOBJ_CLAT "clat" #define IP6F_OFF_MASK 0xf8ff /* mask out offset from _offlg */ #define IP6F_RESERVED_MASK 0x0600 /* reserved bits in ip6f_offlg */ #define IP6F_MORE_FRAG 0x0100 /* more-fragments flag */ #define MAX_TCP_HDR (15 * 4) #define IN6_ARE_ADDR_EQUAL(a,b) \ ((((const uint32_t *) (a))[0] == ((const uint32_t *) (b))[0]) \ && (((const uint32_t *) (a))[1] == ((const uint32_t *) (b))[1]) \ && (((const uint32_t *) (a))[2] == ((const uint32_t *) (b))[2]) \ && (((const uint32_t *) (a))[3] == ((const uint32_t *) (b))[3])) struct klat klat_obj; EXPORT_SYMBOL(klat_obj); static uint16_t ip_checksum_fold(uint32_t temp_sum) { while(temp_sum > 0xFFFF) temp_sum = (temp_sum >> 16) + (temp_sum & 0xFFFF); return temp_sum; } static uint16_t ip_checksum_finish(uint32_t temp_sum) { return ~ip_checksum_fold(temp_sum); } static uint32_t ip_checksum_add(uint32_t curr, const void *data, int len) { uint32_t checksum = curr; int left = len; const uint16_t *data_16 = data; while (left > 1) { checksum += *data_16; data_16++; left -= 2; } if (left) checksum += *(uint8_t *)data_16; return checksum; } static uint16_t ip_checksum(const void *data, int len) { uint32_t temp_sum; temp_sum = ip_checksum_add(0, data, len); return ip_checksum_finish(temp_sum); } static uint32_t ipv6_pseudo_header_checksum(struct ipv6hdr *ip6, uint16_t len, uint8_t protocol) { uint32_t checksum_len, checksum_next; uint32_t curr = 0; checksum_len = htonl((uint32_t) len); checksum_next = htonl(protocol); curr = ip_checksum_add(curr, &(ip6->saddr), sizeof(struct in6_addr)); curr = ip_checksum_add(curr, &(ip6->daddr), sizeof(struct in6_addr)); curr = ip_checksum_add(curr, &checksum_len, sizeof(checksum_len)); curr = ip_checksum_add(curr, &checksum_next, sizeof(checksum_next)); return curr; } static uint32_t ipv4_pseudo_header_checksum(struct iphdr *ip, uint16_t len) { uint16_t temp_protocol, temp_length; uint32_t curr = 0; temp_protocol = htons(ip->protocol); temp_length = htons(len); curr = ip_checksum_add(curr, &(ip->saddr), sizeof(uint32_t)); curr = ip_checksum_add(curr, &(ip->daddr), sizeof(uint32_t)); curr = ip_checksum_add(curr, &temp_protocol, sizeof(uint16_t)); curr = ip_checksum_add(curr, &temp_length, sizeof(uint16_t)); return curr; } static uint16_t ip_checksum_adjust(uint16_t checksum, uint32_t old_hdr_sum, uint32_t new_hdr_sum) { uint16_t folded_sum, folded_old; /* Algorithm suggested in RFC 1624. * http://tools.ietf.org/html/rfc1624#section-3 */ checksum = ~checksum; folded_sum = ip_checksum_fold(checksum + new_hdr_sum); folded_old = ip_checksum_fold(old_hdr_sum); if (folded_sum > folded_old) return ~(folded_sum - folded_old); return ~(folded_sum - folded_old - 1); } static uint16_t packet_checksum(uint32_t checksum, uint8_t *packet, size_t len) { checksum = ip_checksum_add(checksum, packet, len); return ip_checksum_finish(checksum); } static int udp_translate(struct udphdr *udp, uint32_t old_sum, uint32_t new_sum, uint8_t *payload, size_t payload_size) { if (udp->check) { udp->check = ip_checksum_adjust(udp->check, old_sum, new_sum); } else { udp->check = 0; udp->check = packet_checksum(new_sum, payload, payload_size); } /* RFC 768: "If the computed checksum is zero, * it is transmitted as all ones (the equivalent in one's complement * arithmetic)." */ if (!udp->check) udp->check = 0xFFFF; return 1; } static int tcp_translate(struct tcphdr *tcp, size_t header_size, uint32_t old_sum, uint32_t new_sum, const uint8_t *payload, size_t payload_size) { if (header_size > MAX_TCP_HDR) { mif_err("header too long %d > %d, truncating", (int)header_size, MAX_TCP_HDR); header_size = MAX_TCP_HDR; } tcp->check = ip_checksum_adjust(tcp->check, old_sum, new_sum); return 1; } #ifdef SUPPORT_KLAT_ICMP static int is_icmp_error(uint8_t type) { return type == 3 || type == 11 || type == 12; } static int is_icmp6_error(uint8_t type) { return type < 128; } static uint8_t icmp6_to_icmp_type(uint8_t type, uint8_t code) { switch (type) { case ICMPV6_ECHO_REQUEST: return ICMP_ECHO; case ICMPV6_ECHO_REPLY: return ICMP_ECHOREPLY; case ICMPV6_DEST_UNREACH: return ICMP_DEST_UNREACH; case ICMPV6_TIME_EXCEED: return ICMP_TIME_EXCEEDED; } mif_err("unhandled ICMP type/code %d/%d\n", type, code); return ICMP_PARAMETERPROB; } static uint8_t icmp6_to_icmp_code(uint8_t type, uint8_t code) { switch (type) { case ICMPV6_ECHO_REQUEST: case ICMPV6_ECHO_REPLY: case ICMPV6_TIME_EXCEED: return code; case ICMPV6_DEST_UNREACH: switch (code) { case ICMPV6_NOROUTE: return ICMP_HOST_UNREACH; case ICMPV6_ADM_PROHIBITED: return ICMP_HOST_ANO; case ICMPV6_NOT_NEIGHBOUR: return ICMP_HOST_UNREACH; case ICMPV6_ADDR_UNREACH: return ICMP_HOST_UNREACH; case ICMPV6_PORT_UNREACH: return ICMP_PORT_UNREACH; } } mif_err("unhandled ICMP type/code %d/%d\n", type, code); return 0; } static uint8_t icmp_to_icmp6_type(uint8_t type, uint8_t code) { switch (type) { case ICMP_ECHO: return ICMPV6_ECHO_REQUEST; case ICMP_ECHOREPLY: return ICMPV6_ECHO_REPLY; case ICMP_TIME_EXCEEDED: return ICMPV6_TIME_EXCEED; case ICMP_DEST_UNREACH: if (code != ICMP_PORT_UNREACH && code != ICMP_FRAG_NEEDED) return ICMPV6_DEST_UNREACH; } mif_err("unhandled ICMP type %d\n", type); return ICMPV6_PARAMPROB; } static uint8_t icmp_to_icmp6_code(uint8_t type, uint8_t code) { switch (type) { case ICMP_ECHO: case ICMP_ECHOREPLY: return 0; case ICMP_TIME_EXCEEDED: return code; case ICMP_DEST_UNREACH: switch (code) { case ICMP_NET_UNREACH: case ICMP_HOST_UNREACH: return ICMPV6_NOROUTE; case ICMP_PROT_UNREACH: return ICMPV6_PORT_UNREACH; case ICMP_NET_ANO: case ICMP_HOST_ANO: case ICMP_PKT_FILTERED: case ICMP_PREC_CUTOFF: return ICMPV6_ADM_PROHIBITED; } } mif_err("unhandled ICMP type/code %d/%d\n", type, code); return 0; } static int icmp_to_icmp6(struct sk_buff *skb, struct icmphdr *icmp, uint32_t checksum, uint8_t *payload, size_t payload_size) { /* todo */ return 0; } static int icmp6_to_icmp(struct sk_buff *skb, struct icmp6hdr *icmp6, uint8_t *payload, size_t payload_size, bool recursive) { struct icmphdr *icmp_targ = ; uint8_t icmp_type; int clat_packet_len; memset(icmp_targ, 0, sizeof(struct icmphdr)); icmp_type = icmp6_to_icmp_type(icmp6->icmp6_type, icmp6->icmp6_code); icmp_targ->type = icmp_type; icmp_targ->code = icmp6_to_icmp_code(icmp6->icmp6_type, icmp6->icmp6_code); if (!recursive && is_icmp6_error(icmp6->icmp6_type) && icmp_type != ICMP_PARAMETERPROB) { clat_packet_len = ipv6_packet(skb, payload, payload_size); } else if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ECHOREPLY) { // Ping packet. icmp_targ->un.echo.id = icmp6->icmp6_id; icmp_targ->un.echo.sequence = icmp6->icmp6_seq; clat_packet_len = 1; } else { /* Unknown type/code. */ return 0; } icmp_targ->checksum = 0; icmp_targ->checksum = packet_checksum(0, payload, payload_size); return clat_packet_len; } int icmp_packet(struct sk_buff *skb, struct icmphdr *icmp, uint32_t checksum, size_t len) { uint8_t *payload; size_t payload_size; if (len < sizeof(struct icmphdr)) { mif_err("icmp_packet/(too small)\n"); return 0; } payload = (uint8_t *) (icmp + 1); payload_size = len - sizeof(struct icmphdr); return icmp_to_icmp6(skb, icmp, checksum, payload, payload_size); } static int icmp6_packet(struct sk_buff *skb, struct icmp6hdr *icmp6, size_t len, bool recursive) { uint8_t *payload; size_t payload_size; if (len < sizeof(struct icmp6hdr)) { mif_err("too small\n"); return 0; } payload = (uint8_t *) (icmp6 + 1); payload_size = len - sizeof(struct icmp6hdr); return icmp6_to_icmp(skb, icmp6, payload, payload_size, recursive); } #endif static int udp_packet(struct udphdr *udp, uint32_t old_sum, uint32_t new_sum, size_t len) { uint8_t *payload; size_t payload_size; if (len < sizeof(struct udphdr)) { mif_err("too small\n"); return 0; } payload = (uint8_t *) (udp + 1); payload_size = len - sizeof(struct udphdr); return udp_translate(udp, old_sum, new_sum, payload, payload_size); } static int tcp_packet(struct tcphdr *tcp, uint32_t old_sum, uint32_t new_sum, size_t len) { uint8_t *payload; size_t payload_size, header_size; if (len < sizeof(struct tcphdr)) { mif_err("too small\n"); return 0; } if (tcp->doff < 5) { mif_err("tcp header length is less than 5: %x\n", tcp->doff); return 0; } if ((size_t)tcp->doff * 4 > len) { mif_err("tcp header length set too large: %x\n", tcp->doff); return 0; } header_size = tcp->doff * 4; payload = ((uint8_t *)tcp) + header_size; payload_size = len - header_size; return tcp_translate(tcp, header_size, old_sum, new_sum, payload, payload_size); } static uint8_t parse_frag_header(struct frag_hdr *frag_hdr, struct iphdr *ip_targ) { uint16_t frag_off = (ntohs(frag_hdr->frag_off & IP6F_OFF_MASK) >> 3); if (frag_hdr->frag_off & IP6F_MORE_FRAG) { frag_off |= IP_MF; } ip_targ->frag_off = htons(frag_off); ip_targ->id = htons(ntohl(frag_hdr->identification) & 0xFFFF); ip_targ->protocol = frag_hdr->nexthdr; return frag_hdr->nexthdr; } static uint8_t icmp_guess_ttl(uint8_t ttl) { if (ttl > 128) { return 255 - ttl; } else if (ttl > 64) { return 128 - ttl; } else if (ttl > 32) { return 64 - ttl; } else { return 32 - ttl; } } static bool is_in_plat_subnet(struct in6_addr *addr6) { /* Assumes a /96 plat subnet. */ return (addr6 != NULL) && (memcmp(addr6->s6_addr, &klat_obj.plat_subnet, 12) == 0); } static struct in6_addr ipv4_daddr_to_ipv6_daddr(uint32_t addr4) { struct in6_addr addr6; /* Assumes a /96 plat subnet. */ addr6 = klat_obj.plat_subnet; addr6.s6_addr32[3] = addr4; return addr6; } static uint32_t ipv6_saddr_to_ipv4_saddr(struct in6_addr *addr6) { if (is_in_plat_subnet(addr6)) { /* Assumes a /96 plat subnet. */ return addr6->s6_addr32[3]; } return INADDR_NONE; } static void fill_ip_header(struct iphdr *ip, uint16_t payload_len, uint8_t protocol, struct ipv6hdr *old_header, int ndev_index) { int ttl_guess; memset(ip, 0, sizeof(struct iphdr)); ip->ihl = 5; ip->version = 4; ip->tos = 0; ip->tot_len = htons(sizeof(struct iphdr) + payload_len); ip->id = 0; ip->frag_off = htons(IP_DF); ip->ttl = old_header->hop_limit; ip->protocol = protocol; ip->check = 0; ip->saddr = ipv6_saddr_to_ipv4_saddr(&old_header->saddr); ip->daddr = klat_obj.xlat_v4_addrs[ndev_index].s_addr; if ((uint32_t)ip->saddr == INADDR_NONE) { ttl_guess = icmp_guess_ttl(old_header->hop_limit); ip->saddr = htonl((0xff << 24) + ttl_guess); } } static void fill_ip6_header(struct ipv6hdr *ip6, uint16_t payload_len, uint8_t protocol, const struct iphdr *old_header, int ndev_index) { memset(ip6, 0, sizeof(struct ipv6hdr)); ip6->version = 6; ip6->payload_len = htons(payload_len); ip6->nexthdr = protocol; ip6->hop_limit = old_header->ttl; ip6->saddr = klat_obj.xlat_addrs[ndev_index]; ip6->daddr = ipv4_daddr_to_ipv6_daddr(old_header->daddr); } static size_t maybe_fill_frag_header(struct frag_hdr *frag_hdr, struct ipv6hdr *ip6_targ, const struct iphdr *old_header) { uint16_t frag_flags = ntohs(old_header->frag_off); uint16_t frag_off = frag_flags & IP_OFFSET; if (frag_off == 0 && (frag_flags & IP_MF) == 0) { // Not a fragment. return 0; } frag_hdr->nexthdr = ip6_targ->nexthdr; frag_hdr->reserved = 0; frag_hdr->frag_off = htons(frag_off << 3); if (frag_flags & IP_MF) frag_hdr->frag_off |= IP6F_MORE_FRAG; frag_hdr->identification = htonl(ntohs(old_header->id)); ip6_targ->nexthdr = IPPROTO_FRAGMENT; return sizeof(*frag_hdr); } static int ipv4_packet(struct sk_buff *skb, int ndev_index) { struct iphdr *header = (struct iphdr *)skb->data; struct ipv6hdr ip6_targ; struct frag_hdr frag_hdr; size_t frag_hdr_len; uint8_t nxthdr; uint8_t *next_header; uint8_t *p_curr; size_t len_left; uint32_t old_sum, new_sum; int iov_len = 0; int len = skb->len; if (len < sizeof(struct iphdr)) { mif_err("too short for an ip header\n"); return 0; } if (header->ihl < 5) { mif_err("ip header length is less than 5: %x\n", header->ihl); return 0; } if (header->ihl * 4 > len) { mif_err("ip header length set too large: %x\n", header->ihl); return 0; } if (header->version != 4) { mif_err("ip header version not 4: %x\n", header->version); return 0; } next_header = skb->data + header->ihl*4; len_left = len - header->ihl * 4; nxthdr = header->protocol; if (nxthdr == IPPROTO_ICMP) nxthdr = IPPROTO_ICMPV6; fill_ip6_header(&ip6_targ, 0, nxthdr, header, ndev_index); old_sum = ipv4_pseudo_header_checksum(header, len_left); new_sum = ipv6_pseudo_header_checksum(&ip6_targ, len_left, nxthdr); frag_hdr_len = maybe_fill_frag_header(&frag_hdr, &ip6_targ, header); if (frag_hdr_len && frag_hdr.frag_off & IP6F_OFF_MASK) { /* TODO */ } else if (nxthdr == IPPROTO_ICMPV6) { #ifdef SUPPORT_KLAT_ICMP iov_len = icmp_packet(skb, (struct icmphdr *)next_header, new_sum, len_left); #endif } else if (nxthdr == IPPROTO_TCP) { iov_len = tcp_packet((struct tcphdr *)next_header, old_sum, new_sum, len_left); } else if (nxthdr == IPPROTO_UDP) { iov_len = udp_packet((struct udphdr *)next_header, old_sum, new_sum, len_left); } else { #ifdef KLAT_DEBUG mif_err("unknown protocol: %x\n", header->protocol); mif_err("protocol: %*ph\n", skb->data, min(skb->len, 48)); #endif return 0; } if (iov_len) { int headroom = frag_hdr_len + sizeof(struct ipv6hdr) - sizeof(struct iphdr); if (skb_headroom(skb) < headroom) { #ifdef KLAT_DEBUG pr_info("%s: head:%d, needed:%d\n", skb_headroom(skb), headroom); #endif if (pskb_expand_head(skb, headroom, 0, GFP_ATOMIC)) { pr_info("%s: ERR! re alloc failed\n", __func__); return 0; } } skb_push(skb, headroom); /* Set the length.*/ ip6_targ.payload_len = htons(len_left); /* copy ip_targ to skb */ p_curr = skb->data; memcpy(p_curr, &ip6_targ, sizeof(struct ipv6hdr)); if (frag_hdr_len) { p_curr += sizeof(struct ipv6hdr); memcpy(p_curr, &frag_hdr, frag_hdr_len); } } return iov_len; } static int ipv6_packet(struct sk_buff *skb, int ndev_index) { struct ipv6hdr *ip6 = (struct ipv6hdr *)skb->data; struct iphdr ip_targ; struct frag_hdr *frag_hdr = NULL; uint8_t protocol; unsigned char *next_header; size_t len_left; uint32_t old_sum, new_sum; struct in6_addr *xlat_addr = &klat_obj.xlat_addrs[ndev_index]; int iov_len = 0; if (skb->len < sizeof(struct ipv6hdr)) { mif_info("too short for an ip6 header: %d\n", skb->len); return 0; } if (ipv6_addr_is_multicast(&ip6->daddr)) { mif_err("multicast %pI6->%pI6\n", &ip6->saddr, &ip6->daddr); return 0; } if (!(is_in_plat_subnet(&ip6->saddr) && IN6_ARE_ADDR_EQUAL(&ip6->daddr, xlat_addr)) && !(is_in_plat_subnet(&ip6->daddr) && IN6_ARE_ADDR_EQUAL(&ip6->saddr, xlat_addr)) && ip6->nexthdr != NEXTHDR_ICMP) { mif_err("wrong saddr: %pI6->%pI6\n", &ip6->saddr, &ip6->daddr); return 0; } next_header = skb->data + sizeof(struct ipv6hdr); len_left = skb->len - sizeof(struct ipv6hdr); protocol = ip6->nexthdr; fill_ip_header(&ip_targ, 0, protocol, ip6, ndev_index); if (protocol == IPPROTO_FRAGMENT) { frag_hdr = (struct frag_hdr *)next_header; if (len_left < sizeof(*frag_hdr)) { mif_err("short for fragment header: %d\n", skb->len); return 0; } next_header += sizeof(*frag_hdr); len_left -= sizeof(*frag_hdr); protocol = parse_frag_header(frag_hdr, &ip_targ); } if (protocol == IPPROTO_ICMPV6) { protocol = IPPROTO_ICMP; ip_targ.protocol = IPPROTO_ICMP; } old_sum = ipv6_pseudo_header_checksum(ip6, len_left, protocol); new_sum = ipv4_pseudo_header_checksum(&ip_targ, len_left); /* Does not support IPv6 extension headers except Fragment. */ if (frag_hdr && (frag_hdr->frag_off & IP6F_OFF_MASK)) { /* TODO */ } else if (protocol == IPPROTO_ICMP) { #ifdef SUPPORT_KLAT_ICMP iov_len = icmp6_packet(skb, (struct icmp6hdr *)next_header, len_left, recursive); #endif } else if (protocol == IPPROTO_TCP) { iov_len = tcp_packet((struct tcphdr *)next_header, old_sum, new_sum, len_left); } else if (protocol == IPPROTO_UDP) { iov_len = udp_packet((struct udphdr *)next_header, old_sum, new_sum, len_left); } else { #ifdef KLAT_DEBUG mif_err("unknown next header type: %x\n", ip6->nexthdr); mif_err("nxthdr: %*ph\n", skb->data, min(skb->len, 48)); #endif return 0; } if (iov_len) { /* Set the length and calculate the checksum. */ ip_targ.tot_len = htons(len_left + sizeof(struct iphdr)); ip_targ.check = ip_checksum(&ip_targ, sizeof(struct iphdr)); /* copy ip_targ to skb */ memcpy(next_header - sizeof(struct iphdr), &ip_targ, sizeof(struct iphdr)); skb_pull(skb, next_header - skb->data - sizeof(struct iphdr)); } return iov_len; } int klat_rx(struct sk_buff *skb, int ndev_index) { if (klat_obj.use[ndev_index] && skb->protocol == htons(ETH_P_IPV6)) { struct ipv6hdr *ip6hdr = (struct ipv6hdr *)skb->data; if (ipv6_addr_equal(&ip6hdr->daddr, &klat_obj.xlat_addrs[ndev_index])) { if (ipv6_packet(skb, ndev_index) > 0) { skb->protocol = htons(ETH_P_IP); skb->dev = klat_obj.tun_device[ndev_index]; return 0; } else { pr_err("%s: src:%pI6c | dst:%pI6c | %*ph\n", __func__, &ip6hdr->saddr, &ip6hdr->daddr, 48, ip6hdr); } } } return -1; } #ifdef SUPPORT_KLAT_TX static int klat_tun_dev_index(struct net_device *dev) { int index; for (index = 0; index < KLAT_MAX_NDEV; index++) if (klat_obj.tun_device[index] == dev) return index; return -1; } static struct net_device_ops klat_netdev_ops; static const struct net_device_ops *tun_netdev_ops; static netdev_tx_t klat_net_xmit(struct sk_buff *skb, struct net_device *dev) { int ndev_index = klat_tun_dev_index(dev); #if 0 // check if what is better int queue_index = 0; #endif if (ndev_index < 0 || klat_tx(skb, ndev_index)) return tun_netdev_ops->ndo_start_xmit(skb, dev); #if 0 // check if what is better if (dev->real_num_tx_queues != 1) { const struct net_device_ops *ops = dev->netdev_ops; if (ops->ndo_select_queue) queue_index = ops->ndo_select_queue(dev, skb, NULL, NULL); else queue_index = __netdev_pick_tx(dev, skb); queue_index = netdev_cap_txqueue(dev, queue_index); } skb_set_queue_mapping(skb, queue_index); return dev->netdev_ops->ndo_start_xmit(dev, skb); #endif return dev_queue_xmit(skb); } static void hook_tun_ops(struct net_device *ndev) { if (klat_netdev_ops.ndo_start_xmit != klat_net_xmit) { memcpy(&klat_netdev_ops, ndev->netdev_ops, sizeof(struct net_device_ops)); klat_netdev_ops.ndo_start_xmit = klat_net_xmit; tun_netdev_ops = ndev->netdev_ops; } ndev->netdev_ops = &klat_netdev_ops; } static void revert_tun_ops(struct net_device *ndev) { ndev->netdev_ops = tun_netdev_ops; } #endif int klat_tx(struct sk_buff *skb, int ndev_index) { if (klat_obj.use[ndev_index] && skb->protocol == htons(ETH_P_IP)) { struct iphdr *iphdr = (struct iphdr *)skb->data; if (iphdr->saddr == klat_obj.xlat_v4_addrs[ndev_index].s_addr) { if (ipv4_packet(skb, ndev_index) > 0) { skb->ip_summed = CHECKSUM_UNNECESSARY; skb->protocol = htons(ETH_P_IPV6); skb->dev = klat_obj.rmnet_device[ndev_index]; return 0; } } } return -1; } static struct net_device *klat_dev_get_by_name(const char *devname) { struct net_device *dev = NULL; rcu_read_lock(); dev = dev_get_by_name_rcu(&init_net, devname); rcu_read_unlock(); return dev; } static int get_rmnet_index(const char *buf) { if (buf && *buf != '\0' && strncmp(buf, "rmnet", 5) == 0 && *(buf + 5) != '\0') { int rmnet_idx = *(buf + 5) - '0'; if (rmnet_idx < KLAT_MAX_NDEV) return rmnet_idx; } return -1; } static int klat_netdev_event(struct notifier_block *this, unsigned long event, void *ptr) { struct net_device *net = netdev_notifier_info_to_dev(ptr); char *buf = strstr(net->name, "v4-rmnet"); int ndev_idx; if (!buf) return NOTIFY_DONE; ndev_idx = get_rmnet_index(buf + 3); if (ndev_idx < 0) return NOTIFY_DONE; switch (event) { case NETDEV_DOWN: case NETDEV_UNREGISTER: if (klat_obj.use[ndev_idx]) { #ifdef SUPPORT_KLAT_TX revert_tun_ops(net); #endif klat_obj.use[ndev_idx] = 0; mif_info("klat disabled(%s)\n", net->name); } break; } return NOTIFY_DONE; } static struct notifier_block klat_netdev_notifier = { .notifier_call = klat_netdev_event, }; ssize_t klat_plat_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct in6_addr val; char *ptr = NULL; int rmnet_idx = 0; mif_err("plat prefix: %s\n", buf); ptr = strstr(buf, "@"); if (!ptr) return -EINVAL; *ptr++ = '\0'; if (in6_pton(buf, strlen(buf), val.s6_addr, '\0', NULL) == 0) return -EINVAL; rmnet_idx = get_rmnet_index(ptr); if (rmnet_idx >= 0) { klat_obj.plat_subnet = val; klat_obj.use[rmnet_idx] = 1; mif_err("plat prefix: %pI6, klat(%d) enabled\n", &klat_obj.plat_subnet, rmnet_idx); } else { mif_err("plat prefix: %pI6, failed to enable klat\n", &klat_obj.plat_subnet); } return count; } ssize_t klat_addrs_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct in6_addr val; char *ptr = NULL; int dev_index = -1; char v4_rmnet[12]; mif_err("-- v6 addr: %s\n", buf); ptr = strstr(buf, "@"); if (!ptr) return -EINVAL; *ptr++ = '\0'; if (in6_pton(buf, strlen(buf), val.s6_addr, '\0', NULL) == 0) return -EINVAL; if (sscanf(ptr, "rmnet%d", &dev_index) != 1 || dev_index < 0 || dev_index >= KLAT_MAX_NDEV) { mif_err("unhandled clat addr for device %s\n", ptr); return -EINVAL; } sprintf(v4_rmnet, "v4-rmnet%d", dev_index); klat_obj.xlat_addrs[dev_index] = val; klat_obj.tun_device[dev_index] = klat_dev_get_by_name(v4_rmnet); klat_obj.rmnet_device[dev_index] = klat_dev_get_by_name(ptr); #ifdef CONFIG_MODEM_IF_NET_GRO klat_obj.tun_device[dev_index]->features |= NETIF_F_GRO; #endif #ifdef SUPPORT_KLAT_TX hook_tun_ops(klat_obj.tun_device[dev_index]); #endif mif_err("rmnet%d: %pI6\n", dev_index, &val); return count; } ssize_t klat_v4_addrs_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { struct in_addr val; char *ptr = NULL; int dev_index = -1; mif_err("v4 addr: %s\n", buf); ptr = strstr(buf, "@"); if (!ptr) return -EINVAL; *ptr++ = '\0'; if (in4_pton(buf, strlen(buf), (u8 *)&val.s_addr, '\0', NULL) == 0) return -EINVAL; ptr = strstr(ptr, "rmnet"); if (!ptr) { mif_err("can't find the right string\n"); return -EINVAL; } if (sscanf(ptr, "rmnet%d", &dev_index) != 1 || dev_index < 0 || dev_index >= KLAT_MAX_NDEV) { mif_err("unhandled(%d) clat v4 addr for device %s\n", dev_index, ptr); return -EINVAL; } klat_obj.xlat_v4_addrs[dev_index].s_addr = val.s_addr; mif_err("v4_rmnet%d: %pI4\n", dev_index, &val.s_addr); return count; } #ifndef CONFIG_CP_DIT static ssize_t klat_plat_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "plat prefix: %pI6\n", &klat_obj.plat_subnet); } static ssize_t klat_addrs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%pI6\n%pI6\n", &klat_obj.xlat_addrs[0], &klat_obj.xlat_addrs[1]); }; static ssize_t klat_v4_addrs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%pI4\n%pI4\n", &klat_obj.xlat_v4_addrs[0], &klat_obj.xlat_v4_addrs[1]); } static struct kobject *clat_kobject; static struct kobj_attribute xlat_plat_attribute = { .attr = {.name = "xlat_plat", .mode = 0660}, .show = klat_plat_show, .store = klat_plat_store, }; static struct kobj_attribute xlat_addrs_attribute = { .attr = {.name = "xlat_addrs", .mode = 0660}, .show = klat_addrs_show, .store = klat_addrs_store, }; static struct kobj_attribute xlat_v4_addrs_attribute = { .attr = {.name = "xlat_v4_addrs", .mode = 0660}, .show = klat_v4_addrs_show, .store = klat_v4_addrs_store, }; static struct attribute *clat_attrs[] = { &xlat_plat_attribute.attr, &xlat_addrs_attribute.attr, &xlat_v4_addrs_attribute.attr, NULL, }; ATTRIBUTE_GROUPS(clat); #endif static int __init klat_init(void) { #ifndef CONFIG_CP_DIT clat_kobject = kobject_create_and_add(KOBJ_CLAT, kernel_kobj); if (!clat_kobject) mif_err("%s: done ---\n", KOBJ_CLAT); if (sysfs_create_groups(clat_kobject, clat_groups)) mif_err("failed to create clat groups node\n"); #endif register_netdevice_notifier(&klat_netdev_notifier); return 0; } static void klat_exit(void) { unregister_netdevice_notifier(&klat_netdev_notifier); #ifndef CONFIG_CP_DIT if (clat_kobject) kobject_put(clat_kobject); #endif } module_init(klat_init); module_exit(klat_exit); MODULE_LICENSE("GPL and additional rights"); MODULE_DESCRIPTION("SAMSUNG klat module"); MODULE_AUTHOR("Jong eon Park ");