Loading commands/network/ip.cpp +255 −10 Changes for commands/network/ip.cpp: 255 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -34,11 +34,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // getifaddrs() for address enumeration, and /proc/net/route + /proc/net/arp // for the read side of routes/neighbours. That covers every common `ip` // invocation (link/addr/route/neigh show, link set up/down, addr add/del, // route add/del) without needing netlink at all. Toybox itself never had an // `ip` applet (only the older ifconfig/route) - this exists to replace that // pairing with the modern single command, not to be a complete iproute2 // clone (no policy routing, no multiple routing tables, no tunnels/bridges/ // VLANs, IPv6 route add/del is show-only). // route add/del) without needing netlink at all. Bridges and bonds can be // created, deleted and given ports the same way: bridges with brctl's // ioctls (SIOCBRADDBR/SIOCBRDELBR/SIOCBRADDIF/SIOCBRDELIF), bonds through // /sys/class/net/bonding_masters and <bond>/bonding/slaves. Link types that // only netlink can make (veth, dummy, vlan, ...) are refused with an error. // Toybox itself never had an `ip` applet (only the older ifconfig/route) - // this exists to replace that pairing with the modern single command, not // to be a complete iproute2 clone (no policy routing, no multiple routing // tables, no tunnels/VLANs, IPv6 route add/del is show-only). #include <iostream> #include <sstream> Loading @@ -49,8 +53,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <cstdio> #include <cerrno> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/stat.h> #include <linux/sockios.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/route.h> Loading @@ -70,6 +77,10 @@ namespace fresh { namespace cmd { std::cout << "usage: ip link show [DEV]\n" " ip link set DEV up|down\n" " ip link set DEV master BRIDGE|BOND\n" " ip link set DEV nomaster\n" " ip link add NAME type bridge|bond\n" " ip link del DEV\n" " ip addr show [DEV]\n" " ip addr add CIDR dev DEV\n" " ip addr del CIDR dev DEV\n" Loading Loading @@ -289,6 +300,168 @@ namespace fresh { namespace cmd { return 0; } // ---- link add/del/master: bridges (ioctl) and bonds (sysfs) ------- static bool isDir(const std::string &path){ struct stat st; return stat(path.c_str(),&st)==0 && S_ISDIR(st.st_mode); } static bool isBridge(const std::string &dev){ return isDir("/sys/class/net/"+dev+"/bridge"); } static bool isBond(const std::string &dev){ return isDir("/sys/class/net/"+dev+"/bonding"); } // writes one line to a sysfs file; the kernel's answer is errno static bool writeSysfs(const std::string &path,const std::string &value){ int fd=open(path.c_str(),O_WRONLY|O_CLOEXEC); if(fd<0) return false; std::string v=value+"\n"; bool ok=write(fd,v.data(),v.size())==(ssize_t)v.size(); int e=errno; close(fd); errno=e; return ok; } // SIOCBR* take the name (ADDBR/DELBR) or an ifreq (ADDIF/DELIF) static int bridgeIoctl(unsigned long req,const std::string &bridge,const std::string &port){ int s=ioctlSocket(); if(s<0) return -1; int rc; if(port.empty()){ char name[IFNAMSIZ]={0}; strncpy(name,bridge.c_str(),IFNAMSIZ-1); rc=ioctl(s,req,name); }else{ struct ifreq ifr; memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name,bridge.c_str(),IFNAMSIZ-1); ifr.ifr_ifindex=ifIndex(port); rc=ifr.ifr_ifindex ? ioctl(s,req,&ifr) : (errno=ENODEV,-1); } int e=errno; close(s); errno=e; return rc; } static bool validIfName(const std::string &name){ if(name.empty() || name.size()>=IFNAMSIZ || name=="." || name=="..") return false; for(char c:name) if(c=='/' || c==':' || c<=' ') return false; return true; } static int cmdLinkAdd(const std::string &name,const std::string &type){ if(!validIfName(name)){ std::cerr << "ip: link add: invalid name \"" << name << "\"" << std::endl; return 1; } if(ifIndex(name)){ std::cerr << "ip: link add: " << name << ": " << strerror(EEXIST) << std::endl; return 1; } if(type=="bridge"){ if(bridgeIoctl(SIOCBRADDBR,name,"")!=0){ std::cerr << "ip: link add " << name << ": " << strerror(errno) << (errno==ENOPKG || errno==EINVAL || errno==ENOTTY ? " (no bridge support in the kernel: CONFIG_BRIDGE)" : "") << std::endl; return 1; } return 0; } if(type=="bond"){ if(!writeSysfs("/sys/class/net/bonding_masters","+"+name)){ std::cerr << "ip: link add " << name << ": " << (errno==ENOENT ? "no bonding driver (CONFIG_BONDING)" : strerror(errno)) << std::endl; return 1; } return 0; } if(type.empty()) std::cerr << "ip: link add: \"type bridge\" or \"type bond\" is required" << std::endl; else std::cerr << "ip: link add: type \"" << type << "\" isn't supported here (only bridge and bond)" << std::endl; return 1; } static int cmdLinkDel(const std::string &dev){ if(!ifIndex(dev)){ std::cerr << "ip: link del: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } if(isBridge(dev)){ if(bridgeIoctl(SIOCBRDELBR,dev,"")!=0){ // the kernel only deletes a bridge that is down std::cerr << "ip: link del " << dev << ": " << strerror(errno) << (errno==EBUSY ? " (set it down first)" : "") << std::endl; return 1; } return 0; } if(isBond(dev)){ if(!writeSysfs("/sys/class/net/bonding_masters","-"+dev)){ std::cerr << "ip: link del " << dev << ": " << strerror(errno) << std::endl; return 1; } return 0; } std::cerr << "ip: link del: " << dev << " is neither a bridge nor a bond (others need netlink)" << std::endl; return 1; } // the bridge or bond dev is in now ("" if none) static std::string currentMaster(const std::string &dev){ char buf[256]; ssize_t n=readlink(("/sys/class/net/"+dev+"/master").c_str(),buf,sizeof(buf)-1); if(n<=0) return ""; buf[n]=0; const char *slash=strrchr(buf,'/'); return slash ? slash+1 : buf; } static int cmdLinkMaster(const std::string &dev,const std::string &master){ if(!ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } std::string old=currentMaster(dev); if(master.empty()){ // nomaster if(old.empty()) return 0; bool ok=isBond(old) ? writeSysfs("/sys/class/net/"+old+"/bonding/slaves","-"+dev) : bridgeIoctl(SIOCBRDELIF,old,dev)==0; if(!ok){ std::cerr << "ip: " << dev << ": nomaster: " << strerror(errno) << std::endl; return 1; } return 0; } if(old==master) return 0; if(!old.empty() && cmdLinkMaster(dev,"")!=0) return 1; bool ok; if(isBridge(master)){ ok=bridgeIoctl(SIOCBRADDIF,master,dev)==0; }else if(isBond(master)){ // the kernel wants a bond's slaves down when they join cmdLinkSet(dev,"down"); ok=writeSysfs("/sys/class/net/"+master+"/bonding/slaves","+"+dev); }else{ std::cerr << "ip: " << master << " is neither a bridge nor a bond" << std::endl; return 1; } if(!ok){ std::cerr << "ip: " << dev << ": master " << master << ": " << strerror(errno) << std::endl; return 1; } return 0; } // ---- addr --------------------------------------------------------- static int cmdAddrShow(const std::string &onlyDev){ Loading Loading @@ -564,14 +737,68 @@ namespace fresh { namespace cmd { const std::string &object=a[0]; if(object=="link" || object=="l"){ std::string action=a.size()>=2 ? matchAction(a[1],{"show","set"}) : "show"; if(action=="set" && a.size()>=3) return cmdLinkSet(a[2],a.size()>=4 ? a[3] : ""); std::string action=a.size()>=2 ? matchAction(a[1],{"show","set","add","delete","del"}) : "show"; if(action=="set"){ if(a.size()<4){ std::cerr << "ip: usage: ip link set DEV up|down|master BRIDGE|nomaster" << std::endl; return 1; } // several settings at once: ip link set eth1 master br0 up int rc=0; for(size_t i=3;i<a.size() && rc==0;++i){ if(a[i]=="up" || a[i]=="down") rc=cmdLinkSet(a[2],a[i]); else if(a[i]=="master" && i+1<a.size()) rc=cmdLinkMaster(a[2],a[++i]); else if(a[i]=="nomaster") rc=cmdLinkMaster(a[2],""); else{ std::cerr << "ip: link set: unknown setting \"" << a[i] << "\"" << std::endl; rc=1; } } return rc; } if(action=="add"){ // ip link add [name] NAME type TYPE std::string name,type; for(size_t i=2;i<a.size();++i){ if(a[i]=="type" && i+1<a.size()) type=a[++i]; else if(a[i]=="name" && i+1<a.size()) name=a[++i]; else if(name.empty()) name=a[i]; else{ std::cerr << "ip: link add: unknown argument \"" << a[i] << "\"" << std::endl; return 1; } } return cmdLinkAdd(name,type); } if(action=="delete" || action=="del"){ std::string dev=a.size()>=3 ? a[2] : ""; if(dev=="dev" && a.size()>=4) dev=a[3]; if(dev.empty()){ std::cerr << "ip: usage: ip link del DEV" << std::endl; return 1; } return cmdLinkDel(dev); } std::string dev; if(action=="show") dev=a.size()>=3 ? a[2] : ""; else if(a.size()>=2) // bare "ip link eth0" convenience form else if(a.size()>=2){ // bare "ip link eth0" convenience form -- only for an // interface that exists, anything else is a mistake if(!ifIndex(a[1])){ std::cerr << "ip: link: unknown command or device \"" << a[1] << "\"" << std::endl; usage(); return 1; } dev=a[1]; } if(!dev.empty() && !ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } return cmdLinkShow(dev,showStats); } Loading @@ -596,8 +823,21 @@ namespace fresh { namespace cmd { std::string dev; if(action=="show") dev=a.size()>=3 ? a[2] : ""; else if(a.size()>=2) // bare "ip addr eth0" convenience form else if(a.size()>=2){ // bare "ip addr eth0" convenience form -- only for an // interface that exists if(!ifIndex(a[1])){ std::cerr << "ip: addr: unknown command or device \"" << a[1] << "\"" << std::endl; usage(); return 1; } dev=a[1]; } if(dev=="dev" && action=="show" && a.size()>=4) dev=a[3]; // ip addr show dev eth0 if(!dev.empty() && !ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } return cmdAddrShow(dev); } Loading @@ -613,6 +853,11 @@ namespace fresh { namespace cmd { } return cmdRouteAddDel(add,cidr,gateway,dev); } if(a.size()>=2 && action.empty()){ std::cerr << "ip: route: unknown command \"" << a[1] << "\"" << std::endl; usage(); return 1; } return cmdRouteShow(); } Loading Loading
commands/network/ip.cpp +255 −10 Changes for commands/network/ip.cpp: 255 added lines, 10 removed lines. Original line number Diff line number Diff line Loading @@ -34,11 +34,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // getifaddrs() for address enumeration, and /proc/net/route + /proc/net/arp // for the read side of routes/neighbours. That covers every common `ip` // invocation (link/addr/route/neigh show, link set up/down, addr add/del, // route add/del) without needing netlink at all. Toybox itself never had an // `ip` applet (only the older ifconfig/route) - this exists to replace that // pairing with the modern single command, not to be a complete iproute2 // clone (no policy routing, no multiple routing tables, no tunnels/bridges/ // VLANs, IPv6 route add/del is show-only). // route add/del) without needing netlink at all. Bridges and bonds can be // created, deleted and given ports the same way: bridges with brctl's // ioctls (SIOCBRADDBR/SIOCBRDELBR/SIOCBRADDIF/SIOCBRDELIF), bonds through // /sys/class/net/bonding_masters and <bond>/bonding/slaves. Link types that // only netlink can make (veth, dummy, vlan, ...) are refused with an error. // Toybox itself never had an `ip` applet (only the older ifconfig/route) - // this exists to replace that pairing with the modern single command, not // to be a complete iproute2 clone (no policy routing, no multiple routing // tables, no tunnels/VLANs, IPv6 route add/del is show-only). #include <iostream> #include <sstream> Loading @@ -49,8 +53,11 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include <cstdio> #include <cerrno> #include <fcntl.h> #include <unistd.h> #include <sys/socket.h> #include <sys/stat.h> #include <linux/sockios.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/route.h> Loading @@ -70,6 +77,10 @@ namespace fresh { namespace cmd { std::cout << "usage: ip link show [DEV]\n" " ip link set DEV up|down\n" " ip link set DEV master BRIDGE|BOND\n" " ip link set DEV nomaster\n" " ip link add NAME type bridge|bond\n" " ip link del DEV\n" " ip addr show [DEV]\n" " ip addr add CIDR dev DEV\n" " ip addr del CIDR dev DEV\n" Loading Loading @@ -289,6 +300,168 @@ namespace fresh { namespace cmd { return 0; } // ---- link add/del/master: bridges (ioctl) and bonds (sysfs) ------- static bool isDir(const std::string &path){ struct stat st; return stat(path.c_str(),&st)==0 && S_ISDIR(st.st_mode); } static bool isBridge(const std::string &dev){ return isDir("/sys/class/net/"+dev+"/bridge"); } static bool isBond(const std::string &dev){ return isDir("/sys/class/net/"+dev+"/bonding"); } // writes one line to a sysfs file; the kernel's answer is errno static bool writeSysfs(const std::string &path,const std::string &value){ int fd=open(path.c_str(),O_WRONLY|O_CLOEXEC); if(fd<0) return false; std::string v=value+"\n"; bool ok=write(fd,v.data(),v.size())==(ssize_t)v.size(); int e=errno; close(fd); errno=e; return ok; } // SIOCBR* take the name (ADDBR/DELBR) or an ifreq (ADDIF/DELIF) static int bridgeIoctl(unsigned long req,const std::string &bridge,const std::string &port){ int s=ioctlSocket(); if(s<0) return -1; int rc; if(port.empty()){ char name[IFNAMSIZ]={0}; strncpy(name,bridge.c_str(),IFNAMSIZ-1); rc=ioctl(s,req,name); }else{ struct ifreq ifr; memset(&ifr,0,sizeof(ifr)); strncpy(ifr.ifr_name,bridge.c_str(),IFNAMSIZ-1); ifr.ifr_ifindex=ifIndex(port); rc=ifr.ifr_ifindex ? ioctl(s,req,&ifr) : (errno=ENODEV,-1); } int e=errno; close(s); errno=e; return rc; } static bool validIfName(const std::string &name){ if(name.empty() || name.size()>=IFNAMSIZ || name=="." || name=="..") return false; for(char c:name) if(c=='/' || c==':' || c<=' ') return false; return true; } static int cmdLinkAdd(const std::string &name,const std::string &type){ if(!validIfName(name)){ std::cerr << "ip: link add: invalid name \"" << name << "\"" << std::endl; return 1; } if(ifIndex(name)){ std::cerr << "ip: link add: " << name << ": " << strerror(EEXIST) << std::endl; return 1; } if(type=="bridge"){ if(bridgeIoctl(SIOCBRADDBR,name,"")!=0){ std::cerr << "ip: link add " << name << ": " << strerror(errno) << (errno==ENOPKG || errno==EINVAL || errno==ENOTTY ? " (no bridge support in the kernel: CONFIG_BRIDGE)" : "") << std::endl; return 1; } return 0; } if(type=="bond"){ if(!writeSysfs("/sys/class/net/bonding_masters","+"+name)){ std::cerr << "ip: link add " << name << ": " << (errno==ENOENT ? "no bonding driver (CONFIG_BONDING)" : strerror(errno)) << std::endl; return 1; } return 0; } if(type.empty()) std::cerr << "ip: link add: \"type bridge\" or \"type bond\" is required" << std::endl; else std::cerr << "ip: link add: type \"" << type << "\" isn't supported here (only bridge and bond)" << std::endl; return 1; } static int cmdLinkDel(const std::string &dev){ if(!ifIndex(dev)){ std::cerr << "ip: link del: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } if(isBridge(dev)){ if(bridgeIoctl(SIOCBRDELBR,dev,"")!=0){ // the kernel only deletes a bridge that is down std::cerr << "ip: link del " << dev << ": " << strerror(errno) << (errno==EBUSY ? " (set it down first)" : "") << std::endl; return 1; } return 0; } if(isBond(dev)){ if(!writeSysfs("/sys/class/net/bonding_masters","-"+dev)){ std::cerr << "ip: link del " << dev << ": " << strerror(errno) << std::endl; return 1; } return 0; } std::cerr << "ip: link del: " << dev << " is neither a bridge nor a bond (others need netlink)" << std::endl; return 1; } // the bridge or bond dev is in now ("" if none) static std::string currentMaster(const std::string &dev){ char buf[256]; ssize_t n=readlink(("/sys/class/net/"+dev+"/master").c_str(),buf,sizeof(buf)-1); if(n<=0) return ""; buf[n]=0; const char *slash=strrchr(buf,'/'); return slash ? slash+1 : buf; } static int cmdLinkMaster(const std::string &dev,const std::string &master){ if(!ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } std::string old=currentMaster(dev); if(master.empty()){ // nomaster if(old.empty()) return 0; bool ok=isBond(old) ? writeSysfs("/sys/class/net/"+old+"/bonding/slaves","-"+dev) : bridgeIoctl(SIOCBRDELIF,old,dev)==0; if(!ok){ std::cerr << "ip: " << dev << ": nomaster: " << strerror(errno) << std::endl; return 1; } return 0; } if(old==master) return 0; if(!old.empty() && cmdLinkMaster(dev,"")!=0) return 1; bool ok; if(isBridge(master)){ ok=bridgeIoctl(SIOCBRADDIF,master,dev)==0; }else if(isBond(master)){ // the kernel wants a bond's slaves down when they join cmdLinkSet(dev,"down"); ok=writeSysfs("/sys/class/net/"+master+"/bonding/slaves","+"+dev); }else{ std::cerr << "ip: " << master << " is neither a bridge nor a bond" << std::endl; return 1; } if(!ok){ std::cerr << "ip: " << dev << ": master " << master << ": " << strerror(errno) << std::endl; return 1; } return 0; } // ---- addr --------------------------------------------------------- static int cmdAddrShow(const std::string &onlyDev){ Loading Loading @@ -564,14 +737,68 @@ namespace fresh { namespace cmd { const std::string &object=a[0]; if(object=="link" || object=="l"){ std::string action=a.size()>=2 ? matchAction(a[1],{"show","set"}) : "show"; if(action=="set" && a.size()>=3) return cmdLinkSet(a[2],a.size()>=4 ? a[3] : ""); std::string action=a.size()>=2 ? matchAction(a[1],{"show","set","add","delete","del"}) : "show"; if(action=="set"){ if(a.size()<4){ std::cerr << "ip: usage: ip link set DEV up|down|master BRIDGE|nomaster" << std::endl; return 1; } // several settings at once: ip link set eth1 master br0 up int rc=0; for(size_t i=3;i<a.size() && rc==0;++i){ if(a[i]=="up" || a[i]=="down") rc=cmdLinkSet(a[2],a[i]); else if(a[i]=="master" && i+1<a.size()) rc=cmdLinkMaster(a[2],a[++i]); else if(a[i]=="nomaster") rc=cmdLinkMaster(a[2],""); else{ std::cerr << "ip: link set: unknown setting \"" << a[i] << "\"" << std::endl; rc=1; } } return rc; } if(action=="add"){ // ip link add [name] NAME type TYPE std::string name,type; for(size_t i=2;i<a.size();++i){ if(a[i]=="type" && i+1<a.size()) type=a[++i]; else if(a[i]=="name" && i+1<a.size()) name=a[++i]; else if(name.empty()) name=a[i]; else{ std::cerr << "ip: link add: unknown argument \"" << a[i] << "\"" << std::endl; return 1; } } return cmdLinkAdd(name,type); } if(action=="delete" || action=="del"){ std::string dev=a.size()>=3 ? a[2] : ""; if(dev=="dev" && a.size()>=4) dev=a[3]; if(dev.empty()){ std::cerr << "ip: usage: ip link del DEV" << std::endl; return 1; } return cmdLinkDel(dev); } std::string dev; if(action=="show") dev=a.size()>=3 ? a[2] : ""; else if(a.size()>=2) // bare "ip link eth0" convenience form else if(a.size()>=2){ // bare "ip link eth0" convenience form -- only for an // interface that exists, anything else is a mistake if(!ifIndex(a[1])){ std::cerr << "ip: link: unknown command or device \"" << a[1] << "\"" << std::endl; usage(); return 1; } dev=a[1]; } if(!dev.empty() && !ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } return cmdLinkShow(dev,showStats); } Loading @@ -596,8 +823,21 @@ namespace fresh { namespace cmd { std::string dev; if(action=="show") dev=a.size()>=3 ? a[2] : ""; else if(a.size()>=2) // bare "ip addr eth0" convenience form else if(a.size()>=2){ // bare "ip addr eth0" convenience form -- only for an // interface that exists if(!ifIndex(a[1])){ std::cerr << "ip: addr: unknown command or device \"" << a[1] << "\"" << std::endl; usage(); return 1; } dev=a[1]; } if(dev=="dev" && action=="show" && a.size()>=4) dev=a[3]; // ip addr show dev eth0 if(!dev.empty() && !ifIndex(dev)){ std::cerr << "ip: " << dev << ": " << strerror(ENODEV) << std::endl; return 1; } return cmdAddrShow(dev); } Loading @@ -613,6 +853,11 @@ namespace fresh { namespace cmd { } return cmdRouteAddDel(add,cidr,gateway,dev); } if(a.size()>=2 && action.empty()){ std::cerr << "ip: route: unknown command \"" << a[1] << "\"" << std::endl; usage(); return 1; } return cmdRouteShow(); } Loading