Linux 上でイーサネットのハブのようなふるまいをするプログラムをつくろうとすると,ソケットを promiscuous mode (プロミスキャス・モード) で動作させる必要がある. きちんとハブとして動作させようとするとやや複雑なプログラムを書く必要があるが,ここではもっとさぼって,ハブにちかいがもうすこしいいかげんに動作するプログラムをしめす.
以下のプログラムは 2 つの NIC (ネットワーク・インタフェース・カード) をもつ Linux マシン上で動作する. ひとつのインタフェースにとどいたパケットをもうひとつのインタフェースに転送する. パケットがインタフェースにとどくといっても,そのインタフェースに指定された IP アドレスや MAC アドレスが指定されたパケットだけをうけとるのではなく,そのインタフェースがつながっているイーサネット・リンク上のすべてのパケットが対象になる. このように自分 (インタフェース) にあてられた以外のパケットも受信するモードが promiscuous mode である. 以下のプログラムにはいくつかおまけの機能もくみこまれているが,いまはそれらについて解説しないまま,とりあえずプログラムをのせておく. あとでもうすこし整理しようとおもう.
/***
* A Repeater with Partial XUDP Processing for Linux
***/
#include <features.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <asm/types.h>
#include <sys/types.h>
#include <linux/if_packet.h>
#include <linux/if_ether.h>
#include <linux/if_arcnet.h>
#include <linux/version.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <signal.h>
#define MAX_PACKET_SIZE 2048
#define UDP 17
char *interface = NULL;
int fd = -1;
/**
* Open a socket for the network interface
*/
int open_socket(char *interface, int *rifindex) {
u_char buf[2048];
int addrlen = ETH_ALEN; // Ethernet address length (= 6)
int i;
int ifindex;
struct ifreq ifr;
struct sockaddr_ll sll;
fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP));
// fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
if (fd == -1) {
perror("socket");
_exit(1);
};
// get interface index
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interface, IFNAMSIZ);
if (ioctl(fd, SIOCGIFINDEX, &ifr) == -1) {
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_IP);
sll.sll_ifindex = ifindex;
if (bind(fd, (struct sockaddr *)&sll, sizeof(sll)) == -1) {
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) {
recv(fd, buf, i, 0);
};
printf("flushed\n");
} while (i);
return fd;
}
/**
* Print packet content in hex
*/
void hexdump(const u_char *p, int count) {
int i, j;
for (i = 0; i < count; i += 16) {
printf("%04x : ", i);
for (j = 0; j < 16 && i + j < count; j++)
printf("%2.2x ", p[i + j]);
for (; j < 16; j++) {
printf(" ");
};
printf(": ");
for (j = 0; j < 16 && i + j < count; j++) {
char c = toascii(p[i + j]);
printf("%c", isalnum(c) ? c : '.');
};
printf("\n");
};
}
/**
* Print an Ethernet address
*/
void print_ethaddr(const u_char *p) {
int i;
struct ethhdr *eh = (struct ethhdr *)p;
for (i = 0; i < 5; ++i) {
printf("%02x:", (int)eh->h_source[i]);
};
printf("%02x -> ", (int)eh->h_source[i]);
for (i = 0; i < 5; ++i) {
printf("%02x:", (int)eh->h_dest[i]);
};
printf("%02x", (int)eh->h_dest[i]);
printf("\n");
}
/**
* Dump a packet
*/
void dump_message(const u_char *buf, int count) {
if (count > 0) {
print_ethaddr(buf);
hexdump(buf, count);
};
}
/**
* Signal handler
*/
void sigint(int signum) {
struct ifreq ifr;
if (fd == -1) {
return;
};
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, interface, IFNAMSIZ);
ioctl(fd, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags &= ~IFF_PROMISC;
ioctl(fd, SIOCSIFFLAGS, &ifr);
close(fd);
_exit(0);
}
/**
* Receive a packet from the interface.
* Return the packet size (exclding Ethernet header).
*/
int getPacket(int interface, u_char *buf, int maxSize) {
fd = interface;
int count = recv(fd, buf, maxSize, 0);
printf("Count %d\n", count);
if (count < 0) {
perror("recv");
return -1;
};
dump_message(buf, count);
return count;
}
/**
* Send the message to the interface
*/
int putPacket(int interface, int interfaceIndex, u_char *buf, int count) {
struct sockaddr_ll sll;
memset(&sll, 0, sizeof(sll));
sll.sll_family = AF_PACKET;
sll.sll_protocol = htons(ETH_P_IP);
sll.sll_ifindex = interfaceIndex;
fd = interface;
int count1 = sendto(fd, buf, count, 0, (struct sockaddr *)&sll, sizeof(sll));
if (count1 == -1) {
perror("sendto");
};
printf("send %d bytes\n", count1);
dump_message(buf, count);
return count1;
}
/**
* Send an XUDP message and wait and send the original message
* to the interface.
*/
int processXUDP(int interface, int interfaceIndex, u_char *buf, int count) {
int oldType = buf[42];
int pid = fork();
if (pid < 0) { // error
perror("fork");
_exit(0);
} else if (pid == 0) { // child
sleep(1);
putPacket(interface, interfaceIndex, buf, count);
return;
} else { // parent
buf[42] = 0x85; // !! Modify the message !!
putPacket(interface, interfaceIndex, buf, count);
buf[42] = oldType;
};
}
int main(int argc, char **argv) {
u_char buf[MAX_PACKET_SIZE];
signal(SIGINT, sigint);
if (argc <= 2) {
printf("Usage: repeat ethM ethN [XUDPport]\n");
return;
};
printf("Copy packets from %s to %s\n", argv[1], argv[2]);
int ethindex1, ethindex2;
int eth1 = open_socket(argv[1], ðindex1);
printf("%s opened (%d)\n", argv[1], eth1);
int eth2 = open_socket(argv[2], ðindex2);
printf("%s opened (%d)\n", argv[2], eth2);
int port2check = 0;
if (argc > 3) {
port2check = atoi(argv[3]);
};
for (;;) {
int count = getPacket(eth1, buf, MAX_PACKET_SIZE);
if (count >= 40) {
int protocol = buf[23];
int sourcePort = (buf[34] << 8) + buf[35];
if (protocol == UDP && sourcePort == port2check) {
processXUDP(eth2, ethindex2, buf, count);
} else {
putPacket(eth2, ethindex2, buf, count);
};
};
};
}
