每个网络连接都由两个 IP 地址/端口对组成。用于网络编程的 API(应用程序编程接口)被称为 Sockets API。套接字就像一个打开的文件一样,通过对其进行读/写操作,您可以通过网络连接发送数据。有一个函数调用 getsockname
,它将返回本地套接字的 IP 地址。Virtuald 使用 getsockname
来确定本地机器上正在访问哪个 IP。Virtuald 读取配置文件以检索与该 IP 关联的目录。它将 chroot
到该目录并将连接交给服务。 Chroot
将 / 或根目录重置到一个新的位置,以便目录树中更高的所有内容都从正在运行的程序中切断。因此,每个 IP 地址都获得自己的虚拟文件系统。对于网络程序来说,这是透明的,程序会表现得像什么都没发生一样。Virtuald 与像 inetd 这样的程序结合使用,可以用于虚拟化任何服务。
Inetd 是一个网络超级服务器,它监听多个端口,当它接收到连接时(例如,传入的 pop 请求),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 和目录。您可以拥有一个主配置文件,或者如果您希望每个服务获得不同的域名列表,则可以拥有多个配置文件。一个配置文件看起来像这样
# 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,组为 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.49 $"); #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); }