/* Copyright (c) 2017, The Linux Foundation. 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 version 2 and * only version 2 as published by the Free Software Foundation. * * 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. */ #include #include #include #include #include #include #include #include #include unsigned int multiplication_factor = 1; module_param(multiplication_factor, uint, 0644); struct net_device *mydevice; static void iplo_do_ip_loopback(struct sk_buff *skb, int ip_offset) { struct iphdr *hdr; struct icmphdr *icmp; __be32 ipaddr; int i; hdr = (struct iphdr *)(skb->data + ip_offset); ipaddr = hdr->saddr; hdr->saddr = hdr->daddr; hdr->daddr = ipaddr; switch (hdr->protocol) { case 1: /* ICMP */ icmp = (struct icmphdr *)(skb->data + ip_offset + hdr->ihl * 4); if (icmp->type == ICMP_ECHO) icmp->type = ICMP_ECHOREPLY; break; case 11: /* UDP */ break; case 6: /* TCP */ break; default: break; } if (multiplication_factor < 2) { netif_rx(skb); skb->dev->stats.tx_packets++; } else { for (i = 0; i < (multiplication_factor - 1); i++) { netif_rx(skb_copy(skb, GFP_ATOMIC)); skb->dev->stats.tx_packets++; } netif_rx(skb); skb->dev->stats.tx_packets++; } } static netdev_tx_t iplo_vnd_start_xmit(struct sk_buff *skb, struct net_device *dev) { int ip_offset = 0; switch (ntohs(skb->protocol)) { case ETH_P_MAP: ip_offset = 4; case ETH_P_IP: iplo_do_ip_loopback(skb, ip_offset); dev->stats.rx_packets++; break; default: dev->stats.tx_dropped++; kfree_skb(skb); break; } return NETDEV_TX_OK; } static int iplo_vnd_ioctl_extended(struct net_device *dev, struct ifreq *ifr) { struct rmnet_ioctl_extended_s ext_cmd; int rc = 0; rc = copy_from_user(&ext_cmd, ifr->ifr_ifru.ifru_data, sizeof(struct rmnet_ioctl_extended_s)); if (rc) { pr_err("%s() copy_from_user failed, error %d\n", __func__, rc); return rc; } switch (ext_cmd.extended_ioctl) { case RMNET_IOCTL_SET_MRU: break; case RMNET_IOCTL_GET_EPID: ext_cmd.u.data = 100; break; case RMNET_IOCTL_GET_SUPPORTED_FEATURES: ext_cmd.u.data = 0; break; case RMNET_IOCTL_GET_DRIVER_NAME: strlcpy(ext_cmd.u.if_name, "rmnet_mhi", sizeof(ext_cmd.u.if_name)); break; default: rc = -EINVAL; break; } rc = copy_to_user(ifr->ifr_ifru.ifru_data, &ext_cmd, sizeof(struct rmnet_ioctl_extended_s)); if (rc) pr_err("%s() copy_to_user failed, error %d\n", __func__, rc); return rc; } static int iplo_vnd_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) { int rc = 0; struct rmnet_ioctl_data_s ioctl_data; switch (cmd) { case RMNET_IOCTL_SET_LLP_IP: /* Set RAWIP protocol */ break; case RMNET_IOCTL_GET_LLP: /* Get link protocol state */ ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, sizeof(struct rmnet_ioctl_data_s))) rc = -EFAULT; break; case RMNET_IOCTL_GET_OPMODE: /* Get operation mode */ ioctl_data.u.operation_mode = RMNET_MODE_LLP_IP; if (copy_to_user(ifr->ifr_ifru.ifru_data, &ioctl_data, sizeof(struct rmnet_ioctl_data_s))) rc = -EFAULT; break; case RMNET_IOCTL_SET_QOS_ENABLE: rc = -EINVAL; break; case RMNET_IOCTL_SET_QOS_DISABLE: rc = 0; break; case RMNET_IOCTL_OPEN: case RMNET_IOCTL_CLOSE: /* We just ignore them and return success */ rc = 0; break; case RMNET_IOCTL_EXTENDED: rc = iplo_vnd_ioctl_extended(dev, ifr); break; default: /* Don't fail any IOCTL right now */ rc = 0; break; } return rc; } static int iplo_vnd_change_mtu(struct net_device *dev, int new_mtu) { if (0 > new_mtu || 16384 < new_mtu) return -EINVAL; dev->mtu = new_mtu; return 0; } static const struct net_device_ops iplo_device_ops = { .ndo_init = 0, .ndo_do_ioctl = iplo_vnd_ioctl, .ndo_start_xmit = iplo_vnd_start_xmit, .ndo_change_mtu = iplo_vnd_change_mtu, }; static void iplo_device_setup(struct net_device *dev) { dev->flags |= IFF_NOARP; dev->netdev_ops = &iplo_device_ops; dev->mtu = 1500; dev->needed_headroom = 0; dev->watchdog_timeo = 100; dev->header_ops = 0; dev->type = ARPHRD_RAWIP; dev->hard_header_len = 0; dev->flags &= ~(IFF_BROADCAST | IFF_MULTICAST); dev->tx_queue_len = 1000; } int __init rmnet_iplo_init(void) { int rc; pr_err("iplo: Module is coming up\n"); mydevice = alloc_netdev(100, "rmnet_mhi0", NET_NAME_ENUM, iplo_device_setup); if (!mydevice) { pr_err("iplo: Failed to to allocate netdev for iplo\n"); return -EINVAL; } rtnl_lock(); rc = register_netdevice(mydevice); rtnl_unlock(); if (rc != 0) { pr_err("iplo: Failed to to register netdev [%s]\n", mydevice->name); free_netdev(mydevice); return -EINVAL; } return 0; } void __exit rmnet_iplo_exit(void) { unregister_netdev(mydevice); free_netdev(mydevice); pr_err("iplo: Module is going away\n"); } module_init(rmnet_iplo_init) module_exit(rmnet_iplo_exit) MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("RmNet IP Loop Back Driver");