/* * NET3 IP device support routines. * * 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. * * Derived from the IP parts of dev.c 1.0.19 * Authors: Ross Biro, * Fred N. van Kempen, * Mark Evans, * * Additional Authors: * Alan Cox, */ #include /* For CONFIG_IP_CLASSLESS */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_KERNELD #include #endif extern struct notifier_block *netdev_chain; /* * Determine a default network mask, based on the IP address. */ static unsigned long ip_get_mask(unsigned long addr) { unsigned long dst; if (ZERONET(addr)) return(0L); /* special case */ dst = ntohl(addr); if (IN_CLASSA(dst)) return(htonl(IN_CLASSA_NET)); if (IN_CLASSB(dst)) return(htonl(IN_CLASSB_NET)); if (IN_CLASSC(dst)) return(htonl(IN_CLASSC_NET)); /* * Something else, probably a multicast. */ return(0); } /* * This checks bitmasks for the ioctl calls for devices. */ static inline int bad_mask(__u32 mask, __u32 addr) { if (addr & (mask = ~mask)) return 1; mask = ntohl(mask); if (mask & (mask+1)) return 1; return 0; } int devinet_ioctl(unsigned int cmd, void *arg) { struct ifreq ifr; struct device *dev; __u32 addr; #ifdef CONFIG_NET_ALIAS int err; #endif /* * Fetch the caller's info block into kernel space */ if (copy_from_user(&ifr, arg, sizeof(struct ifreq))) return -EFAULT; /* * See which interface the caller is talking about. */ /* * * net_alias_dev_get(): dev_get() with added alias naming magic. * only allow alias creation/deletion if (getset==SIOCSIFADDR) * */ #ifdef CONFIG_KERNELD dev_load(ifr.ifr_name); #endif #ifdef CONFIG_NET_ALIAS if ((dev = net_alias_dev_get(ifr.ifr_name, cmd == SIOCSIFADDR, &err, NULL, NULL)) == NULL) return(err); #else if ((dev = dev_get(ifr.ifr_name)) == NULL) return(-ENODEV); #endif if (cmd != SIOCSIFADDR && dev->family != AF_INET) return(-EINVAL); switch(cmd) { case SIOCGIFADDR: /* Get interface address (and family) */ if (ifr.ifr_addr.sa_family == AF_UNSPEC) { memcpy(ifr.ifr_hwaddr.sa_data, dev->dev_addr, MAX_ADDR_LEN); ifr.ifr_hwaddr.sa_family = dev->type; } else { (*(struct sockaddr_in *) &ifr.ifr_addr).sin_addr.s_addr = dev->pa_addr; (*(struct sockaddr_in *) &ifr.ifr_addr).sin_family = dev->family; (*(struct sockaddr_in *) &ifr.ifr_addr).sin_port = 0; } break; case SIOCSIFADDR: /* Set interface address (and family) */ if (!suser()) return -EPERM; /* * BSDism. SIOCSIFADDR family=AF_UNSPEC sets the * physical address. We can cope with this now. */ if(ifr.ifr_addr.sa_family==AF_UNSPEC) { int ret; if(dev->set_mac_address==NULL) return -EOPNOTSUPP; ret = dev->set_mac_address(dev,&ifr.ifr_addr); if (!ret) notifier_call_chain(&netdev_chain, NETDEV_CHANGEADDR, dev); return ret; } if(ifr.ifr_addr.sa_family!=AF_INET) return -EINVAL; addr = (*(struct sockaddr_in *)&ifr.ifr_addr).sin_addr.s_addr; dev_lock_wait(); dev_lock_list(); if (dev->family == AF_INET && addr == dev->pa_addr) { dev_unlock_list(); return 0; } if (dev->flags & IFF_UP) notifier_call_chain(&netdev_chain, NETDEV_DOWN, dev); /* * if dev is an alias, must rehash to update * address change */ #ifdef CONFIG_NET_ALIAS if (net_alias_is(dev)) net_alias_dev_rehash(dev, &ifr.ifr_addr); #endif dev->pa_addr = addr; dev->ip_flags |= IFF_IP_ADDR_OK; dev->ip_flags &= ~(IFF_IP_BRD_OK|IFF_IP_MASK_OK); dev->family = AF_INET; if (dev->flags & IFF_POINTOPOINT) { dev->pa_mask = 0xFFFFFFFF; dev->pa_brdaddr = 0xFFFFFFFF; } else { dev->pa_mask = ip_get_mask(dev->pa_addr); dev->pa_brdaddr = dev->pa_addr|~dev->pa_mask; } if (dev->flags & IFF_UP) notifier_call_chain(&netdev_chain, NETDEV_UP, dev); dev_unlock_list(); return 0; case SIOCGIFBRDADDR: /* Get the broadcast address */ (*(struct sockaddr_in *) &ifr.ifr_broadaddr).sin_addr.s_addr = dev->pa_brdaddr; (*(struct sockaddr_in *) &ifr.ifr_broadaddr).sin_family = dev->family; (*(struct sockaddr_in *) &ifr.ifr_broadaddr).sin_port = 0; break; case SIOCSIFBRDADDR: /* Set the broadcast address */ if (!suser()) return -EPERM; addr = (*(struct sockaddr_in *)&ifr.ifr_broadaddr).sin_addr.s_addr; if (dev->flags & IFF_UP) ip_rt_change_broadcast(dev, addr); dev->pa_brdaddr = addr; dev->ip_flags |= IFF_IP_BRD_OK; return 0; case SIOCGIFDSTADDR: /* Get the destination address (for point-to-point links) */ (*(struct sockaddr_in *) &ifr.ifr_dstaddr).sin_addr.s_addr = dev->pa_dstaddr; (*(struct sockaddr_in *) &ifr.ifr_dstaddr).sin_family = dev->family; (*(struct sockaddr_in *) &ifr.ifr_dstaddr).sin_port = 0; break; case SIOCSIFDSTADDR: /* Set the destination address (for point-to-point links) */ if (!suser()) return -EPERM; addr = (*(struct sockaddr_in *)&ifr.ifr_dstaddr).sin_addr.s_addr; if (addr == dev->pa_dstaddr) return 0; if (dev->flags & IFF_UP) ip_rt_change_dstaddr(dev, addr); dev->pa_dstaddr = addr; return 0; case SIOCGIFNETMASK: /* Get the netmask for the interface */ (*(struct sockaddr_in *) &ifr.ifr_netmask).sin_addr.s_addr = dev->pa_mask; (*(struct sockaddr_in *) &ifr.ifr_netmask).sin_family = dev->family; (*(struct sockaddr_in *) &ifr.ifr_netmask).sin_port = 0; break; case SIOCSIFNETMASK: /* Set the netmask for the interface */ if (!suser()) return -EPERM; addr = (*(struct sockaddr_in *)&ifr.ifr_netmask).sin_addr.s_addr; if (addr == dev->pa_mask) { dev->ip_flags |= IFF_IP_MASK_OK; return 0; } /* * The mask we set must be legal. */ if (bad_mask(addr, 0)) return -EINVAL; if (addr == htonl(0xFFFFFFFE)) return -EINVAL; if (dev->flags & IFF_UP) ip_rt_change_netmask(dev, addr); dev->pa_mask = addr; dev->ip_flags |= IFF_IP_MASK_OK; dev->ip_flags &= ~IFF_IP_BRD_OK; return 0; default: return -EINVAL; } if (copy_to_user(arg, &ifr, sizeof(struct ifreq))) return -EFAULT; return 0; }