Commit 9ce54615 authored by jan.koester's avatar jan.koester
Browse files

tset

parent 418d788d
Loading
Loading
Loading
Loading
+255 −10
Changes for commands/network/ip.cpp: 255 added lines, 10 removed lines.
Original line number Diff line number Diff line
@@ -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>
@@ -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>
@@ -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"
@@ -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){
@@ -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);
            }

@@ -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);
            }

@@ -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();
            }