イーサネットフレーム転送プログラム(RAWソケットプログラム)
LinuxマシンにNICを複数刺して、ルータとして使うことはよくあると思います。
この場合、レイヤ3のIPパケットをLinuxが中継しているわけですが、
プログラムを書けば、レイヤ2のイーサネットフレームを中継することもできます。
イーサネットフレームの中継なので、基本的にはハブ(今時はスイッチングハブ)のように動作します。
市販のハブだとMACアドレスの学習とかしますが、ここでは単純に2つのインタフェース間でパケットを中継させる場合のプログラムを紹介します。
※イーサネットフレームの送受信プログラムは他のHPでも紹介されていて、以下のページを参考にしながら作成しました。
【手順】
1.RAWソケットの生成
PF_PACKET、SOCK_RAWを指定して、RAWソケットを生成します。
第3引数のprotocolは、受信するイーサネットタイプを指定します。ETH_P_ALLであれば、すべてのタイプを受信。ETH_P_IPであればIPパケットのみ。ETH_P_ARPであれば、ARPパケットのみ。指定できるタイプは、linux/if_ether.hに定義されています。
if((sockA = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 ){
perror("socket");
exit(0);
}
2.インタフェース番号の取得
eth1などのインタフェース名からインタフェース番号を取得します。
memset(&ifr, 0, sizeof(struct ifreq));
strcpy(ifr.ifr_name, argv[1]);
if(ioctl(sockA, SIOCGIFINDEX, &ifr) < 0 ){
perror("ioctl SIOCGIFINDEX");
exit(0);
}
3.bind
取得したインタフェース番号を使ってbindします。
sll_protocolはソケット生成時と同じものを指定する。
sockA_ifidx=ifr.ifr_ifindex;
sa.sll_family=AF_PACKET;
sa.sll_protocol=htons(ETH_P_ALL);
sa.sll_ifindex=ifr.ifr_ifindex;
if(bind(sockA, (struct sockaddr *)&sa, sizeof(sa)) < 0){
//Error
perror("bind 1");
close(sockA);
exit(0);
}
4.プロミスキャスモードの指定(必要であれば)
自インタフェース宛以外のパケットも受信する場合に設定します。
mreq.mr_type = PACKET_MR_PROMISC;
mreq.mr_ifindex = ifr.ifr_ifindex;
mreq.mr_alen = 0;
mreq.mr_address[0] ='\0';
if(setsockopt(sockB, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
5.パケットの受信
パケットの受信はUDPと同様にrecvfrom関数で行います。
6.パケットの転送
受信したパケットをインタフェースを指定して、sendtoで送信すればいいのですが、この時、自ホストから送信されてループバックしてきたパケットは無視したいので、パケットタイプがPACKET_OUTGOINGになっているものを落とします。
以下、ソースコードです。
ソースコード
/*
* Ethernet Frame Forwarding Program (L2forwarder.c)
*
* gcc L2Forwarder.c -o L2Forwarder
*
* Takuya Tojo 2011
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define max(a, b) ((a) > (b) ? (a) : (b))
#define PRINT_MAC_ADDR(ether_addr_octet) { \
int i; \
for ( i =0; i < 6; i++){ \
printf("%02x",(ether_addr_octet[i])); \
if(i != 5) \
printf(":"); \
} \
}
struct ethhdr_vlan
{
unsigned char h_dest[6]; /* destination eth addr */
unsigned char h_source[6]; /* source ether addr */
unsigned int h_vlan; /* VLAN */
unsigned short h_proto; /* packet type ID field */
};
void print_header(char *frame);
void forward_frame();
int sockB;
int sockA;
int sockA_ifidx;
int sockB_ifidx;
//#define DEBUG
//#define DEBUG_IP_LEVEL
//#define DEBUG_TCP_LEVEL
//Frame forwarding procedure
void forward_frame()
{
int rll_size;
struct sockaddr_ll rll;
fd_set fds, readfds;
char buffer[8000];
ssize_t frame_len, send_len;
struct sockaddr_ll sll;
int max_fd;
FD_ZERO(&readfds);
FD_SET(sockA, &readfds);
FD_SET(sockB, &readfds);
max_fd = max(sockA, sockB) + 1;
while(1){
memcpy(&fds, &readfds, sizeof(fd_set));
if(select(max_fd, &fds , NULL, NULL, NULL) < 0 ){
perror("select");
exit(0);
}
// Check the buffer of sockA
if(FD_ISSET(sockA, &fds)){
memset(&rll, 0, sizeof(rll));
rll_size = sizeof(rll);
// Read one frame from sockA
if((frame_len=recvfrom(sockA, &buffer, sizeof(buffer), 0, (struct sockaddr *)&rll, &rll_size))<0){
perror("recvfrom");
exit(1);
}
// Forward the frame if the frame is not sent by the same interface
if(rll.sll_pkttype!=PACKET_OUTGOING){
memset(&sll, 0, sizeof(sll));
sll.sll_family=AF_PACKET;
sll.sll_halen=ETH_ALEN;
sll.sll_ifindex = sockB_ifidx;
if((send_len=sendto(sockB, &buffer, frame_len, 0, (struct sockaddr *)&sll, sizeof(sll)))<0){
perror("send sockB :");
exit(1);
}else{
#ifdef DEBUG
printf("forward %d bytes to IF%d\n", send_len, sockB_ifidx);
#endif
}
}
}
// Check the buffer of sockB
if(FD_ISSET(sockB, &fds)){
memset(&rll, 0, sizeof(rll));
rll_size = sizeof(rll);
// Read one frame from sockB
if((frame_len=recvfrom(sockB, &buffer, sizeof(buffer), 0, (struct sockaddr *)&rll, &rll_size))<0){
perror("recvfrom");
exit(1);
}
// Forward the frame if the frame is not sent by the same interface
if(rll.sll_pkttype!=PACKET_OUTGOING){
memset(&sll, 0, sizeof(sll));
sll.sll_family=AF_PACKET;
sll.sll_halen=ETH_ALEN;
sll.sll_ifindex = sockA_ifidx;
if((send_len=sendto(sockA, &buffer, frame_len, 0, (struct sockaddr *)&sll, sizeof(sll)))<0){
perror("send AockA");
exit(1);
}else{
#ifdef DEBUG
printf("forward %d bytes to IF%d\n", send_len, sockA_ifidx);
#endif
}
}
}
#ifdef DEBUG
printf("FromInterface = %d\n", rll.sll_ifindex);
printf("PacketType = %d\n", rll.sll_pkttype);
printf("HeaderType = %d\n", rll.sll_hatype);
printf("Frame Size = %d\n", frame_len);
print_header(&buffer);
#endif
}
}
void print_header(char *frame)
{
struct ethhdr *p_ether;
struct iphdr *p_ip;
struct tcphdr *p_tcp;
struct in_addr insaddr,indaddr;
p_ether=(struct ethhdr *)frame;
p_ip=(struct iphdr *)(frame + sizeof(struct ethhdr));
p_tcp=(struct tcphdr *)(frame + sizeof(struct ethhdr) + sizeof(struct iphdr));
printf("Ether SrcAdr = ");
PRINT_MAC_ADDR(p_ether->h_source);
puts("");
printf("Ether DstAdr = ");
PRINT_MAC_ADDR(p_ether->h_dest);
puts("");
printf("Ether Type = %d\n", ntohs(p_ether->h_proto));
insaddr.s_addr = p_ip->saddr;
indaddr.s_addr = p_ip->daddr;
/*
char *tmp;
unsigned int t;
tmp=(char *)&buffer;
for(t=0;t<30;t++)
printf("%d, %x\n", t, tmp[t]);
tmp=(char *)p_ip;
puts("--------------");
for(t=0;t<20;t++)
printf("%d, %x\n", t, tmp[t]);
exit(1);
*/
#ifdef DEBUG_IP_LEVEL
printf("----IP Header--------------------\n");
printf("version : %u\n",p_ip->version);
printf("ihl : %u\n",p_ip->ihl);
printf("tos : %u\n",p_ip->tos);
printf("tot length : %u\n",ntohs(p_ip->tot_len));
printf("id : %u\n",ntohs(p_ip->id));
printf("frag_off : %u\n",p_ip->frag_off & 8191);
printf("ttl : %u\n",p_ip->ttl);
printf("protocol : %u\n",p_ip->protocol);
printf("check : 0x%x\n",ntohs(p_ip->check));
printf("saddr : %s\n",inet_ntoa(insaddr));
printf("daddr : %s\n",inet_ntoa(indaddr));
#ifdef DEBUG_TCP_LEVEL
if(p_ip->protocol == IPPROTO_TCP){
printf("----TCP Header-------------------\n");
printf("source port : %u\n",ntohs(p_tcp->source));
printf("dest port : %u\n",ntohs(p_tcp->dest));
printf("sequence : %u\n",ntohl(p_tcp->seq));
printf("ack seq : %u\n",ntohl(p_tcp->ack_seq));
printf("frags :");
p_tcp->fin ? printf(" FIN") : 0 ;
p_tcp->syn ? printf(" SYN") : 0 ;
p_tcp->rst ? printf(" RST") : 0 ;
p_tcp->psh ? printf(" PSH") : 0 ;
p_tcp->ack ? printf(" ACK") : 0 ;
p_tcp->urg ? printf(" URG") : 0 ;
printf("\n");
printf("window : %u\n",ntohs(p_tcp->window));
printf("check : 0x%x\n",ntohs(p_tcp->check));
printf("urt_ptr : %u\n\n\n",p_tcp->urg_ptr);
}
#endif
#endif
}
main(int argc, char *argv[]) {
struct ifreq ifr;
struct packet_mreq mreq;
struct sockaddr_ll sa;
if(argc < 3){
printf("Usage: %s interface1 interface2\n", argv[0]);
printf("Exmaple: %s eth0 eth2\n", argv[0]);
exit(1);
}
// SocketA
if((sockA = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 ){
perror("socket");
exit(0);
}
// Bind
memset(&ifr, 0, sizeof(struct ifreq));
strcpy(ifr.ifr_name, argv[1]);
if(ioctl(sockA, SIOCGIFINDEX, &ifr) < 0 ){
perror("ioctl SIOCGIFINDEX");
exit(0);
}
sockA_ifidx=ifr.ifr_ifindex;
sa.sll_family=AF_PACKET;
sa.sll_protocol=htons(ETH_P_ALL);
sa.sll_ifindex=ifr.ifr_ifindex;
if(bind(sockA, (struct sockaddr *)&sa, sizeof(sa)) < 0){
//Error
perror("bind 1");
close(sockA);
exit(0);
}
// Set promiscuous mode
mreq.mr_type = PACKET_MR_PROMISC;
mreq.mr_ifindex = ifr.ifr_ifindex;
mreq.mr_alen = 0;
mreq.mr_address[0] ='\0';
if(setsockopt(sockA, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
// SocketB
if ((sockB = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 ){
perror("socket");
exit(0);
}
// Bind
memset(&ifr, 0, sizeof(struct ifreq));
strcpy(ifr.ifr_name, argv[2]);
if(ioctl(sockB, SIOCGIFINDEX, &ifr) < 0 ){
perror("ioctl SIOCGIFINDEX");
exit(0);
}
sockB_ifidx=ifr.ifr_ifindex;
sa.sll_family=AF_PACKET;
sa.sll_protocol=htons(ETH_P_ALL);
sa.sll_ifindex=ifr.ifr_ifindex;
if(bind(sockB, (struct sockaddr *)&sa, sizeof(sa)) < 0){
//Error
perror("bind 0");
close(sockB);
exit(0);
}
// Set promiscuous mode
mreq.mr_type = PACKET_MR_PROMISC;
mreq.mr_ifindex = ifr.ifr_ifindex;
mreq.mr_alen = 0;
mreq.mr_address[0] ='\0';
if(setsockopt(sockB, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
printf("Interface1 (SockA) : %s (index %d)\n", argv[1], sockA_ifidx);
printf("Interface2 (SockB) : %s (index %d)\n", argv[2], sockB_ifidx);
forward_frame();
}
(ファイル)
(参考)
上記ソースはLinuxで動作します。Solaris版については、下記URLにて同様のプログラムが紹介されています。
2011.4.17更新
この場合、レイヤ3のIPパケットをLinuxが中継しているわけですが、
プログラムを書けば、レイヤ2のイーサネットフレームを中継することもできます。
イーサネットフレームの中継なので、基本的にはハブ(今時はスイッチングハブ)のように動作します。
市販のハブだとMACアドレスの学習とかしますが、ここでは単純に2つのインタフェース間でパケットを中継させる場合のプログラムを紹介します。
※イーサネットフレームの送受信プログラムは他のHPでも紹介されていて、以下のページを参考にしながら作成しました。
【手順】
1.RAWソケットの生成
PF_PACKET、SOCK_RAWを指定して、RAWソケットを生成します。
第3引数のprotocolは、受信するイーサネットタイプを指定します。ETH_P_ALLであれば、すべてのタイプを受信。ETH_P_IPであればIPパケットのみ。ETH_P_ARPであれば、ARPパケットのみ。指定できるタイプは、linux/if_ether.hに定義されています。
if((sockA = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0 ){
perror("socket");
exit(0);
}
2.インタフェース番号の取得
eth1などのインタフェース名からインタフェース番号を取得します。
memset(&ifr, 0, sizeof(struct ifreq));
strcpy(ifr.ifr_name, argv[1]);
if(ioctl(sockA, SIOCGIFINDEX, &ifr) < 0 ){
perror("ioctl SIOCGIFINDEX");
exit(0);
}
3.bind
取得したインタフェース番号を使ってbindします。
sll_protocolはソケット生成時と同じものを指定する。
sockA_ifidx=ifr.ifr_ifindex;
sa.sll_family=AF_PACKET;
sa.sll_protocol=htons(ETH_P_ALL);
sa.sll_ifindex=ifr.ifr_ifindex;
if(bind(sockA, (struct sockaddr *)&sa, sizeof(sa)) < 0){
//Error
perror("bind 1");
close(sockA);
exit(0);
}
4.プロミスキャスモードの指定(必要であれば)
自インタフェース宛以外のパケットも受信する場合に設定します。
mreq.mr_type = PACKET_MR_PROMISC;
mreq.mr_ifindex = ifr.ifr_ifindex;
mreq.mr_alen = 0;
mreq.mr_address[0] ='\0';
if(setsockopt(sockB, SOL_PACKET, PACKET_ADD_MEMBERSHIP, (void *)&mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
5.パケットの受信
パケットの受信はUDPと同様にrecvfrom関数で行います。
6.パケットの転送
受信したパケットをインタフェースを指定して、sendtoで送信すればいいのですが、この時、自ホストから送信されてループバックしてきたパケットは無視したいので、パケットタイプがPACKET_OUTGOINGになっているものを落とします。
以下、ソースコードです。
ソースコード
(ファイル)
(参考)
上記ソースはLinuxで動作します。Solaris版については、下記URLにて同様のプログラムが紹介されています。
2011.4.17更新
トラックバック(0)
トラックバックURL: http://kirihari.net/mt/mt-tb.cgi/3
コメントする