Logo Search packages:      
Sourcecode: parprouted version File versions  Download package

parprouted.c

/* parprouted: ProxyARP routing daemon. 
 * (C) 2008 Vladimir Ivaschenko <vi@maks.net>
 *
 * This application is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License.
 *
 * This software 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 */
 
#include "parprouted.h"

char *progname;
int debug=0;
int verbose=0;
int option_arpperm=0;
static int perform_shutdown=0;

char *errstr;

pthread_t my_threads[MAX_IFACES+1];
int last_thread_idx=-1;

char * ifaces[MAX_IFACES];
int last_iface_idx=-1;

ARPTAB_ENTRY **arptab;
pthread_mutex_t arptab_mutex;

ARPTAB_ENTRY * replace_entry(struct in_addr ipaddr, char *dev) 
{
    ARPTAB_ENTRY * cur_entry=*arptab;
    ARPTAB_ENTRY * prev_entry=NULL;

    while (cur_entry != NULL && ( ipaddr.s_addr != cur_entry->ipaddr_ia.s_addr || ( strncmp(cur_entry->ifname,dev,strlen(dev)) != 0 ) ) ) {
      prev_entry = cur_entry;
      cur_entry = cur_entry->next;
    };

    if (cur_entry == NULL) {
      if (debug) printf("Creating new arptab entry %s(%s)\n", inet_ntoa(ipaddr), dev);

      if ((cur_entry = (ARPTAB_ENTRY *) malloc(sizeof(ARPTAB_ENTRY))) == NULL) {
          errstr = strerror(errno);
          syslog(LOG_INFO, "No memory: %s", errstr);
      } else {
          if (prev_entry == NULL) { *arptab=cur_entry; }
          else { prev_entry->next = cur_entry; }
          cur_entry->next = NULL;
          cur_entry->ifname[0] = '\0';
          cur_entry->route_added = 0;
          cur_entry->want_route = 1;
      }
    }
    
    return cur_entry;   
}

int findentry(struct in_addr ipaddr)
{
    ARPTAB_ENTRY * cur_entry=*arptab;
    ARPTAB_ENTRY * prev_entry=NULL;
        
    while (cur_entry != NULL && ipaddr.s_addr != cur_entry->ipaddr_ia.s_addr) {
      prev_entry = cur_entry;
      cur_entry = cur_entry->next;
    };
    
    if (cur_entry == NULL)
      return 0;
    else
      return 1;
}

/* Remove all entires in arptab where ipaddr is NOT on interface dev */
int remove_other_routes(struct in_addr ipaddr, const char* dev)
{
    ARPTAB_ENTRY * cur_entry;
    int removed = 0;
        
    for (cur_entry=*arptab; cur_entry != NULL; cur_entry = cur_entry->next) {
        if (ipaddr.s_addr == cur_entry->ipaddr_ia.s_addr && strcmp(dev, cur_entry->ifname) != 0)  {
            if (debug && cur_entry->want_route) printf("Marking entry %s(%s) for removal\n", inet_ntoa(ipaddr), cur_entry->ifname);
            cur_entry->want_route = 0;
            ++removed;
        }
    }
    return removed;
}


/* Remove route from kernel */
int route_remove(ARPTAB_ENTRY* cur_entry)
{
    char routecmd_str[ROUTE_CMD_LEN];
    int success = 1;
    
    if (snprintf(routecmd_str, ROUTE_CMD_LEN-1, 
          "/sbin/ip route del %s/32 metric 50 dev %s scope link",
          inet_ntoa(cur_entry->ipaddr_ia), cur_entry->ifname) > ROUTE_CMD_LEN-1) 
    {
      syslog(LOG_INFO, "ip route command too large to fit in buffer!");
    } else {
      if (system(routecmd_str) != 0)
      {
          syslog(LOG_INFO, "'%s' unsuccessful!", routecmd_str);
          if (debug) printf("%s failed\n", routecmd_str);
          success = 0;
      }
      else 
      {
          if (debug) printf("%s success\n", routecmd_str);
          success = 1;
      }
    }
    if (success)
      cur_entry->route_added = 0;
    
    return success;
}

/* Add route into kernel */
int route_add(ARPTAB_ENTRY* cur_entry)
{
    char routecmd_str[ROUTE_CMD_LEN];
    int success = 1;

    if (snprintf(routecmd_str, ROUTE_CMD_LEN-1, 
          "/sbin/ip route add %s/32 metric 50 dev %s scope link",
          inet_ntoa(cur_entry->ipaddr_ia), cur_entry->ifname) > ROUTE_CMD_LEN-1) 
    {
      syslog(LOG_INFO, "ip route command too large to fit in buffer!");
    } else {
      if (system(routecmd_str) != 0)
      { 
          syslog(LOG_INFO, "'%s' unsuccessful, will try to remove!", routecmd_str); 
          if (debug) printf("%s failed\n", routecmd_str);
          route_remove(cur_entry);
          success = 0;
      }
      else
      {
          if (debug) printf("%s success\n", routecmd_str);
          success = 1;
      }
    }
    if (success)
      cur_entry->route_added = 1;

    return success;
}


void processarp(int in_cleanup) 
{
    ARPTAB_ENTRY *cur_entry=*arptab, *prev_entry=NULL;

    /* First loop to remove unwanted routes */
    while (cur_entry != NULL) {
      if (debug && verbose) printf("Working on route %s(%s) tstamp %u want_route %d\n", inet_ntoa(cur_entry->ipaddr_ia), cur_entry->ifname, (int) cur_entry->tstamp, cur_entry->want_route);

      if ( !cur_entry->want_route
          || time(NULL) - cur_entry->tstamp > ARP_TABLE_ENTRY_TIMEOUT 
          || in_cleanup)  {
          
          if (cur_entry->route_added)
            route_remove(cur_entry);

          /* remove from arp list */
          if (debug) printf("Delete arp %s(%s)\n", inet_ntoa(cur_entry->ipaddr_ia), cur_entry->ifname);
            
          if (prev_entry != NULL) {
              prev_entry->next = cur_entry->next;
              free(cur_entry);
              cur_entry=prev_entry->next;
          } else {
              *arptab = cur_entry->next;
              free(cur_entry);
            cur_entry=*arptab;
            }
      } else {
          prev_entry = cur_entry;
          cur_entry = cur_entry->next;
      }     
    } /* while loop */

    /* Now loop to add new routes */
    cur_entry=*arptab;
    while (cur_entry != NULL) {
      if (time(NULL) - cur_entry->tstamp <= ARP_TABLE_ENTRY_TIMEOUT 
            && cur_entry->want_route
            && !cur_entry->route_added 
            && !in_cleanup) 
      {
          /* add route to the kernel */
          route_add(cur_entry);
      }
      cur_entry = cur_entry->next;
    } /* while loop */

}     

void parseproc()
{
    FILE *arpf;
    int firstline;
    ARPTAB_ENTRY *entry;
    char line[ARP_LINE_LEN];
    struct in_addr ipaddr;
    int incomplete=0;
    int i;
    char *ip, *mac, *dev, *hw, *flags, *mask;
    
    /* Parse /proc/net/arp table */
        
    if ((arpf = fopen(PROC_ARP, "r")) == NULL) {
      errstr = strerror(errno);
      syslog(LOG_INFO, "Error during ARP table open: %s", errstr);
    }

    firstline=1;

    while (!feof(arpf)) {
      
      if (fgets(line, ARP_LINE_LEN, arpf) == NULL) {
          if (!ferror(arpf))
            break;
          else {
            errstr = strerror(errno);
            syslog(LOG_INFO, "Error during ARP table open: %s", errstr);
          }
      } else {
          if (firstline) { firstline=0; continue; }
          if (debug && verbose) printf("read ARP line %s", line);

          incomplete=0;
                
          /* Incomplete ARP entries with MAC 00:00:00:00:00:00 */
          if (strstr(line, "00:00:00:00:00:00") != NULL) 
            incomplete=1;
            
          /* Incomplete entries having flag 0x0 */
          if (strstr(line, "0x0") != NULL)
            incomplete=1;
          
          ip=strtok(line, " ");

          if ((inet_aton(ip, &ipaddr)) == -1)
                syslog(LOG_INFO, "Error parsing IP address %s", ip);
                  
          /* if IP address is marked as undiscovered and does not exist in arptab,
             send ARP request to all ifaces */

          if (incomplete &! findentry(ipaddr) ) {
            if (debug)  printf("incomplete entry %s found, request on all interfaces\n", inet_ntoa(ipaddr));
            for (i=0; i <= last_iface_idx; i++)
                arp_req(ifaces[i], ipaddr, 0);
          }

          /* Hardware type */
          hw=strtok(NULL, " "); 
          
          /* flags */
          flags=strtok(NULL, " "); 

          /* MAC address */       
          mac=strtok(NULL, " ");

          /* Mask */
          mask=strtok(NULL, " "); 

          /* Device */
          dev=strtok(NULL, " ");

          if (dev[strlen(dev)-1] == '\n') { dev[strlen(dev)-1] = '\0'; }

          entry=replace_entry(ipaddr, dev);
          
          if (entry->incomplete != incomplete && debug)
            printf("change entry %s(%s) to incomplete=%d\n", ip, dev, incomplete);

          entry->ipaddr_ia.s_addr = ipaddr.s_addr;
          entry->incomplete       = incomplete;
          
          if (strlen(mac) < ARP_TABLE_ENTRY_LEN)
            strncpy(entry->hwaddr, mac, ARP_TABLE_ENTRY_LEN);
          else 
            syslog(LOG_INFO, "Error during ARP table parsing");
          
          if (strlen(dev) < ARP_TABLE_ENTRY_LEN) 
            strncpy(entry->ifname, dev, ARP_TABLE_ENTRY_LEN);
          else
            syslog(LOG_INFO, "Error during ARP table parsing");

          /* do not add routes for incomplete entries */
          if (debug && entry->want_route != !incomplete) 
            printf("%s(%s): set want_route %d\n", inet_ntoa(entry->ipaddr_ia), entry->ifname, !incomplete);
          entry->want_route   = !incomplete; 

            /* Remove route from kernel if it already exists through
               a different interface */
            if (entry->want_route)
            {
                if (remove_other_routes(entry->ipaddr_ia, entry->ifname) > 0)
                    if (debug) printf("Found ARP entry %s(%s), removed entries via other interfaces\n", inet_ntoa(entry->ipaddr_ia), entry->ifname);
            }

          time(&entry->tstamp);
          
          if (debug && !entry->route_added && entry->want_route) {
              printf("arptab entry: '%s' HWAddr: '%s' Dev: '%s' route_added:%d want_route:%d\n", 
                inet_ntoa(entry->ipaddr_ia), entry->hwaddr, entry->ifname, entry->route_added, entry->want_route);
          }
      }
    }

    if (fclose(arpf)) {
      errstr = strerror(errno);
      syslog(LOG_INFO, "Error during ARP table open: %s", errstr);
    }
}

void cleanup() 
{
    /* FIXME: I think this is a wrong way to do it ... */
    
    syslog(LOG_INFO, "Received signal; cleaning up.");
    /*
    for (i=0; i <= last_thread_idx; i++) {
      pthread_cancel(my_threads[i]);
    }
    */
    pthread_mutex_trylock(&arptab_mutex);
    processarp(1);
    syslog(LOG_INFO, "Terminating.");
    exit(1);
}

void sighandler()
{
    /* FIXME: I think this is a wrong way to do it ... */
    perform_shutdown=1;
}

void *main_thread()
{
    time_t last_refresh;

    signal(SIGINT, sighandler);
    signal(SIGTERM, sighandler);
    signal(SIGHUP, sighandler);
    
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
    pthread_cleanup_push(cleanup, NULL);
    while (1) {
      if (perform_shutdown) {
          pthread_exit(0);
      }
      pthread_testcancel();
        pthread_mutex_lock(&arptab_mutex);
        parseproc();
        processarp(0);
      pthread_mutex_unlock(&arptab_mutex);
      usleep(SLEEPTIME);
      if (!option_arpperm && time(NULL)-last_refresh > REFRESHTIME) {
          pthread_mutex_lock(&arptab_mutex);
          refresharp(*arptab);
          pthread_mutex_unlock(&arptab_mutex);
          time(&last_refresh);
      }
    }
    /* required since pthread_cleanup_* are implemented as macros */
    pthread_cleanup_pop(0);
}
    
int main (int argc, char **argv)
{
    pid_t child_pid;
    int i, help=1;
    
    progname = (char *) basename(argv[0]);
    
    for (i = 1; i < argc; i++) {
      if (!strcmp(argv[i],"-d")) { 
          debug=1;
          help=0;
      }
      else if (!strcmp(argv[i],"-p")) { 
          option_arpperm=1;
          help=0;
      }
      else if (!strcmp(argv[i],"-h") || !strcmp(argv[i],"--help")) {
          break;
      }
      else {
          last_iface_idx++;
          ifaces[last_iface_idx]=argv[i];
          help=0;
      }
    }

    if (help || last_iface_idx <= -1) {
          printf("parprouted: proxy ARP routing daemon, version %s.\n", VERSION);
          printf("(C) 2007 Vladimir Ivaschenko <vi@maks.net>, GPL2 license.\n");
          printf("Usage: parprouted [-d] [-p] interface [interface]\n");
          exit(1);
    }

    if (!debug) {
        /* fork to go into the background */
        if ((child_pid = fork()) < 0) {
            fprintf(stderr, "could not fork(): %s", strerror(errno));
            exit(1);
        } else if (child_pid > 0) {
            /* fork was ok, wait for child to exit */
            if (waitpid(child_pid, NULL, 0) != child_pid) {
                perror(progname);
                exit(1);
            }
            /* and exit myself */
            exit(0);
        }
        /* and fork again to make sure we inherit all rights from init */
        if ((child_pid = fork()) < 0) {
            perror(progname);
            exit(1);
        } else if (child_pid > 0)
            exit(0);

        /* create our own session */
        setsid();

        /* close stdin/stdout/stderr */
        close(0);
        close(1);
        close(2);

    }

    openlog(progname, LOG_PID | LOG_CONS | LOG_PERROR, LOG_DAEMON);
    syslog(LOG_INFO, "Starting.");

    signal(SIGINT, sighandler);
    signal(SIGTERM, sighandler);
    signal(SIGHUP, sighandler);

    if ((arptab = (ARPTAB_ENTRY **) malloc(sizeof(ARPTAB_ENTRY **))) == NULL) {
          errstr = strerror(errno);
          syslog(LOG_INFO, "No memory: %s", errstr);
    }
    
    *arptab = NULL;

    pthread_mutex_init(&arptab_mutex, NULL);
    pthread_mutex_init(&req_queue_mutex, NULL);
    
    if (pthread_create(&my_threads[++last_thread_idx], NULL, main_thread, NULL)) {
      syslog(LOG_ERR, "Error creating main thread.");
      abort();
    }

    for (i=0; i <= last_iface_idx; i++) {
      if (pthread_create(&my_threads[++last_thread_idx], NULL, (void *) arp, (void *) ifaces[i])) {
          syslog(LOG_ERR, "Error creating ARP thread for %s.",ifaces[i]);
          abort();
      }
      if (debug) printf("Created ARP thread for %s.\n",ifaces[i]);
    }
        
    if (pthread_join(my_threads[0], NULL)) {
      syslog(LOG_ERR, "Error joining thread.");
      abort();
    }

    while (waitpid(-1, NULL, WNOHANG)) { }
    exit(1);     
}

Generated by  Doxygen 1.6.0   Back to index