Enhver nettverksoppkobling er lagd av to IP adresse/port par. API (Applications Program Interface) for nettverksprogrammering har fått navnet Sockets API. Denne socketen fungerer som en åpen fil, og ved å lese/skrive til den kan du sende data over en nettverksoppkobling. Det er en funksjon kalt getsocketname som vil returnere IP adressen for en lokal socket. Virtuald bruker getsocketname for å avgjøre hvilken IP på den lokale maskinen som blir brukt. Virtuald leser en konfigurasjonsfil for å hente den katalogen som er assosiert med den IP'en. Den vil chroot til den katalogen og gi oppkoblingen til tjenesten. Chroot resetter / eller root katalogen til et nytt punkt slik at alt som er høyere i katalogtreet blir avkuttet fra programmet som kjører. Derfor kan hver IP adresse få sitt eget virtuelle filsystem. For nettverksprogrammet utgjør dette ingen forskjell og programmet vil oppføre seg som om ingenting skjedde. Virtuald i samarbeid med et program som inetd kan derfor bli brukt til å virtualisere en hvilkensomhelst tjeneste.
Inetd er en nettverks superserver som hører på flere porter og når den får en oppkobling (for eksempel, en innkommende POP request), sørger inetd for nettverksforhandlingene og gir nettverksoppkoblingen videre til det spesifiserte program. Dette forhindrer at tjenester kjører når de ikke trengs.
En standard /etc/inetd.conf fil ser slik ut:
ftp stream tcp nowait root /usr/bin/tcpd \ wu.ftpd -l -a pop-3 stram tcp nowait root /usr/bin/tcpd \ in.qpop -s
En virtuell /etc/inetd.conf ser slik ut:
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
Hver tjeneste får en konfigurasjonsfil som vil kontrollere hvilke IP'er og kataloger som er lovlige for den tjenesten. Du kan ha en "master" konfigurasjonsfil eller flere konfigurasjonsfiler hvis du vil ha hver tjeneste til å få en forskjellig liste over domener. En konfigurasjonsfil ser slik ut:
# 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 /
Dette er C koden til virtuald programmet. Kompiler det og installer det i /usr/local/bin med rettigheter 0755, bruker root, og gruppe root. Den eneste kompilasjonsopsjonen er VERBOSELOG som vil skru av/på logging av oppkoblinger.
#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); }