/* This program runs on a server which has connectivity to various horizon servers. It accepts a connection from the blackhole server. The blackhole server instructs the wormhole to establish connections with two horizons servers and then acts as a proxy between the two. The connection is a link and a connect. It links to one horizon server with a pending connection from a local vserver. It request a new connection on another horizon server and establish the copy back and forth between the two. The wormhole acts as a network optimisation to make a more direct route between two horizon servers. A network may very well work without wormhole servers. It is just an optimisation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "fdpass.h" using namespace std; static DEBUG_KEY D_WORMHOLE("wormhole","Wormhole main program"); static int horizon_splitbind (const char *s, SSTRING &name, SSTRING &port) { int ret = -1; const char *comma = strchr(s,','); if (comma == NULL){ tlmp_error ("No comma found in bind definition: %s\n",s); }else{ name.setfrom(s,comma-s); comma++; port = comma; ret = 0; } return ret; } enum CONNECT_TYPE { TYPE_CONTROL, TYPE_MASTER, TYPE_FORK }; struct HANDLE_INFO: public ARRAY_OBJ{ CONNECT_TYPE type; SSTRING comment; time_t lastping; time_t intruder; HANDLE_INFO(){ lastping = (time_t)0; intruder = (time_t)0; } }; #include "proto/wormhole_control.protoh" #include "proto/wormhole_master.protoh" /* Copy back and forth between two raw tcp session. Ends whenever one hang up. */ static void horizon_runlink (int fd1, int fd2, const char *description, int do_not_close) { if (fdpass_sendfd2proxy(fd1,fd2,do_not_close,description)==-1){ fdpass_loop (fd1,fd2,do_not_close); } _exit(0); } static const char *NETWORK_KEYWORD = "NETWORK"; static int wormhole_connect_one ( const map &secrets, const char *host1, const char *vserv1, const char *port1, const char *horizon_port) { int fd1 = -1; if (strcmp(host1,NETWORK_KEYWORD)==0){ // Direct connection to the service fd1 = fdpass_tcpconnect (vserv1,port1); if (fd1 == -1){ tlmp_error ("Can't connect to NETWORK server %s on port %s\n",vserv1,port1); } }else{ // Connection through an horizon server fd1 = fdpass_tcpconnect (host1,horizon_port); if (fd1 == -1){ tlmp_error ("Can't connect to horizon server %s on port %s\n",host1,horizon_port); }else{ fdpass_sendtof (fd1,host1,secrets,"connect %s %s _\n",vserv1,port1); if (fdpass_waitdata(fd1)==-1) fd1 = -1; } } return fd1; } static void wormhole_connect_link ( const map &secrets, const char *host1, const char *vserv1, const char *port1, const char *host2, const char *link2, const char *horizon_port, int do_not_close) { if (fork()==0){ int fd1 = wormhole_connect_one (secrets,host1,vserv1,port1,horizon_port); int fd2 = fdpass_tcpconnect (host2,horizon_port); if (fd2 == -1){ tlmp_error ("Can't connect to horizon server %s on port %s\n",host2,horizon_port); }else if (fd1 == -1){ // We were unable to establish one end of the connection // so we tell the horizon server not to wait anymore if (fdpass_sendmaster(host2,secrets,fd2,false) != -1){ fdpass_sendtof (fd2,host2,secrets,"reject %s\n",link2); } }else{ fdpass_sendtof (fd2,host2,secrets,"link %s\n",link2); if (fdpass_waitdata(fd2)!=-1){ SSTRING tmp; tmp.setfromf ("connect %s %s %s -> link %s %s",host1,vserv1,port1,host2,link2); horizon_runlink (fd1,fd2,tmp.c_str(),do_not_close); } } _exit (0); } } static void wormhole_connect ( const map &secrets, const char *host1, const char *port1, int fd2, const char *horizon_port, int do_not_close) { if (fork()==0){ int fd1 = wormhole_connect_one (secrets,NETWORK_KEYWORD,host1,port1,horizon_port); if (fd1 != -1){ fdpass_okdata (fd2); SSTRING tmp; tmp.setfromf ("net-connect %s %s from blackhole",host1,port1); horizon_runlink (fd1,fd2,tmp.c_str(),do_not_close); } _exit (0); } } int main (int argc, char *argv[]) { glocal const char *pidfile = "/run/wormhole.pid"; glocal const char *user = "blackhole"; glocal bool daemon = false; glocal const char *master = "0.0.0.0,8000"; glocal const char *control = "/var/run/blackhole/wormhole.sock"; glocal const char *horizon_port = "8000"; glocal const char *secretfile = NULL; glocal const char *wormname = NULL; // Name used for this wormhole instead of the hostname glocal int ret; glocal.ret = (argc,argv); setproginfo ("wormhole",VERSION,"Proxy TCP connection between horizon server\n" "under control of the blackhole server\n"); setgrouparg ("Port"); setarg (' ',"control","Unix socket control port",glocal.control,false); setarg (' ',"master","Listen for blackhole server on IP,port",glocal.master,false); setarg (' ',"horizon_port","TCP port to contact horizon servers",glocal.horizon_port,false); setarg (' ',"secretfile","File holding secrets",glocal.secretfile,false); setarg (' ',"name","Name to use instead of the hostname",glocal.wormname,false); fdpass_setarg (this); setgrouparg ("Daemon options"); setarg (' ',"daemon","Run in background",glocal.daemon,false); setarg (' ',"pidfile","Write the daemon pid in this file",glocal.pidfile,false); setarg (' ',"user","Run as this user",glocal.user,false); if (glocal.daemon){ syslog (LOG_ERR,"%s",msg); }else{ fprintf (stderr,"%s",msg); } int ret = -1; glocal set allows; // Allow master connection from those IPs glocal int nbfork = 0; glocal map secrets; glocal string myname; // Name used to identify this wormhole and lookup its secret if (glocal.wormname != NULL){ glocal.myname = glocal.wormname; }else{ char buf[1000]; if (gethostname(buf,sizeof(buf))!=-1){ char *pt = strchr(buf,'.'); if (pt != NULL) *pt = '\0'; glocal.myname = buf; } } fdpass_readsecrets (glocal.secretfile,glocal.secrets); fdpass_checkservice (glocal.control); (); debug_printf (D_WORMHOLE,"newclient no=%d port=%s\n",no,info.port); HANDLE_INFO *c = new HANDLE_INFO; info.data = c; char addr[20]; ipnum_ip2a (from,addr); if (strncmp(info.port,"unix:",5)==0){ c->type = TYPE_CONTROL; }else{ // This is a connection from a blackhole server if (glocal.allows.count(addr)==0){ endclient = true; tlmp_error ("Rejected master connection from ip %s\n",addr); }else{ debug_printf (D_WORMHOLE,"Connection from master %s\n",addr); c->type = TYPE_MASTER; } } HANDLE_INFO *c = (HANDLE_INFO*)info.data; if (c->type == TYPE_FORK){ glocal.nbfork--; debug_printf (D_WORMHOLE,"sub-process ending: %s\n",c->comment.c_str()); } HANDLE_INFO *c = (HANDLE_INFO*)info.data; if (c->type == TYPE_CONTROL){ (this,line,endserver,endclient,no,c); glocal.allows.insert (ipaddr); debug_printf (D_WORMHOLE,"Allow master connection from ip %s\n",ipaddr); glocal.TCPSERVER.sendf ("wormhole version %s\n",VERSION); glocal.TCPSERVER.sendf ("myname=%s\n",glocal.myname.c_str()); glocal.TCPSERVER.sendf ("secrets %zu\n",glocal.secrets.size()); glocal.TCPSERVER.sendf ("nbfork=%d\n",glocal.nbfork); for (set::iterator it=glocal.allows.begin(); it != glocal.allows.end(); it++){ glocal.TCPSERVER.sendf ("allow %s\n",it->c_str()); } void *data; int fd = glocal.TCPSERVER.iter_init(data); time_t now = time(NULL); while (fd != -1){ HANDLE_INFO *n = (HANDLE_INFO*)data; glocal.TCPSERVER.sendf ("Connection %d",fd); if (n->type == TYPE_CONTROL){ glocal.TCPSERVER.send ("(control)"); }else if (n->type == TYPE_MASTER){ char intruder[100]; fdpass_format_intruder (n->intruder,intruder); glocal.TCPSERVER.sendf ("(master lastping %lds ago) %s",now-n->lastping,intruder); }else if (n->type == TYPE_FORK){ glocal.TCPSERVER.send ("(fork)"); } glocal.TCPSERVER.sendf (": %s\n",n->comment.c_str()); fd = glocal.TCPSERVER.iter_next(data); } void *data; int fd = glocal.TCPSERVER.iter_init(data); while (fd != -1){ HANDLE_INFO *n = (HANDLE_INFO*)data; if (n->type == TYPE_FORK){ glocal.TCPSERVER.sendf ("fd=%d %s\n",fd,n->comment.c_str()); } fd = glocal.TCPSERVER.iter_next(data); } fdpass_readsecrets (glocal.secretfile,glocal.secrets); endserver = true; bool ison = atoi(on)==1; if (ison){ debug_seton(); }else{ debug_setoff(); } debug_setfdebug (filename); glocal.TCPSERVER.send ("Invalid command\n"); endclient = true; }else if (c->type == TYPE_MASTER){ string secret = fdpass_findsecret (glocal.secrets,glocal.myname); (this,line,endserver,endclient,secret,c->intruder,no,c); // In this case, all traffic will be handled by the wormhole. // It will connect to two horizons (or one internet server and one horizon) // and manage the flow between both. debug_printf (D_WORMHOLE,"connect-link %s %s %s -> %s %s\n",server1,vserver1,port1,server2,linkstr); int tb[2]; if (pipe(tb)==-1){ tlmp_error ("Can't set pipe, ending\n"); endserver = true; }else{ HANDLE_INFO *n = new HANDLE_INFO; n->type = TYPE_FORK; n->comment.setfromf ("%s %s %s -> %s %s",server1,vserver1,port1,server2,linkstr); glocal.TCPSERVER.inject (tb[0],n); glocal.nbfork++; wormhole_connect_link (glocal.secrets,server1,vserver1,port1,server2,linkstr,glocal.horizon_port,tb[1]); close (tb[1]); } // In this case, we connect to a network, not an horizon // and the blackhole master stay part of the loop. // THIS CODE is not used anymore since this case is handled by the horizon with its // --open_client mode // The third arguemt is discarded, it is a _ anyway. // blackhole uses the same routine to talk to horizon and wormhole in that case. debug_printf (D_WORMHOLE,"connect %s %s\n",server,port); int tb[2]; if (pipe(tb)==-1){ tlmp_error ("Can't set pipe, ending\n"); endserver = true; }else{ HANDLE_INFO *n = new HANDLE_INFO; n->type = TYPE_FORK; n->comment.setfromf ("-> %s %s",server,port); glocal.TCPSERVER.inject (tb[0],n); glocal.nbfork++; wormhole_connect (glocal.secrets,server,port,no,glocal.horizon_port,tb[1]); close (tb[1]); endclient = true; } debug_printf (D_EXTRA,"Ping received from master %d\n",no); glocal.TCPSERVER.send ("pong\n"); c->lastping = time(NULL); // We keep that just to help debug in // the status control program // A wormhome server does not care if a master // is not alive anymore. glocal.TCPSERVER.send ("Invalid command\n"); tlmp_error ("Rejected command from master: %s\n",line); endclient = true; } bool some_errors = false; { SSTRING name,port; if(horizon_splitbind(glocal.master,name,port)==-1){ some_errors = true; }else if (o.listen (name.c_str(),port.c_str()) == -1){ some_errors = true; } } if (fdpass_setcontrol(o,glocal.control,glocal.user)==-1){ some_errors = true; } if (some_errors){ tlmp_error ("Some errors, ending\n"); }else{ signal (SIGCHLD,SIG_IGN); if (glocal.daemon) daemon_init (glocal.pidfile,glocal.user); o.loop(); ret = 0; } return ret; return glocal.ret; }