[ Top page ]

« Connection check program for non IP network environment | Main | Two types of GRE tunneling configurations »

Network and communication

Software-based Ethernet switch

Although we can buy a high-performance Ethernet switch at 10 or 20 dollars, a programmable (software) switch is convenient for experimental purpose such as observing the learning steps. The following program is a Ethernet switching program with learning function but not completely conforming to the Ethernet standard.

The usage is as follows.

./ethernode <FirstInterface> <LastInterface>

or

./ethernode <FirstInterface> <LastInterface> <PrintLevel>

Because the PC is used as a switch, it must have at least three network interfaces. If the specified range is 1..3, the names of the interfaces are eth1, eth2, and eth3. It is a good idea to reserve eth0 for control purpose. PrintLevel is used for controlling messaging on packet transmission, learning, and packet contents. If 0 is specified, nothing is outputted. If 1 to 4 is specified, messages are outputted. (See the program for detail.)

/***
 *
 * Software-based Ethernet Switch
 *
 * Coded by Yasusi Kanada
 * Ver 0.1  2011-1-11	Initial version
 * Ver 1.0  2011-1-16   48-bit ID splitted
 * Ver 1.1  2011-2-23   Learning/traffic statistics function introduced
 * Ver 1.11 2011-2-24   Debugged and main-loop optimized
 * Ver 1.12 2012-5-20   Cleaned
 *
 ***/

#include "Ether.h"
#include <fcntl.h>
#include <sys/time.h>

#define bool		int32_t
#define true		1
#define false		0

// #define DEBUG		0

#define MAX_PACKET_SIZE	2048
// Sufficiently larger than the MTU

// Timeout time in usec
#define LearningTimeout  15000000
#define ReferenceTimeout 60000000

#define MaxInterfaces 5

int32_t FirstInterface = 1;
int32_t LastInterface = 3;

int32_t fd[MaxInterfaces];
int32_t ifindex[MaxInterfaces];

int32_t displayLevel = 0;
/* displayLevel = 0: no display mode
   displayLevel = 1: statistics-only mode
   displayLevel = 2: learning-table dump mode
   displayLevel = 3: debug mode
   displayLevel = 4: louder debug mode
*/

int32_t numLearned = 0;
int32_t numInput   = 0;
int32_t numOutput  = 0;

extern void _exit(int32_t);

time_t time(time_t *timer);


/**
 * Open a socket for the network interface
 */
int32_t open_socket(int32_t index, int32_t *rifindex) {
  unsigned char buf[MAX_PACKET_SIZE];
  int32_t i;
  int32_t ifindex;
  struct ifreq ifr;
  struct sockaddr_ll sll;
  unsigned char interface[IFNAMSIZ];
  strncpy(interface, "ethX", IFNAMSIZ);
  interface[3] = '0' + index;

  int32_t fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
  if (fd == -1) {
    printf("%s - ", interface);
    perror("socket");
    _exit(1);
  };

  // get interface index
  memset(&ifr, 0, sizeof(ifr));
  strncpy(ifr.ifr_name, interface, IFNAMSIZ);
  if (ioctl(fd, SIOCGIFINDEX, &ifr) == -1) {
    printf("%s - ", interface);
    perror("SIOCGIFINDEX");
    _exit(1);
  };
  ifindex = ifr.ifr_ifindex;
  *rifindex = ifindex;

  // set promiscuous mode
  memset(&ifr, 0, sizeof(ifr));
  strncpy(ifr.ifr_name, interface, IFNAMSIZ);
  ioctl(fd, SIOCGIFFLAGS, &ifr);
  ifr.ifr_flags |= IFF_PROMISC;
  ioctl(fd, SIOCSIFFLAGS, &ifr);

  memset(&sll, 0xff, sizeof(sll));
  sll.sll_family = AF_PACKET;
  sll.sll_protocol = htons(ETH_P_ALL);
  sll.sll_ifindex = ifindex;
  if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) == -1) {
    printf("%s - ", interface);
    perror("bind");
    _exit(1);
  };

  /* flush all received packets. 
   *
   * raw-socket receives packets from all interfaces
   * when the socket is not bound to an interface
   */
  do {
    fd_set fds;
    struct timeval t;
    FD_ZERO(&fds);	
    FD_SET(fd, &fds);
    memset(&t, 0, sizeof(t));
    i = select(FD_SETSIZE, &fds, NULL, NULL, &t);
    if (i > 0) {
      ssize_t size = recv(fd, buf, MAX_PACKET_SIZE, 0);
      if (displayLevel == 4 && size > 0) printf("interface %d flushed\n", ifindex);
    };
  } while (i);
  if (displayLevel >= 4) {
    printf("%s opened (fd=%d interface=%d)\n", interface, fd, ifindex);
  }

  return fd;
}


/**
 * Print IPEC packet content
 */
void printPacket(EtherPacket *packet, ssize_t packetSize, char *message) {
  printf("%s from x%1x:x%1x to x%1x:x%1x\n",
	 message, ntohs(packet->srcMAC1), ntohl(packet->srcMAC2),
	 ntohs(packet->destMAC1), ntohl(packet->destMAC2));
}


/**
 * Forwarding-table Handler
 */

/* Forwarding table entry data structure */
struct ForwardingTable {
  time_t tv_sec;	// timestamp 'sec'-part
  suseconds_t tv_usec;	// timestamp 'usec'-part
  uint32_t idh;
  uint32_t idl;
  int32_t ifnum;
  unsigned short age;
} __attribute__((packed));

#define FWDTBLSIZE 1000

struct ForwardingTable fwdtbl[FWDTBLSIZE];

/* Initialize forwarding table */
void initfwdtbl() {
  int32_t i;
  for (i = 0; i < FWDTBLSIZE; i++) {
    fwdtbl[i].idh = 0;
    fwdtbl[i].idl = 0;
  }
}

/* Dump a forwarding table entry */
void dumpfwdtbl(int32_t index, char *message) {
  printf("%s index=%d id=x%04x%08x ifnum=eth%d age=%d timestamp=%d.%d\n",
	 message, index, (uint16_t)fwdtbl[index].idh,
	 (uint32_t)fwdtbl[index].idl, fwdtbl[index].ifnum,
	 fwdtbl[index].age, (int32_t)fwdtbl[index].tv_sec, (int32_t)fwdtbl[index].tv_usec);
}

/* Snapshot the forwarding table */
void snapfwdtbl() {
  int32_t i;
  printf("  Learned addresses:\n");
  for (i = 1; i < FWDTBLSIZE; i++) {
    if (fwdtbl[i].idh != 0 || fwdtbl[i].idl != 0) {
      printf("  %2d %04x%08x eth%d\n",
	     i, fwdtbl[i].idh, fwdtbl[i].idl, fwdtbl[i].ifnum);
    } else {
      return;
    }
  }
}

/* Search the forwarding table (linear search used currently) */
int32_t getfwdtbl(uint32_t idh, uint32_t idl) {
  // int32_t getfwdtbl(uint64_t id) {
  int32_t i;
  for (i = 1; i < FWDTBLSIZE; i++) {	// (fwdtbl[0] not used)
    if (fwdtbl[i].idh == idh && fwdtbl[i].idl == idl) {		// already registered
      return i;
    } else if (fwdtbl[i].idh == 0 && fwdtbl[i].idl == 0) {	// not yet registered
      return -i;
    }      
  }
  return 0;	// Table full!
}

/* Fill a forwarding table entry */
int32_t setfwdtbl(EtherPacket *packet, int32_t index, int32_t srcIfnum) {
  struct timeval tv;
  struct timezone tz;
  fwdtbl[index].idh = ntohs(packet->srcMAC1);
  fwdtbl[index].idl = ntohl(packet->srcMAC2);
  fwdtbl[index].ifnum = srcIfnum;
  gettimeofday(&tv, &tz);
  fwdtbl[index].tv_sec = tv.tv_sec;
  fwdtbl[index].tv_usec = tv.tv_usec;

  if (displayLevel >= 3) dumpfwdtbl(index, "\nNewly learned:");
  return index;
}

/* Return true iff the forwarding table entry is timed-out. */
int32_t timedOut(struct timeval now, struct ForwardingTable fwdentry,
	     suseconds_t timeoutTime) {
  double sec = (now.tv_sec - fwdentry.tv_sec) +
    ((double)(now.tv_usec - fwdentry.tv_usec - timeoutTime)) / 1000000;
  if (displayLevel >= 4) {
    printf("%s: now %d.%d fwdtbl %d.%d timeout %d msec %d\n",
	   sec > 0 ? "Timed out" : "Not timed out",
	   (int32_t)now.tv_sec, (int32_t)now.tv_usec, 
	   (int32_t)fwdentry.tv_sec, (int32_t)fwdentry.tv_usec,
	   (int32_t)timeoutTime, (int32_t)(1000 * sec));
  }
  return sec > 0;
}


/**
 * Forward a packet
 */
int32_t forward(EtherPacket *packet, ssize_t sizein, int32_t srcIfnum) {
  struct timeval now;
  struct timezone tz;
  gettimeofday(&now, &tz);	// get current time

  int32_t ifnum;
  if (displayLevel >= 3) {
    printPacket(packet, sizein, " ");
  }
  int32_t index = getfwdtbl(ntohs(packet->srcMAC1), ntohl(packet->srcMAC2));
  if (index == 0) {		// Table full!
    _exit(1);
  } else if (index < 0) {	// source not yet learned - learn
    setfwdtbl(packet, -index, srcIfnum);
    numLearned++;	// 2011-2-23
  } else {			// already learned
    if (displayLevel >= 4) dumpfwdtbl(index, "Before:");
    if (timedOut(now, fwdtbl[index], LearningTimeout)) { // timeout (_sec?!)
     if (displayLevel >= 3) {
	printf("Updated! if: %d -> %d, timestamp: %d.%d -> %d.%d\n",
	       fwdtbl[index].ifnum, srcIfnum,
	       (int32_t)fwdtbl[index].tv_sec, (int32_t)fwdtbl[index].tv_usec,
	       (int32_t)now.tv_sec, (int32_t)now.tv_usec);
      }
      fwdtbl[index].ifnum = srcIfnum;
      fwdtbl[index].tv_sec = now.tv_sec;
      fwdtbl[index].tv_usec = now.tv_usec;
    }
  }

  struct sockaddr_ll sll;
  memset(&sll, 0, sizeof(sll));
  sll.sll_family = AF_PACKET;
  sll.sll_protocol = htons(ETH_P_ALL);

  uint32_t destMAC1 = ntohs(packet->destMAC1);
  uint32_t destMAC2 = ntohl(packet->destMAC2);
  int32_t destIndex = getfwdtbl(destMAC1, destMAC2);
  if (destIndex < 0 ||	// next-hop interface not known
      timedOut(now, fwdtbl[destIndex], ReferenceTimeout)) {  // timeout (_sec?!)
    // flood packet
    if (displayLevel >= 3) {
	printf("@@@@@@@@\n@ FLOOD packet @ (dest=%x:%x)\n@@@@@@@@\n",
	       destMAC1, destMAC2);
    }
    for (ifnum = FirstInterface; ifnum <= LastInterface; ifnum++) {
      if (srcIfnum != ifnum) {
	sll.sll_ifindex = ifindex[ifnum];
	ssize_t sizeout = sendto(fd[ifnum], packet, sizein, 0,
				 (struct sockaddr *)&sll, sizeof(sll));
	if (sizeout < 0) {
	  perror("sendto");
	} else {
	  if (displayLevel >= 4) printf("%d bytes sent through eth%d\n", sizeout, ifnum);
	}
      }
    }
  } else {
    int32_t destIfnum = fwdtbl[destIndex].ifnum;
    if (displayLevel >= 3) {
      printf("@@@@@@@@@\n@ SWITCH packet @ (dest=%x:%x) to eth%d\n@@@@@@@@@\n",
	     destMAC1, destMAC2, destIfnum);
    }
    sll.sll_ifindex = ifindex[destIfnum];
    ssize_t sizeout = sendto(fd[destIfnum], packet, sizein, 0,
			     (struct sockaddr *)&sll, sizeof(sll));
    if (sizeout < 0) {
      perror("sendto");
    } else {
      if (displayLevel >= 4) printf("%d bytes sent through eth%d\n", sizeout, destIfnum);
    }
  }
}


/**
 * Main program
 */
int32_t main(int32_t argc, char **argv) {
  unsigned char buf[MAX_PACKET_SIZE];
  int32_t ifnum;	// interface number (different from ifindex)
  int32_t count = 0;
  time_t lastTime, timer;

  if (++count < argc) {
    FirstInterface = atoi(argv[count]);		// Min interface #
  }
  if (++count < argc) {
    LastInterface = atoi(argv[count]);		// Max interface #
  }
  if (FirstInterface < 0 || LastInterface < FirstInterface ||
      MaxInterfaces < LastInterface) {
    printf("Interface range (%d..%d) must be between 0..%d\n",
	   FirstInterface, LastInterface, MaxInterfaces);
  }
  if (LastInterface < FirstInterface + 2) {
    printf("Three or more interfaces are required for switching\n");
  }

  if (++count < argc) {				// Debug print
    displayLevel = atoi(argv[count]);
  }

  // Open raw sockets and initialize for sending packets
  for (ifnum = FirstInterface; ifnum <= LastInterface; ifnum++) {
    fd[ifnum] = open_socket(ifnum, &ifindex[ifnum]);

    // Set non-blocking mode:
    int32_t flags = fcntl(fd[ifnum], F_GETFL, 0);
    fcntl(fd[ifnum], F_SETFL, O_NONBLOCK | flags);
  }

  initfwdtbl();

  lastTime = time(&timer);

  // Forwarding operation
  for (;;) {
    for (ifnum = FirstInterface; ifnum <= LastInterface; ifnum++) {
      ssize_t sizein = recv(fd[ifnum], buf, MAX_PACKET_SIZE, 0);
      if (sizein >= 0) {
	if (displayLevel >= 3) {
	  printf("\nReceived through eth%d (%d bytes)\n", ifnum, sizein);
	}
	forward((EtherPacket*)buf, sizein, ifnum);
	count++;

	if (displayLevel >= 1) {
	  time_t now = time(&timer);
	  if (now >= lastTime + 1) {
	    printf("\n\n");
	    if (numLearned > 2) {
	      printf("Learning: %3d times\n", numLearned);
	    } else if (numLearned == 2) {
	      printf("Learning: Twice\n");
	    } else {
	      printf("Learning: Once\n");
	    }
	    if (displayLevel == 2) {
	      snapfwdtbl();
	    }
	    printf("External packets:  RX %4d  TX %4d\n", numInput, numOutput);
	    lastTime = now;
	  }
	}
      }
    }
  }
}

The content of Ether.h used in this program is as follows.

/***
 *
 * Software-based Ethernet Common Header
 *
 * Coded by Yasusi Kanada
 * Ver 0.2  2011-1-11   Initial version
 *
 ***/

#include <linux/if_packet.h>
#include <linux/if_ether.h> 
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <stdio.h>
#include <elf.h>
#include <string.h>

// 0       6      12   14
// +-------+-------+----+---------------+
// | DMAC  | SMAC  |Type|    Payload    |
// +-------+-------+----+---------------+

struct _EtherHeader {
  uint16_t destMAC1;
  uint32_t destMAC2;
  uint16_t srcMAC1;
  uint32_t srcMAC2;
#ifdef VLAN
  uint32_t VLANTag;
#endif
  uint16_t type;
  int32_t  payload;
} __attribute__((packed));

typedef struct _EtherHeader EtherPacket;
Keywords:

TrackBack

TrackBack URL for this entry:
https://www.kanadas.com/mt/mt-tb.cgi/5633

Post a comment

About

This page contains a single entry from the blog posted on May 20, 2012 5:55 PM.

Many more can be found on the main index page or by looking through the archives.

Creative Commons License
This weblog is licensed under a Creative Commons License.
Powered by Movable Type