#include #include #include #include #include #include "netconf.h" #include "../paths.h" #include "netconf.m" #include "internal.h" extern CONFIG_FILE f_conf_routes; static CONFIG_FILE f_proc_route (PROC_NET_ROUTE,help_nil,CONFIGF_PROBED); /* Parse the argument of a route add command (read from ETC_CONF_ROUTES Return -1 if any error. */ static int route_parsecmd ( const char *buf, int noline, SSTRING &ip_dst, SSTRING &gateway, SSTRING &netmask, SSTRING &flags, SSTRING &iface) { int ret = 0; char words[8][100]; netmask.setfrom(""); words[0][0] = words[1][0] = words[2][0] = words[3][0] = '\0'; words[4][0] = words[5][0] = words[6][0] = words[7][0] = '\0'; sscanf (buf,"%s %s %s %s %s %s %s %s" ,words[0],words[1],words[2],words[3] ,words[4],words[5],words[6],words[7]); int i=0; int seen_host = 0; if (strcmp(words[0],"-net")==0){ flags.setfrom ("UG"); i++; }else if (strcmp(words[0],"-host")==0){ flags.setfrom ("UGH"); seen_host = 1; i++; }else{ flags.setfrom ("UG"); } if (strcmp(words[i],"default")==0 || ipnum_validip(words[i],seen_host)){ ip_dst.setfrom (words[i]); }else{ struct netent *ent = getnetbyname (words[i]); if (ent == NULL){ xconf_error (MSG_U(E_IVLDEST ,"Invalid destination %s for line %d in file %s\n%s\n") ,words[i],noline,ETC_CONF_ROUTES,buf); ret = -1; }else{ ipnum_ip2a(ent,ip_dst); } } i++; if (strcmp(words[i],"dev")==0){ i++; iface.setfrom (words[i]); i++; gateway.setfrom ("*"); flags.setfrom ("U"); }else if (strcmp(words[i],"gw")!=0){ xconf_error (MSG_U(E_NOKEYGW ,"Keyword gw missing from line %d of file %s\n%s") ,noline,ETC_CONF_ROUTES,buf); ret = -1; }else{ i++; if (ipnum_validip(words[i],1)){ gateway.setfrom (words[i]); }else{ struct hostent *ent = gethostbyname (words[i]); if (ent == NULL){ xconf_error (MSG_U(E_IVLGTW ,"Invalid gateway %s for line %d in file %s\n%s\n") ,words[i],noline,ETC_CONF_ROUTES,buf); ret = -1; }else{ ipnum_ip2a (ent,gateway); } } i++; } if (words[i][0] != '\0'){ if (strcmp(words[i],"netmask")!=0){ xconf_error (MSG_U(E_NETMASK ,"Keyword netmask expected, on line %d of file %s\n%s") ,noline,ETC_CONF_ROUTES,buf); ret = -1; }else{ i++; netmask.setfrom (words[i]); } } return ret; } /* Reformat a command (reverse route_parsecmd) */ static void route_formatcmd( const SSTRING &ip_dst, const SSTRING &gateway, const SSTRING &netmask, const SSTRING &flags, const SSTRING &iface, char *buf) { buf[0] = '\0'; if (flags.strchr('H')!=NULL){ strcat (buf,"-host "); }else{ strcat (buf,"-net "); } buf += strlen(buf); if (flags.strchr('G')==NULL){ buf += sprintf(buf,"%s dev %s",ip_dst.get(),iface.get()); }else{ buf += sprintf(buf,"%s gw %s",ip_dst.get(),gateway.get()); } if (flags.strchr('H')==NULL){ if (!netmask.is_empty()){ sprintf (buf," netmask %s",netmask.get()); } } } /* Control a route in the kernel */ PUBLIC ROUTE::ROUTE ( const char *dst, const char *gate, const char *mask, const char *_flags, const char *_iface) { ip_dst.setfrom (dst); ip_gateway.setfrom (gate); netmask.setfrom (mask); flags.setfrom (_flags); iface.setfrom (_iface); tag = 0; } PUBLIC ROUTE::ROUTE ( const char *buf, int noline) // Help generate error message { if (route_parsecmd(buf,noline,ip_dst,ip_gateway,netmask,flags,iface) ==-1){ invalid_line.setfrom (buf); } tag = 0; } PUBLIC ROUTE::ROUTE () { tag = 0; } PUBLIC ROUTE::~ROUTE () { } PUBLIC void ROUTE::formatcmd(char *buf) const { route_formatcmd (ip_dst,ip_gateway,netmask,flags,iface,buf); } /* Format and output in a file like ETC_CONF_ROUTE Output a line only if it contain either a valid info or an invalid (unparsable) line. So it does not generate empty line. */ PUBLIC void ROUTE::write (FILE_CFG *fout) { if (!invalid_line.is_empty()){ fprintf (fout,"%s\n",invalid_line.get()); }else if (!ip_dst.is_empty()){ char buf[300]; formatcmd(buf); fprintf (fout,"%s\n",buf); } } /* Return a flag recorded by settag. This allows an application to mark a ROUTE as seen or ok or not ok and later, get the flag back. THis flag has no internal use for ROUTE. It is just kind enough to store it. */ PUBLIC int ROUTE::gettag () const { return tag; } PUBLIC void ROUTE::settag (int _tag) { tag = _tag; } /* Return != 0 if this route is simply the route to the localnet without any gateway. */ PUBLIC int ROUTE::isdevice () const { return ip_gateway.cmp("*")==0; } /* Return the type of the route */ PUBLIC RTTYPE ROUTE::gettype() const { RTTYPE ret = RTTYPE_NETWORK; if (is_default()){ ret = RTTYPE_DEFAULT; }else if (isdevice()){ ret = RTTYPE_ALTNET; }else if (dst_is_host()){ ret = RTTYPE_HOST; } return ret; } /* Return the netmask of the route */ PUBLIC const char *ROUTE::getmask() const { return netmask.get(); } /* Get the interface (eth0) used for a route */ PUBLIC const char *ROUTE::getiface () const { return iface.get(); } /* Return the gateway of a route or the interface if it is a locally connected network. */ PUBLIC const char *ROUTE::getgateway() const { return isdevice() ? iface.get() : ip_gateway.get(); } /* Return the destination of a route. */ PUBLIC const char *ROUTE::getdst() const { return ip_dst.get(); } /* Return if the destination is a host or a network. Return != 0 if it is a host. */ PUBLIC int ROUTE::dst_is_host() const { return flags.strchr('H')!=NULL; } /* Tell is a route match a destination. Return != 0 if true. */ PUBLIC int ROUTE::match(const SSTRING &dst) const { return ip_dst.cmp(dst)==0; } /* Tell if two routes are the same. Return 0 if different destination. 1 if same destination but different configuration. 2 if exactly the same. */ PUBLIC int ROUTE::compare(const ROUTE *r) { int ret = 0; if (ip_dst.cmp(r->ip_dst) == 0){ ret = 1; const char *gw1 = getgateway(); // Get the gateway or interface const char *gw2 = r->getgateway(); if (strcmp(gw1,gw2)==0 && (r->netmask.is_empty() || netmask.cmp(r->netmask)==0) && flags.cmp(r->flags)==0){ ret = 2; } } return ret; } /* Delete a route from the kernel routing table. Returne -1 if any error. */ PUBLIC int ROUTE::kill () const { char cmd[100]; sprintf (cmd,"del %s",ip_dst.get()); return netconf_system_if ("route",cmd); } /* Indicate if a route define the loopback device */ PUBLIC int ROUTE::is_loopback() { return ip_dst.cmp("127.0.0.1")==0 && ip_gateway.cmp("*")==0; } /* Indicate if a route define the default route */ PUBLIC int ROUTE::is_default() const { return ip_dst.cmp("default")==0; } /* read all the routes currently active */ PUBLIC int ROUTES::readactive () { int ret = -1; FILE_CFG *fin = f_proc_route.fopen ("r"); if (fin != NULL){ char buf[300]; /* #Specification: route / /proc/net/route netconf use /proc/net/route to read the current route table. It read only the first four fields (destination, gateway genmask and Flags) are read. The genmask is ignored */ // Skip the first line (title line) fgets(buf,sizeof(buf)-1,fin); ret = 0; while (fgets(buf,sizeof(buf)-1,fin)!=NULL){ unsigned long ip_dst,ip_gate,gen_mask,flags; char junk[20],iface[20]; if (sscanf (buf,"%s %lx %lx %lx %s %s %s %lx",iface,&ip_dst,&ip_gate ,&flags,junk,junk,junk,&gen_mask)!=8){ xconf_error (MSG_U(E_IVLOUTPUT ,"Invalid content in /proc/net/route\n%s\n") ,buf); ret = -1; }else if (flags & 1){ // Only collect routes which are UP ip_dst = ntohl(ip_dst); ip_gate = ntohl(ip_gate); gen_mask = ntohl(gen_mask); char ip_dst_str[20],ip_gate_str[20],gen_mask_str[20]; ipnum_ip2a(ip_dst,ip_dst_str); ipnum_ip2a(ip_gate,ip_gate_str); ipnum_ip2a(gen_mask,gen_mask_str); if (ip_gate == 0) strcpy (ip_gate_str,"*"); if (ip_dst == 0) strcpy (ip_dst_str,"default"); if (gen_mask == 0) strcpy (gen_mask_str,"*"); char flagstr[20]; flagstr[0] = '\0'; if (flags & 1) strcat (flagstr,"U"); if (flags & 2){ // iface[0] = '\0'; strcat (flagstr,"G"); } if (flags & 4) strcat (flagstr,"H"); ROUTE *rt = new ROUTE (ip_dst_str,ip_gate_str,gen_mask_str ,flagstr,iface); if (rt != NULL) add (rt); } } fclose (fin); } return ret; } PUBLIC void ROUTES::write (FILE_CFG *fout) { for (int i=0; iwrite (fout); } /* Get one ROUTE of the table or NULL */ PUBLIC ROUTE *ROUTES::getitem(int no) const { return (ROUTE*)ARRAY::getitem(no); } /* Locate a ROUTE in the routing table. Return NULL if it does not exist. */ PUBLIC ROUTE *ROUTES::find (const SSTRING &ip_dst) { ROUTE *ret = NULL; int nb = getnb(); for (int i=0; imatch(ip_dst)){ ret = pt; break; } } return ret; } /* Locate the default ROUTE in the routing table. Return NULL if it does not exist. */ PUBLIC ROUTE *ROUTES::finddefault () { ROUTE *ret = NULL; int nb = getnb(); for (int i=0; iis_default()){ ret = pt; break; } } return ret; } extern NETCONF_HELP_FILE help_routes; static CONFIG_FILE f_var_run_current (VAR_RUN_ROUTES_CURRENT ,help_routes,CONFIGF_MANAGED|CONFIGF_OPTIONAL|CONFIGF_ERASED); /* Read all route info stored in VAR_RUN_ROUTES_CURRENT */ PUBLIC void ROUTES::readbyme() { FILE_CFG *fin = f_var_run_current.fopen("r"); if (fin != NULL){ char buf[400]; int noline = 0; while (fgets(buf,sizeof(buf)-1,fin)){ noline++; add (new ROUTE (buf,0)); } fclose (fin); } } /* Store all route set during this session in VAR_RUN_ROUTES_CURRENT */ PUBLIC void ROUTES::writebyme() { FILE_CFG *fout = f_var_run_current.fopen("w"); if (fout != NULL){ write (fout); fclose (fout); } } struct DEVINFO{ const char *device; long addr; // Ip number of the device long mask; // netmask }; /* Verify if the proper device is initialised to set a route Check also the route to alternate local net to see if a gateway is reachable from there. Return true if one is available */ static bool route_maywork ( ROUTE *rt, DEVINFO tbinfo[], int nbdev, ROUTES &conf) // Configured route. Only the RTTYPE_ALTNET // matter { bool ret = false; if (rt->isdevice()){ const char *device = rt->getiface(); for (int i=0; igetgateway()); for (int i=0; imask) == (pdev->addr & pdev->mask)){ ret = true; break; } } if (!ret){ for (int i=0; igettype()==RTTYPE_ALTNET){ long net = ipnum_aip2l(drt->getdst()); long msk = ipnum_aip2l(drt->getmask()); if ((ip_gtw & msk) == net){ ret = true; break; } } } } } return ret; } /* Locate the device associate with a network/netmask pair or a host Use the current kernel setup to locate the device, not the system configuration (some device are dhcp so do not have configs) Return -1 if no device matches. */ static int route_finddev( const char *hostip, // Host IP number char *dev) { dev[0] = '\0'; int ret = -1; unsigned long hostipl = ipnum_aip2l(hostip); SSTRINGS lst; if (devlist_read (lst,true,true) != -1){ int n = lst.getnb(); for (int i=0; iget(); IFCONFIG_INFO info; if (ifconfig_getinfo(netdev,info)!=-1){ unsigned long net = ipnum_aip2l (info.ip_addr); unsigned long msk = ipnum_aip2l (info.netmask); net &= msk; if ((hostipl & msk) == net){ strcpy (dev,netdev); ret = 0; break; } } } } return ret; } enum ROUTE_CMD { ROUTE_KILL, ROUTE_ADD, ROUTE_KEEP }; /* Add this route to the list of device for which the route have changed */ static int route_addchange ( const ROUTE *pt, ROUTE_CMD routecmd, // Should we kill the route const char *target, // We manage only route using this // device unless target==NULL SSTRINGS &devreconf) { int ret = 0; const char *dev = pt->getgateway(); char device[20]; if (!pt->isdevice()){ if (route_finddev (dev,device) != -1){ dev = device; }else{ dev = "unknown"; } } if (routecmd != ROUTE_KEEP && devreconf.lookup(dev)==-1){ devreconf.add (new SSTRING(dev)); } if (target == NULL || strcmp(target,dev)==0){ if (routecmd == ROUTE_KILL){ net_printhint ("del %s\n",pt->getdst()); ret = pt->kill(); }else{ char cmd[300],buf[300]; pt->formatcmd (buf); if (routecmd == ROUTE_ADD){ sprintf (cmd,"add %s",buf); net_printhint ("%s\n",cmd); ret = netconf_system_if ("route",cmd); }else{ // ROUTE_KEEP //net_printhint ("keep %s\n",buf); } } } return ret; } /* Read the /etc/conf.routes and install/correct all routes. New routes are added, current routes are validated and corrected if need (deleted, reinstall), and obsolete route are removed. Return -1 if any error. */ int route_install (const char *routedev, bool dev_normal, bool dev_aliases) { int ret = -1; /* #Specification: ETC_CONF_ROUTES / optionnal The file ETC_CONF_ROUTES is optionnal. It means no extra routes (only route to local network) will be set if missing. */ ROUTES active; active.readactive(); ROUTES lasttime; // List of routes established by this program // during a different session lasttime.readbyme(); ROUTES thistime; // Routes that will be set or accepted this // time thistime.neverdelete(); ROUTES conf; conf.readconf (); /* #Specification: IP routes / activation order We activate the routes to alternate local net, then route to hosts or networks. We also only activate route for device which are available. */ SSTRINGS tbdev; int nbdev = devlist_read (tbdev,dev_normal,dev_aliases); DEVINFO tbinfo[nbdev]; for (int dev=0; devget(); tbinfo[dev].device = device; IFCONFIG_INFO info; ifconfig_getinfo (device,info); tbinfo[dev].addr = ipnum_aip2l (info.ip_addr); tbinfo[dev].mask = ipnum_aip2l (info.netmask); } net_enableprinthint (routedev != NULL); SSTRINGS devreconf; for (int order=0; order<4; order++){ static RTTYPE tbtype[]={ RTTYPE_ALTNET,RTTYPE_NETWORK,RTTYPE_HOST,RTTYPE_DEFAULT }; RTTYPE route_type = tbtype[order]; for (int r=0; rgettype()==route_type){ const char *ip_dst = pt->getdst(); ROUTE * rt = active.find (ip_dst); bool add = false; if (rt == NULL){ if(route_maywork(pt,tbinfo,nbdev,conf)){ add = true; } }else if(lasttime.find(ip_dst)!=NULL){ // This route was set by me, check if it has // to be updated rt->settag(1); if (rt->compare (pt) != 2){ ret |= route_addchange (rt,ROUTE_KILL,routedev,devreconf); add = true; }else{ // This route is still valid, must be written // back into /var/run/routes.current thistime.add (pt); //ret |= route_addchange (rt,ROUTE_KEEP,routedev,devreconf); } } /* #Specification: netconf / update routes netconf will only update a route or delete it if it is not needed any more or is slightly different. This means that you should not see any network problem if you add a new route and run netconf --update */ if (add){ thistime.add (pt); ret |= route_addchange (pt,ROUTE_ADD,routedev,devreconf); } } } } /* #Specification: netconf / update routes / routed netconf kills any route not found any more in ETC_CONF_ROUTES This may cause a problem to routed. To avoid it, netconf will only kill the routes it have setup itself. Comments welcome. */ for (int i=0; igettag()==0 && lasttime.find(pt->getdst())!=NULL){ route_addchange (pt,ROUTE_KILL,routedev,devreconf); } } if (routedev == NULL) groutes_setrouting(); if (!simul_ison() || simul_ishint())thistime.writebyme(); if (routedev == NULL){ SSTRING reconf; for (int i=0; iget()); } net_hint ("DEV_RECONF_ROUTES",reconf.get()); } net_enableprinthint (true); return ret; } /* Check if a route is active to a destination */ int route_isactive ( const char *dest, char *gateway) { ROUTES active; active.readactive(); ROUTE *rt = active.find(dest); if (rt != NULL) strcpy (gateway,rt->getgateway()); return rt != NULL; }