オルタナティブ・ブログ > プログラマー社長のブログ >

プログラミングでメシが食えるか!?

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);
}

Comment(0)