sendmsg(),recvmsg()を使ったUDP送受信プログラム:つづき
昨日に続き、sendmsg(),recvmsg()を研究(?)しました。マニアックな内容なのに意外とアクセス数が多いので・・・。
・BSD系でも使いたい
とりあえず私が常用しているMacOSX(SnowLeopard)で試行錯誤したのですが、OSXにはIP_PKTINFOがないので、IP_RECVDSTADDRとIP_RECVIFを使います。いちおうこれで受信に関してはどのインターフェースで受信したか、どのIPアドレス宛であったかは取得できます。
しかし、送信はうまくいきませんでした。IP_RECVDSTADDRやIP_RECVIFを受信したデータと同一にして応答する感じしても、送信するインターフェースやIPアドレスを使い分けることはできませんでした。OSXでは、送信はIPアドレスをbind()してやらないと無理かも知れません。
ちなみに、SolarisではIP_PKTINFOもIP_RECVDSTADDRもIP_RECVIFも使えるので、IP_PKTINFOを使わずに、IP_RECVDSTADDRやIP_RECVIFを使って確認してみたのですが、やはりうまくいきませんでした。もともと「RECV」と名前がついていますし、マニュアルなどを調べても「受信」に関する事しか書いてないので、送信には関係ないのかも知れません。
・Solarisでも使いたい
こちらはなんと、IP_PKTINFO、IP_RECVDSTADDR、IP_RECVIF全て使うことができます。ただし、_XPG4_2と__EXTENSIONS__をデファインする必要があります。。ただし、ブロードキャストを受信した場合に一部違う値になります。
IP_PKTINFOを使えるので、送信もLinuxと同様に使えます。
Linuxでの受信結果
「
eth0 192.168.0.13
eth0:1 192.168.0.3
1:lo
2:eth1
3:eth0
4:sit0
IP_PKTINFO:on
*************************
buf=[192.168.0.13:44444] <- eth0宛
from 192.168.0.215:32858
pktinfo->ipi_ifindex=3
pktinfo->ipi_spec_dst=192.168.0.13
pktinfo->ipi_addr=192.168.0.13
*************************
*************************
buf=[192.168.0.3:44444] <- eth0:1宛
from 192.168.0.215:32858
pktinfo->ipi_ifindex=3
pktinfo->ipi_spec_dst=192.168.0.3
pktinfo->ipi_addr=192.168.0.3
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.215:32858
pktinfo->ipi_ifindex=3
pktinfo->ipi_spec_dst=192.168.0.13 <- Solarisと異なる
pktinfo->ipi_addr=192.168.0.255
*************************
」
BSDで受信
「
en0 192.168.0.229
en5 192.168.0.235
1:lo0
2:gif0
3:stf0
4:en0
5:en2
6:en3
9:en5
IP_RECVDSTADDR:on
IP_RECVIF:on
*************************
buf=[192.168.0.229:44444] <- en0宛
from 192.168.0.13:38930
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.235:44444] <- en5宛
from 192.168.0.13:56830
dstaddr=192.168.0.235
sdl->sdl_index=9
sdl->data=en5
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.13:56830
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
」
Solarisで受信
「
bge0 192.168.0.215
bge0:1 192.168.0.213
bge1 192.168.0.214
1:lo0
2:bge0
3:bge1
IP_PKTINFO:on
IP_RECVDSTADDR:on
IP_RECVIF:on
*************************
buf=[192.168.0.213:44444] <- bge0:1宛
from 192.168.0.13:35645
pktinfo->ipi_ifindex=2
pktinfo->ipi_spec_dst=192.168.0.214 <- 不思議
pktinfo->ipi_addr=192.168.0.213
dstaddr=192.168.0.213
sdl->sdl_index=0
sdl->data=
*************************
*************************
buf=[192.168.0.214:44444] <- bge1宛
from 192.168.0.13:35645
pktinfo->ipi_ifindex=3
pktinfo->ipi_spec_dst=192.168.0.214
pktinfo->ipi_addr=192.168.0.214
dstaddr=192.168.0.214
sdl->sdl_index=0
sdl->data=
*************************
*************************
buf=[192.168.0.215:44444] <- bge0宛
from 192.168.0.13:35645
pktinfo->ipi_ifindex=2
pktinfo->ipi_spec_dst=192.168.0.215
pktinfo->ipi_addr=192.168.0.215
dstaddr=192.168.0.215
sdl->sdl_index=0
sdl->data=
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.13:35645
pktinfo->ipi_ifindex=2
pktinfo->ipi_spec_dst=0.0.0.0
pktinfo->ipi_addr=192.168.0.255 <- Linuxと異なる
dstaddr=192.168.0.255
sdl->sdl_index=0
sdl->data=
*************************
」
送信の動作確認(Linux、Solarisのみ)
「
/// from linux ///
//// ./send eth0 192.168.0.13
*************************
buf=[192.168.0.229:44444]
from 192.168.0.13:48628 <- 192.168.0.13から届いている
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.13:38863 <- 192.168.0.13から届いている
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
//// ./send eth0:1 192.168.0.3
*************************
buf=[192.168.0.229:44444]
from 192.168.0.3:50638 <- 192.168.0.3から届いている
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.3:36112 <- 192.168.0.3から届いている
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
/// from solaris ///
//// ./send bge0 192.168.0.215
*************************
buf=[192.168.0.229:44444]
from 192.168.0.215:32869 <- 192.168.0.215から届いている
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.215:32877 <- 192.168.0.215から届いている
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
//// ./send bge0:1 192.168.0.213
*************************
buf=[192.168.0.229:44444]
from 192.168.0.213:32870 <- 192.168.0.213から届いている
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.213:32878 <- 192.168.0.213から届いている
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
//// ./send bge1 192.168.0.214
*************************
buf=[192.168.0.229:44444]
from 192.168.0.214:32871 <- 192.168.0.214から届いている
dstaddr=192.168.0.229
sdl->sdl_index=4
sdl->data=en0
*************************
*************************
buf=[192.168.0.255:44444] <- ブロードキャスト宛
from 192.168.0.214:32879 <- 192.168.0.214から届いている
dstaddr=192.168.0.255
sdl->sdl_index=4
sdl->data=en0
*************************
」
ということで、Linux,BSD,Solarisで使えるようにしたソースを最後に載せておきます。SolarisはIPMP(IPマルチパス)にしてやってみました。ネットワークインターフェースの冗長化です。こういう場合にはどのインターフェースから受信したのかがわかると便利・・・というより、わからないとUDPで応答パケットを返す際に困る場合も結構あるものなのです。
私がProDHCPを開発した際にはUDPの送受信でインターフェースを意識して作らなければならず、リンクレイヤーで作りましたが、sendmsg(),recvmsg()を使えば同じことが比較的簡単にできます。ただしBSD系は送信する際に手間がかかりそうですが。ちなみに、IPv6ではIPV6_PKTINFOがどの処理系でも存在するようなので良い感じです!
受信プログラム
「
#ifdef __SunOS(Solaris)
#define _XPG4_2
#define __EXTENSIONS__
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#ifdef IP_RECVIF
#include <net/if_dl.h>
#endif
#define BUF_LEN 512
int main(int argc,char *argv[])
{
int s,rc,port;
int one=1;
struct sockaddr_in my;
struct if_nameindex *nindex,*ptr;
if(argc<=1){
fprintf(stderr,"%s port-no\n",argv[0]);
return(-1);
}
port=atoi(argv[1]);
/* for debug */
nindex=if_nameindex();
for(ptr=nindex;ptr->if_index!=0;ptr++){
printf("%d:%s\n",ptr->if_index,ptr->if_name);
}
if_freenameindex(nindex);
/* for debug */
s=socket(PF_INET,SOCK_DGRAM,0);
if(s<0){
perror("socket");
return(1);
}
#ifdef IP_PKTINFO
rc=setsockopt(s,IPPROTO_IP,IP_PKTINFO,&one,sizeof(one));
if(rc<0){
perror("setsockopt(IP_PKTINFO)");
return(1);
}
printf("IP_PKTINFO:on\n");
#endif
#ifdef IP_RECVDSTADDR
rc=setsockopt(s,IPPROTO_IP,IP_RECVDSTADDR,&one,sizeof(one));
if(rc<0){
perror("setsockopt(IP_RECVDSTADDR)");
return(1);
}
printf("IP_RECVDSTADDR:on\n");
#endif
#ifdef IP_RECVIF
rc=setsockopt(s,IPPROTO_IP,IP_RECVIF,&one,sizeof(one));
if(rc<0){
perror("setsockopt(IP_RECVIF)");
return(1);
}
printf("IP_RECVIF:on\n");
#endif
memset(&my,0,sizeof(my));
my.sin_family=AF_INET;
my.sin_addr.s_addr=htonl(INADDR_ANY);
my.sin_port=ntohs(port);
if(bind(s,(struct sockaddr *)&my,sizeof(my))==-1){
perror("bind");
return(-1);
}
while(1){
unsigned char buf[BUF_LEN];
struct msghdr msg;
struct iovec iov[1];
struct cmsghdr *cmsg;
char cbuf[512];
struct sockaddr_in sin;
#ifdef IP_PKTINFO
struct in_pktinfo *pktinfo=NULL;
#endif
#ifdef IP_RECVIF
struct sockaddr_dl *sdl=NULL;
#endif
#ifdef IP_RECVDSTADDR
struct in_addr *dstaddr=NULL;
char ifname[128];
#endif
iov[0].iov_base=buf;
iov[0].iov_len=BUF_LEN;
memset(&msg,0,sizeof(msg));
msg.msg_name=&sin;
msg.msg_namelen=sizeof(sin);
msg.msg_iov=iov;
msg.msg_iovlen=1;
msg.msg_control=cbuf;
msg.msg_controllen=512;
rc=recvmsg(s,&msg,0);
if(rc<0){
perror("recvmsg");
continue;
}
for(cmsg=CMSG_FIRSTHDR(&msg);cmsg!=NULL;
cmsg=CMSG_NXTHDR(&msg,cmsg)){
#ifdef IP_PKTINFO
if(cmsg->cmsg_level==IPPROTO_IP&& cmsg->cmsg_type==IP_PKTINFO){
pktinfo=(struct in_pktinfo *)CMSG_DATA(cmsg);
}
#endif
#ifdef IP_RECVDSTADDR
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR){
dstaddr = (struct in_addr *)CMSG_DATA(cmsg);
}
#endif
#ifdef IP_RECVIF
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVIF) {
sdl = (struct sockaddr_dl *)CMSG_DATA(cmsg);
}
#endif
}
printf("*************************\n");
printf("buf=[%s]\n",buf);
printf("from %s:%d\n",inet_ntoa(sin.sin_addr),ntohs(sin.sin_port));
#ifdef IP_PKTINFO
printf("pktinfo->ipi_ifindex=%d\n",pktinfo->ipi_ifindex);
printf("pktinfo->ipi_spec_dst=%s\n",inet_ntoa(pktinfo->ipi_spec_dst));
printf("pktinfo->ipi_addr=%s\n",inet_ntoa(pktinfo->ipi_addr));
#endif
#ifdef IP_RECVDSTADDR
printf("dstaddr=%s\n",inet_ntoa(*dstaddr));
#endif
#ifdef IP_RECVIF
printf("sdl->sdl_index=%d\n",sdl->sdl_index);
memcpy(ifname,sdl->sdl_data, sdl->sdl_nlen);
ifname[sdl->sdl_nlen] = 0;
printf("sdl->data=%s\n",ifname);
#endif
printf("*************************\n");
}
close(s);
return(0);
}
」
送信プログラム
「
#ifdef __SunOS(Solaris)
#define _XPG4_2
#define __EXTENSIONS__
#endif
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <arpa/inet.h>
#ifdef IP_RECVIF
#include <net/if_dl.h>
#include <net/if_types.h>
#endif
#define BUF_LEN 512
int main(int argc,char *argv[])
{
int s,rc;
int one=1;
struct if_nameindex *nindex,*ptr;
struct in_addr myaddr;
char *if_name;
if(argc<=2){
fprintf(stderr,"%s if-name my-ipaddress\n",argv[0]);
return(-1);
}
if_name=argv[1];
myaddr.s_addr=inet_addr(argv[2]);
/* for debug */
nindex=if_nameindex();
for(ptr=nindex;ptr->if_index!=0;ptr++){
printf("%d:%s\n",ptr->if_index,ptr->if_name);
}
if_freenameindex(nindex);
/* for debug */
s=socket(PF_INET,SOCK_DGRAM,0);
if(s<0){
perror("socket");
return(1);
}
if(setsockopt(s,SOL_SOCKET,SO_BROADCAST,&one,sizeof(one))==-1){
perror("setsockopt");
return(-1);
}
#ifdef IP_PKTINFO
rc=setsockopt(s,IPPROTO_IP,IP_PKTINFO,&one,sizeof(one));
if(rc<0){
perror("setsockopt(IP_PKTINFO)");
return(1);
}
printf("IP_PKTINFO:on\n");
#endif
printf("input \"host:port\"\n");
while(1){
unsigned char buf[BUF_LEN];
char buf2[BUF_LEN];
char *hostnm,*portnm,*ptr;
struct msghdr msg;
struct iovec iov[1];
struct cmsghdr *cmsg;
char cbuf[512];
struct sockaddr_in sin;
#ifdef IP_PKTINFO
struct in_pktinfo pktinfo;
#endif
memset(buf,0,sizeof(buf));
fgets((char *)buf,sizeof(buf),stdin);
if(feof(stdin)){
break;
}
strcpy(buf2,(char *)buf);
if((hostnm=strtok(buf2,":"))==NULL){
fprintf(stderr,"Input-error\n");
fprintf(stderr,"host:port:nic\n");
break;
}
if((portnm=strtok(NULL,"\r\n"))==NULL){
fprintf(stderr,"Input-error\n");
fprintf(stderr,"host:port:nic\n");
break;
}
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_addr.s_addr=inet_addr(hostnm);
sin.sin_port=ntohs(atoi(portnm));
if((ptr=strchr((char *)buf,'\n'))!=NULL){
*ptr='\0';
}
iov[0].iov_base=buf;
iov[0].iov_len=BUF_LEN;
memset(&msg,0,sizeof(msg));
msg.msg_name=&sin;
msg.msg_namelen=sizeof(sin);
msg.msg_iov=iov;
msg.msg_iovlen=1;
msg.msg_control=cbuf;
msg.msg_controllen=512;
#ifdef IP_PKTINFO
memset(&pktinfo,0,sizeof(pktinfo));
pktinfo.ipi_ifindex=if_nametoindex(if_name);
fprintf(stderr,"ifindex=%d\n",pktinfo.ipi_ifindex);
pktinfo.ipi_spec_dst=myaddr;
// pktinfo.ipi_addr=myaddr; /* no need to */
cmsg=CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level=IPPROTO_IP;
cmsg->cmsg_type=IP_PKTINFO;
cmsg->cmsg_len=CMSG_LEN(sizeof(struct in_pktinfo));
memcpy(CMSG_DATA(cmsg),&pktinfo,sizeof(pktinfo));
msg.msg_controllen=cmsg->cmsg_len;
#endif
rc=sendmsg(s,&msg,0);
if(rc<0){
perror("send");
continue;
}
}
close(s);
return(0);
}
」