ネットdeダビングのクライアントソフトを作る(その2)

 前回はRD-XV81でダビング先切換を行った際にUDPの137番ポートに送信される内容を表示するところまで作ったので、今回は送信された内容に対する応答を送信する部分を作成して、ネットdeダビング応答ソフト(rdnmb)を作ってみた。
 引数にanonymous FTPサーバのIPアドレスとネットワーク機器名(本体名)を指定して実行する(例:sudo ./rdnmb 192.168.100.20 RD-CLIENT &)。終了させるにはCtrl+\を入力するかkill -3 723のようにプロセス番号を指定して終了させる(例:sudo kill -3 723)。
 エラーチェックは一切していないので動作しない場合はsocketやbind、recvfrom、sendto等の戻り値(fd、rc等)を確認して適切に対処する必要がある。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc,char *argv[]) {
	int fd,rc,ad0,ad1,ad2,ad3,i;
	struct sockaddr_in addr1,addr2;
	socklen_t addrlen;
	char buf[1024],name[16];

	sscanf(argv[1],"%d.%d.%d.%d",&ad0,&ad1,&ad2,&ad3);
	fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
	addr1.sin_family=AF_INET;
	addr1.sin_port=htons(137);
	addr1.sin_addr.s_addr=htonl(INADDR_ANY);
	rc=bind(fd,(struct sockaddr *)&addr1,sizeof(addr1));
	while(1) {
		addrlen=sizeof(addr1);
		rc=recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr *)&addr1,&addrlen);
		for(i=0;i<16;i++) name[i]=(buf[13+i*2]-0x41)*16+(buf[13+i*2+1]-0x41);
		if(strncmp(name,"GR-",3)==0) {
			for(i=2;i<62;i++) buf[i]=0x00;
			buf[0]=1;
			buf[2]=0x85;
			buf[7]=0x01;
			buf[12]=0x20;
			strcpy(name,argv[2]);
			for(i=strlen(argv[2]);i<15;i++) name[i]=' ';
			name[15]=0x00;
			for(i=0;i<16;i++) {
				buf[13+i*2]=name[i]/16+0x41;
				buf[14+i*2]=name[i]%16+0x41;
			}
			buf[47]=0x20;
			buf[49]=0x01;
			buf[55]=0x06;
			buf[58]=ad0;
			buf[59]=ad1;
			buf[60]=ad2;
			buf[61]=ad3;
			addr2.sin_family=AF_INET;
			addr2.sin_port=htons(137);
			addr2.sin_addr.s_addr=addr1.sin_addr.s_addr;
			rc=sendto(fd,buf,62,0,(struct sockaddr *)&addr2,sizeof(addr2));
		}
	}
}

 anonymous FTPサーバには予め$xml$status.xmlを下記内容で作成しておく必要がある。

<?xml version="1.0" encoding="shift_jis"?>
<status>
<dubbing>ready</dubbing>
<accept_dubbing>ready</accept_dubbing>
<device0_remain>167772160</device0_remain>
<device0_recorded>0</device0_recorded>
<device0_title_remain>350</device0_title_remain>
</status>

 anonymous FTPサーバとして動作するネットdeダビング転送ソフト(rdftp)も作ってみた。TCPの21番ポートをsocket、bindするまではUDPの場合と基本的には同じ。この後にlistenでサーバとして動作開始。acceptで接続待ちをして送受信用のファイルディスクリプタを開き、以後はrecvとsendで送受信するだけ。
 コマンドや応答メッセージは末尾にCRとLFが付く。ちなみにコマンドはネットdeダビングで使われるものしか実装していないが、一応ftpクライアントからの接続もちゃんと出来る。
 引数に自分のIPアドレスを指定して実行する(例:sudo ./rdftp 192.168.100.20 &)。終了させるにはCtrl+\を入力するかkill -3 725のようにプロセス番号を指定して終了させる(例:sudo kill -3 725)。
 エラーチェックは一切していないので動作しない場合はsocketやbind、listen、accept、recv、send等の戻り値(fd、rc等)を確認して適切に対処する必要がある。また、TCPの仕様とそれに対する対策をしていないのでFTPサーバ終了後に再起動する場合は60秒以上(システムによって異なる場合がある)待つ必要がある。

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc,char *argv[]) {
	FILE *fp;
	int fd1,fd2,fd3,fd4,rc,ad0,ad1,ad2,ad3,ap,i;
	struct sockaddr_in addr1,addr2;
	socklen_t addrlen;
	char buf[1024],cmd1[1024],cmd2[1024];

	sscanf(argv[1],"%d.%d.%d.%d",&ad0,&ad1,&ad2,&ad3);
	fd1=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	addr1.sin_family=AF_INET;
	addr1.sin_port=htons(21);
	addr1.sin_addr.s_addr=htonl(INADDR_ANY);
	rc=bind(fd1,(struct sockaddr *)&addr1,sizeof(addr1));
	rc=listen(fd1,1);
	ap=2048;
	while(1) {
		addrlen=sizeof(addr1);
		fd2=accept(fd1,(struct sockaddr *)&addr1,&addrlen);
		rc=send(fd2,"220 \r\n",6,0);
		fd3=-1;
		while(1) {
			rc=recv(fd2,buf,sizeof(buf),0);
			sscanf(buf,"%s%s",cmd1,cmd2);
			if(strcmp(cmd1,"USER")==0) rc=send(fd2,"331 \r\n",6,0);
			else if(strcmp(cmd1,"PASS")==0) rc=send(fd2,"230 \r\n",6,0);
			else if(strcmp(cmd1,"TYPE")==0) rc=send(fd2,"200 \r\n",6,0);
			else if(strcmp(cmd1,"PASV")==0) {
				if(fd3>=0) rc=close(fd3);
				fd3=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
				addr2.sin_family=AF_INET;
				addr2.sin_port=htons(ap);
				addr2.sin_addr.s_addr=htonl(INADDR_ANY);
				rc=bind(fd3,(struct sockaddr *)&addr2,sizeof(addr2));
				rc=listen(fd3,1);
				sprintf(buf,"227 Entering passive mode (%d,%d,%d,%d,%d,%d).\r\n",ad0,ad1,ad2,ad3,ap/256,ap%256);
				rc=send(fd2,buf,strlen(buf),0);
				ap++;
			}
			else if(strcmp(cmd1,"RETR")==0) {
				rc=send(fd2,"150 \r\n",6,0);
				addrlen=sizeof(addr2);
				fd4=accept(fd3,(struct sockaddr *)&addr2,&addrlen);
				fp=fopen(cmd2,"rb");
				while(1) {
					rc=fread(buf,1,sizeof(buf),fp);
					if(rc<1) break;
					rc=send(fd4,buf,rc,0);
				}
				rc=fclose(fp);
				rc=close(fd4);
				rc=send(fd2,"226 \r\n",6,0);
			}
			else if(strcmp(cmd1,"STOR")==0) {
				rc=send(fd2,"150 \r\n",6,0);
				addrlen=sizeof(addr2);
				fd4=accept(fd3,(struct sockaddr *)&addr2,&addrlen);
				fp=fopen(cmd2,"wb");
				while(1) {
					rc=recv(fd4,buf,sizeof(buf),0);
					if(rc<1) break;
					rc=fwrite(buf,1,rc,fp);
				}
				rc=fclose(fp);
				rc=close(fd4);
				rc=send(fd2,"226 \r\n",6,0);
			}
			else if(strcmp(cmd1,"QUIT")==0) {
				rc=send(fd2,"221 \r\n",6,0);
				break;
			}
			else rc=send(fd2,"502 \r\n",6,0);
		}
		if(fd3>=0) rc=close(fd3);
		rc=close(fd2);
	}
}

 これでネットdeダビング応答ソフトのrdnmbとネットdeダビング転送ソフトのrdftpを起動した状態で、RD-BR610からネットdeダビングできることを確認した。転送されたタイトルは$netdubbing$dev0.datのファイル名で保存されている。
 尚、複数タイトルのダビングを行った場合、rdftpが同じファイル名を上書きしてしまう為、最後にダビングしたタイトルしか残らないので、rdftpを書き換えるか、1つずつダビングする度にファイル名を変更する等の対応が必要となる。