모든 네트워크 연결은 2개의 IP 어드레스/포트 쌍으로 이루어진다.
네트워크 프로그래밍을 위한 API(Applications Program Interface)는
소켓 API로 불린다. 소켓은 마치 열려있는 파일과 같이 작동하기 때문에
네트워크 연결을 통해 데이터를 주고받는 것은 소켓에 쓰고읽는 과정으로
이해된다. 로컬 소켓의 주소를 돌려주는 함수는 getsockname
이다.
Virtuald는 getsockname
을 사용하여 로컬 시스템의 어떤 IP가
접근 가능한지를 판단한다. Virtuald는 설정 파일을 읽어서 해당 IP에 할당된
디렉토리를 돌려받는다. 그리고는 chroot
를 통해서 모든 서비스에 대한
제어권을 넘겨준다. Chroot
은 루트 디렉토리를 재설정하여 새롭게
지정받은 포인트를 루트로 설정하기 때문에 상위 디렉토리는 실행
프로그램들에 의해 무시된다. 네트워크 프로그램에 대해서는 이 과정은 마치
아무일도 일어나지 않은 것처럼 투명하게 보일 것이다. Virtuald는 inetd와 같은
프로그램과 연결되어 어떤 서비스라도 가상적으로 만들 수 있다.
Inetd는 외부에서 네트워크를 통해 연결을 시도할 때(예를 들면 POP서버의 요청) 여러 개의 포트를 감시하고 있다가 요청에 대한 적당한 연결을 구현해주는 네트워크 super server이다. Inetd는 네트워크를 전반적으로 관리하면서 특정 프로그램에 대해 네트워크를 연결해 주는 일을 한다. 이런 과정을 통해 어떤 서비스가 네트워크 연결이 필요 없을 때 불필요하게 실행되는 것을 막을 수 있다.
표준적인 /etc/inetd.conf 파일은 다음과 같다:
ftp stream tcp nowait root /usr/sbin/tcpd \ wu.ftpd -l -a pop-3 stream tcp nowait root /usr/sbin/tcpd \ in.qpop -s
가상 서비스를 할 때 /etc/inetd.conf 파일은 다음과 같이 된다:
ftp stream tcp nowait root /usr/local/bin/virtuald \ virtuald /virtual/conf.ftp wu.ftpd -l -a pop-3 stream tcp nowait root /usr/local/bin/virtuald \ virtuald /virtual/conf.pop in.qpop -s
각각의 서비스들은 어떤 IP들과 디렉토리들을 사용하고 관리할 것인지에 대한 정보를 설정 파일에서 얻게 된다. 당신은 하나의 전체 설정 파일 (master config file)을 갖던지 아니면 몇몇의 설정 파일들을 통해서 각각의 서비스에서 도메인의 다른 리스트를 얻게 할 수 있다. 설정 파일은 다음과 같다:
# This is a comment and so are blank lines # Format IP SPACE dir NOSPACES 10.10.10.129 /virtual/domain1.com 10.10.10.130 /virtual/domain2.com 10.10.10.157 /virtual/domain3.com # Default option for all other IPs default /
아래는 virtuald 프로그램에 대한 C 소스 코드이다. 이것을 컴파일하고 /usr/local/bin 아래에 0755의 퍼미션으로 설치하라.(소유자와 그룹은 모두 root로 한다.) 컴파일시에는 VERBOSELOG가 옵션으로 사용될 수 있는데 (오직 이 옵션만이 존재한다), 이것은 연결시 기록을 남길것인지를 정한다.
#include <netinet/in.h> #include <sys/socket.h> #include <arpa/inet.h> #include <stdarg.h> #include <unistd.h> #include <string.h> #include <syslog.h> #include <stdio.h> #undef VERBOSELOG #define BUFSIZE 8192 int getipaddr(char **ipaddr) { struct sockaddr_in virtual_addr; static char ipaddrbuf[BUFSIZE]; int virtual_len; char *ipptr; virtual_len=sizeof(virtual_addr); if (getsockname(0,(struct sockaddr *)&virtual_addr,&virtual_len)<0) { syslog(LOG_ERR,"getipaddr: getsockname failed: %m"); return -1; } if (!(ipptr=inet_ntoa(virtual_addr.sin_addr))) { syslog(LOG_ERR,"getipaddr: inet_ntoa failed: %m"); return -1; } strncpy(ipaddrbuf,ipptr,sizeof(ipaddrbuf)-1); *ipaddr=ipaddrbuf; return 0; } int iptodir(char **dir,char *ipaddr,char *filename) { char buffer[BUFSIZE],*bufptr; static char dirbuf[BUFSIZE]; FILE *fp; if (!(fp=fopen(filename,"r"))) { syslog(LOG_ERR,"iptodir: fopen failed: %m"); return -1; } *dir=NULL; while(fgets(buffer,BUFSIZE,fp)) { buffer[strlen(buffer)-1]=0; if (*buffer=='#' || *buffer==0) continue; if (!(bufptr=strchr(buffer,' '))) { syslog(LOG_ERR,"iptodir: strchr failed"); return -1; } *bufptr++=0; if (!strcmp(buffer,ipaddr)) { strncpy(dirbuf,bufptr,sizeof(dirbuf)-1); *dir=dirbuf; break; } if (!strcmp(buffer,"default")) { strncpy(dirbuf,bufptr,sizeof(dirbuf)-1); *dir=dirbuf; break; } } if (fclose(fp)==EOF) { syslog(LOG_ERR,"iptodir: fclose failed: %m"); return -1; } if (!*dir) { syslog(LOG_ERR,"iptodir: ip not found in conf file"); return -1; } return 0; } int main(int argc,char **argv) { char *ipaddr,*dir; openlog("virtuald",LOG_PID,LOG_DAEMON); #ifdef VERBOSELOG syslog(LOG_ERR,"Virtuald Starting: $Revision: 1.1.1.1 $"); #endif if (!argv[1]) { syslog(LOG_ERR,"invalid arguments: no conf file"); exit(0); } if (!argv[2]) { syslog(LOG_ERR,"invalid arguments: no program to run"); exit(0); } if (getipaddr(&ipaddr)) { syslog(LOG_ERR,"getipaddr failed"); exit(0); } #ifdef VERBOSELOG syslog(LOG_ERR,"Incoming ip: %s",ipaddr); #endif if (iptodir(&dir,ipaddr,argv[1])) { syslog(LOG_ERR,"iptodir failed"); exit(0); } if (chroot(dir)<0) { syslog(LOG_ERR,"chroot failed: %m"); exit(0); } #ifdef VERBOSELOG syslog(LOG_ERR,"Chroot dir: %s",dir); #endif if (chdir("/")<0) { syslog(LOG_ERR,"chdir failed: %m"); exit(0); } if (execvp(argv[2],argv+2)<0) { syslog(LOG_ERR,"execvp failed: %m"); exit(0); } closelog(); exit(0); }