Annotation of loncom/loncnew, revision 1.4

1.1       foxr        1: #!/usr/bin/perl
1.2       albertel    2: # The LearningOnline Network with CAPA
                      3: # lonc maintains the connections to remote computers
                      4: #
1.4     ! foxr        5: # $Id: loncnew,v 1.3 2003/04/18 03:10:36 albertel Exp $
1.2       albertel    6: #
                      7: # Copyright Michigan State University Board of Trustees
                      8: #
                      9: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
                     10: #
                     11: # LON-CAPA is free software; you can redistribute it and/or modify
                     12: # it under the terms of the GNU General Public License as published by
                     13: # the Free Software Foundation; either version 2 of the License, or
                     14: # (at your option) any later version.
                     15: #
                     16: # LON-CAPA is distributed in the hope that it will be useful,
                     17: # but WITHOUT ANY WARRANTY; without even the implied warranty of
                     18: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     19: # GNU General Public License for more details.
                     20: #
                     21: # You should have received a copy of the GNU General Public License
                     22: # along with LON-CAPA; if not, write to the Free Software
                     23: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
                     24: #
                     25: # /home/httpd/html/adm/gpl.txt
                     26: #
                     27: # http://www.lon-capa.org/
                     28: #
1.1       foxr       29: #
                     30: # new lonc handles n requestors spread out bver m connections to londs.
                     31: # This module is based on the Event class.
                     32: #   Development iterations:
                     33: #    - Setup basic event loop.   (done)
                     34: #    - Add timer dispatch.       (done)
                     35: #    - Add ability to accept lonc UNIX domain sockets.  (done)
                     36: #    - Add ability to create/negotiate lond connections (done).
                     37: #    - Add general logic for dispatching requests and timeouts.
                     38: #    - Add support for the lonc/lond requests.
                     39: #    - Add logging/status monitoring.
                     40: #    - Add Signal handling - HUP restarts. USR1 status report.
                     41: #    - Add Configuration file I/O
                     42: #    - Add Pending request processing on startup.
                     43: #    - Add management/status request interface.
                     44: 
                     45: use lib "/home/httpd/lib/perl/";
                     46: use lib "/home/foxr/newloncapa/types";
                     47: use Event qw(:DEFAULT );
                     48: use POSIX qw(:signal_h);
                     49: use IO::Socket;
                     50: use IO::Socket::INET;
                     51: use IO::Socket::UNIX;
                     52: use Socket;
                     53: use Crypt::IDEA;
                     54: use LONCAPA::Queue;
                     55: use LONCAPA::Stack;
                     56: use LONCAPA::LondConnection;
                     57: use LONCAPA::Configuration;
                     58: use LONCAPA::HashIterator;
                     59: 
                     60: print "Loncnew starting\n";
                     61: 
                     62: #
                     63: #   Disable all signals we might receive from outside for now.
                     64: #
                     65: $SIG{QUIT}  = IGNORE;
                     66: $SIG{HUP}   = IGNORE;
                     67: $SIG{USR1}  = IGNORE;
                     68: $SIG{INT}   = IGNORE;
                     69: $SIG{CHLD}  = IGNORE;
                     70: $SIG{__DIE__}  = IGNORE;
                     71: 
                     72: 
                     73: # Read the httpd configuration file to get perl variables
                     74: # normally set in apache modules:
                     75: 
                     76: my $perlvarref = LONCAPA::Configuration::read_conf('loncapa.conf');
                     77: my %perlvar    = %{$perlvarref};
                     78: 
                     79: #
                     80: #  parent and shared variables.
                     81: 
                     82: my %ChildHash;			# by pid -> host.
                     83: 
                     84: 
                     85: my $MaxConnectionCount = 5;	# Will get from config later.
                     86: my $ClientConnection = 0;	# Uniquifier for client events.
                     87: 
                     88: my $DebugLevel = 5;
                     89: my $IdleTimeout= 3600;		# Wait an hour before pruning connections.
                     90: 
                     91: #
                     92: #  The variables below are only used by the child processes.
                     93: #
                     94: my $RemoteHost;			# Name of host child is talking to.
                     95: my $UnixSocketDir= "/home/httpd/sockets"; 
                     96: my $IdleConnections = Stack->new(); # Set of idle connections
                     97: my %ActiveConnections;		# Connections to the remote lond.
                     98: my %ActiveTransactions;		# Transactions in flight.
                     99: my %ActiveClients;		# Serial numbers of active clients by socket.
                    100: my $WorkQueue       = Queue->new(); # Queue of pending transactions.
                    101: my $ClientQueue     = Queue->new(); # Queue of clients causing xactinos.
                    102: my $ConnectionCount = 0;
1.4     ! foxr      103: my $IdleSeconds     = 0;	# Number of seconds idle.
1.1       foxr      104: 
                    105: #
1.3       albertel  106: 
1.1       foxr      107: =pod
1.3       albertel  108: 
                    109: =head2 GetPeerName
                    110: 
                    111: Returns the name of the host that a socket object is connected to.
                    112: 
1.1       foxr      113: =cut
                    114: 
                    115: sub GetPeername {
                    116:     my $connection = shift;
                    117:     my $AdrFamily  = shift;
                    118:     my $peer       = $connection->peername();
                    119:     my $peerport;
                    120:     my $peerip;
                    121:     if($AdrFamily == AF_INET) {
                    122: 	($peerport, $peerip) = sockaddr_in($peer);
                    123: 	my $peername    = gethostbyaddr($iaddr, $AdrFamily);
                    124: 	return $peername;
                    125:     } elsif ($AdrFamily == AF_UNIX) {
                    126: 	my $peerfile;
                    127: 	($peerfile) = sockaddr_un($peer);
                    128: 	return $peerfile;
                    129:     }
                    130: }
                    131: #----------------------------- Timer management ------------------------
                    132: =pod
1.3       albertel  133: 
1.1       foxr      134: =head2 Debug
1.3       albertel  135: 
                    136: Invoked to issue a debug message.
                    137: 
1.1       foxr      138: =cut
1.3       albertel  139: 
1.1       foxr      140: sub Debug {
                    141:     my $level   = shift;
                    142:     my $message = shift;
                    143:     if ($level <= $DebugLevel) {
                    144: 	print $message." host = ".$RemoteHost."\n";
                    145:     }
                    146: }
                    147: 
                    148: sub SocketDump {
                    149:     my $level = shift;
                    150:     my $socket= shift;
                    151:     if($level <= $DebugLevel) {
                    152: 	$socket->Dump();
                    153:     }
                    154: }
1.3       albertel  155: 
1.1       foxr      156: =pod
1.3       albertel  157: 
1.1       foxr      158: =head2 Tick
1.3       albertel  159: 
                    160: Invoked  each timer tick.
                    161: 
1.1       foxr      162: =cut
                    163: 
                    164: sub Tick {
                    165:     my $client;
1.4     ! foxr      166:     $0 = 'lonc: '.GetServerHost()." Connection count: ".$ConnectionCount;
1.1       foxr      167:     Debug(6, "Tick");
                    168:     Debug(6, "    Current connection count: ".$ConnectionCount);
                    169:     foreach $client (keys %ActiveClients) {
                    170: 	Debug(7, "    Have client:  with id: ".$ActiveClients{$client});
                    171:     }
1.4     ! foxr      172:     # Is it time to prune connection count:
        !           173: 
        !           174: 
        !           175:     if($IdleConnections->Count()  && 
        !           176:        ($WorkQueue->Count() == 0)) { # Idle connections and nothing to do?
        !           177: 	$IdleSeconds++;
        !           178: 	if($IdleSeconds > $IdleTimeout) { # Prune a connection...
        !           179: 	    $Socket = $IdleConnections->pop();
        !           180: 	    KillSocket($Socket, 0);
        !           181: 	}
        !           182:     } else {
        !           183: 	$IdleSeconds = 0;	# Reset idle count if not idle.
        !           184:     }
1.1       foxr      185: }
                    186: 
                    187: =pod
1.3       albertel  188: 
1.1       foxr      189: =head2 SetupTimer
                    190: 
1.3       albertel  191: Sets up a 1 per sec recurring timer event.  The event handler is used to:
1.1       foxr      192: 
1.3       albertel  193: =item
                    194: 
                    195: Trigger timeouts on communications along active sockets.
                    196: 
                    197: =item
                    198: 
                    199: Trigger disconnections of idle sockets.
1.1       foxr      200: 
                    201: =cut
                    202: 
                    203: sub SetupTimer {
                    204:     Debug(6, "SetupTimer");
                    205:     Event->timer(interval => 1, debug => 1, cb => \&Tick );
                    206: }
1.3       albertel  207: 
1.1       foxr      208: =pod
1.3       albertel  209: 
1.1       foxr      210: =head2 ServerToIdle
1.3       albertel  211: 
                    212: This function is called when a connection to the server is
                    213: ready for more work.
                    214: 
                    215: If there is work in the Work queue the top element is dequeued
1.1       foxr      216: and the connection will start to work on it.  If the work queue is
                    217: empty, the connection is pushed on the idle connection stack where
                    218: it will either get another work unit, or alternatively, if it sits there
                    219: long enough, it will be shut down and released.
                    220: 
1.3       albertel  221: =cut
1.1       foxr      222: 
                    223: sub ServerToIdle {
                    224:     my $Socket   = shift;	# Get the socket.
                    225: 
                    226:     &Debug(6, "Server to idle");
                    227: 
                    228:     #  If there's work to do, start the transaction:
                    229: 
                    230:     $reqdata = $WorkQueue->dequeue();
                    231:     Debug(9, "Queue gave request data: ".$reqdata);
                    232:     unless($reqdata eq undef)  {
                    233: 	my $unixSocket = $ClientQueue->dequeue();
                    234: 	&Debug(6, "Starting new work request");
                    235: 	&Debug(7, "Request: ".$reqdata);
                    236: 	
                    237: 	&StartRequest($Socket, $unixSocket, $reqdata);
                    238:     } else {
                    239: 	
                    240:     #  There's no work waiting, so push the server to idle list.
                    241: 	&Debug(8, "No new work requests, server connection going idle");
                    242: 	delete($ActiveTransactions{$Socket});
                    243: 	$IdleConnections->push($Socket);
                    244:     }
                    245: }
1.3       albertel  246: 
1.1       foxr      247: =pod
1.3       albertel  248: 
1.1       foxr      249: =head2 ClientWritable
1.3       albertel  250: 
                    251: Event callback for when a client socket is writable.
                    252: 
                    253: This callback is established when a transaction reponse is
                    254: avaiable from lond.  The response is forwarded to the unix socket
                    255: as it becomes writable in this sub.
                    256: 
1.1       foxr      257: Parameters:
                    258: 
1.3       albertel  259: =item Event
                    260: 
                    261: The event that has been triggered. Event->w->data is
                    262: the data and Event->w->fd is the socket to write.
1.1       foxr      263: 
                    264: =cut
1.3       albertel  265: 
1.1       foxr      266: sub ClientWritable {
                    267:     my $Event    = shift;
                    268:     my $Watcher  = $Event->w;
                    269:     my $Data     = $Watcher->data;
                    270:     my $Socket   = $Watcher->fd;
                    271: 
                    272:     # Try to send the data:
                    273: 
                    274:     &Debug(6, "ClientWritable writing".$Data);
                    275:     &Debug(9, "Socket is: ".$Socket);
                    276: 
                    277:     my $result = $Socket->send($Data, 0);
                    278: 
                    279:     # $result undefined: the write failed.
                    280:     # otherwise $result is the number of bytes written.
                    281:     # Remove that preceding string from the data.
                    282:     # If the resulting data is empty, destroy the watcher
                    283:     # and set up a read event handler to accept the next
                    284:     # request.
                    285: 
                    286:     &Debug(9,"Send result is ".$result." Defined: ".defined($result));
                    287:     if(defined($result)) {
                    288: 	&Debug(9, "send result was defined");
                    289: 	if($result == length($Data)) { # Entire string sent.
                    290: 	    &Debug(9, "ClientWritable data all written");
                    291: 	    $Watcher->cancel();
                    292: 	    #
                    293: 	    #  Set up to read next request from socket:
                    294: 	    
                    295: 	    my $descr     = sprintf("Connection to lonc client %d",
                    296: 				    $ActiveClients{$Socket});
                    297: 	    Event->io(cb    => \&ClientRequest,
                    298: 		      poll  => 'r',
                    299: 		      desc  => $descr,
                    300: 		      data  => "",
                    301: 		      fd    => $Socket);
                    302: 
                    303: 	} else {		# Partial string sent.
                    304: 	    $Watcher->data(substr($Data, $result));
                    305: 	}
                    306: 	
                    307:     } else {			# Error of some sort...
                    308: 
                    309: 	# Some errnos are possible:
                    310: 	my $errno = $!;
                    311: 	if($errno == POSIX::EWOULDBLOCK   ||
                    312: 	   $errno == POSIX::EAGAIN        ||
                    313: 	   $errno == POSIX::EINTR) {
                    314: 	    # No action taken?
                    315: 	} else {		# Unanticipated errno.
                    316: 	    &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost);
                    317: 	    $Watcher->cancel;	# Stop the watcher.
                    318: 	    $Socket->shutdown(2); # Kill connection
                    319: 	    $Socket->close();	# Close the socket.
                    320: 	}
                    321: 	
                    322:     }
                    323: }
                    324: 
                    325: =pod
1.3       albertel  326: 
1.1       foxr      327: =head2 CompleteTransaction
1.3       albertel  328: 
                    329: Called when the reply data has been received for a lond 
1.1       foxr      330: transaction.   The reply data must now be sent to the
                    331: ultimate client on the other end of the Unix socket.  This is
                    332: done by setting up a writable event for the socket with the
                    333: data the reply data.
1.3       albertel  334: 
1.1       foxr      335: Parameters:
1.3       albertel  336: 
                    337: =item Socket
                    338: 
                    339: Socket on which the lond transaction occured.  This is a
                    340: LondConnection. The data received is in the TransactionReply member.
                    341: 
                    342: =item Client
                    343: 
                    344: Unix domain socket open on the ultimate client.
1.1       foxr      345: 
                    346: =cut
1.3       albertel  347: 
1.1       foxr      348: sub CompleteTransaction {
                    349:     &Debug(6,"Complete transaction");
                    350:     my $Socket = shift;
                    351:     my $Client = shift;
                    352: 
                    353:     my $data   = $Socket->GetReply(); # Data to send.
                    354: 
                    355:     &Debug(8," Reply was: ".$data);
                    356:     my $Serial         = $ActiveClients{$Client};
                    357:     my $desc           = sprintf("Connection to lonc client %d",
                    358: 				 $Serial);
                    359:     Event->io(fd       => $Client,
                    360: 	      poll     => "w",
                    361: 	      desc     => $desc,
                    362: 	      cb       => \&ClientWritable,
                    363: 	      data     => $data);
                    364: }
1.4     ! foxr      365: =pod
        !           366: =head2 FailTransaction
        !           367: 
        !           368:   Finishes a transaction with failure because the associated lond socket
        !           369:   disconnected.  It is up to our client to retry if desired.  
        !           370: 
        !           371: Parameters:
        !           372: 
        !           373: =item client  
        !           374:  
        !           375:    The UNIX domain socket open on our client.
        !           376: 
        !           377: =cut
        !           378: 
        !           379: sub FailTransaction {
        !           380:     my $client = shift;
        !           381: 
        !           382:     &Debug(8, "Failing transaction due to disconnect");
        !           383:     my $Serial = $ActiveClients{$client};
        !           384:     my $desc   = sprintf("Connection to lonc client %d", $Serial);
        !           385:     my $data   = "error: Connection to lond lost\n";
        !           386: 
        !           387:     Event->io(fd     => $client,
        !           388: 	      poll   => "w",
        !           389: 	      desc   => $desc,
        !           390: 	      cb     => \&ClientWritable,
        !           391: 	      data   => $data);
        !           392: 
        !           393: }
        !           394: 
        !           395: =pod
        !           396: 
        !           397: =head2 KillSocket
        !           398:  
        !           399: Destroys a socket.  This function can be called either when a socket
        !           400: has died of 'natural' causes or because a socket needs to be pruned due to
        !           401: idleness.  If the socket has died naturally, if there are no longer any 
        !           402: live connections a new connection is created (in case there are transactions
        !           403: in the queue).  If the socket has been pruned, it is never re-created.
        !           404: 
        !           405: Parameters:
1.1       foxr      406: 
1.4     ! foxr      407: =item Socket
        !           408:  
        !           409:   The socket to kill off.
        !           410: 
        !           411: =item Restart
        !           412: 
        !           413: nonzero if we are allowed to create a new connection.
        !           414: 
        !           415: 
        !           416: =cut
        !           417: sub KillSocket {
        !           418:     my $Socket = shift;
        !           419:     my $Restart= shift;
        !           420: 
        !           421:     #  If the socket came from the active connection set, delete it.
        !           422:     # otherwise it came from the idle set and has already been destroyed:
        !           423:     
        !           424:     if(exists($ActiveTransactions{$Socket})) {
        !           425: 	delete ($ActiveTransactions{$Socket});
        !           426:     }
        !           427:     if(exists($ActiveConnections{$Socket})) {
        !           428: 	delete($ActiveConnections{$Socket});
        !           429:     }
        !           430:     $ConnectionCount--;
        !           431:     if( ($ConnectionCount = 0) && ($Restart)) {
        !           432: 	MakeLondConnection();
        !           433:     }
        !           434: 
        !           435: }
1.1       foxr      436: 
                    437: =pod
1.3       albertel  438: 
1.1       foxr      439: =head2 LondReadable
1.3       albertel  440: 
1.1       foxr      441: This function is called whenever a lond connection
                    442: is readable.  The action is state dependent:
                    443: 
1.3       albertel  444: =head3 State=Initialized
                    445: 
                    446: We''re waiting for the challenge, this is a no-op until the
1.1       foxr      447: state changes.
1.3       albertel  448: 
1.1       foxr      449: =head3 State=Challenged 
1.3       albertel  450: 
                    451: The challenge has arrived we need to transition to Writable.
1.1       foxr      452: The connection must echo the challenge back.
1.3       albertel  453: 
1.1       foxr      454: =head3 State=ChallengeReplied
1.3       albertel  455: 
                    456: The challenge has been replied to.  The we are receiveing the 
1.1       foxr      457: 'ok' from the partner.
1.3       albertel  458: 
1.1       foxr      459: =head3 State=RequestingKey
1.3       albertel  460: 
                    461: The ok has been received and we need to send the request for
1.1       foxr      462: an encryption key.  Transition to writable for that.
1.3       albertel  463: 
1.1       foxr      464: =head3 State=ReceivingKey
1.3       albertel  465: 
                    466: The the key has been requested, now we are reading the new key.
                    467: 
1.1       foxr      468: =head3 State=Idle 
1.3       albertel  469: 
                    470: The encryption key has been negotiated or we have finished 
1.1       foxr      471: reading data from the a transaction.   If the callback data has
                    472: a client as well as the socket iformation, then we are 
                    473: doing a transaction and the data received is relayed to the client
                    474: before the socket is put on the idle list.
1.3       albertel  475: 
1.1       foxr      476: =head3 State=SendingRequest
1.3       albertel  477: 
                    478: I do not think this state can be received here, but if it is,
1.1       foxr      479: the appropriate thing to do is to transition to writable, and send
                    480: the request.
1.3       albertel  481: 
1.1       foxr      482: =head3 State=ReceivingReply
1.3       albertel  483: 
                    484: We finished sending the request to the server and now transition
1.1       foxr      485: to readable to receive the reply. 
                    486: 
                    487: The parameter to this function are:
1.3       albertel  488: 
1.1       foxr      489: The event. Implicit in this is the watcher and its data.  The data 
                    490: contains at least the lond connection object and, if a 
                    491: transaction is in progress, the socket attached to the local client.
                    492: 
1.3       albertel  493: =cut
1.1       foxr      494: 
                    495: sub LondReadable {
                    496:     my $Event      = shift;
                    497:     my $Watcher    = $Event->w;
                    498:     my $Socket     = $Watcher->data;
                    499:     my $client     = undef;
                    500: 
                    501: 
                    502:     my $State = $Socket->GetState(); # All action depends on the state.
                    503: 
                    504:     &Debug(6,"LondReadable called state = ".$State);
                    505:     SocketDump(6, $Socket);
                    506: 
                    507:     if($Socket->Readable() != 0) {
1.4     ! foxr      508: 	 # bad return from socket read. Currently this means that
        !           509: 	# The socket has become disconnected. We fail the transaction.
        !           510: 
        !           511: 	if(exists($ActiveTransactions{$Socket})) {
        !           512: 	    Debug(3,"Lond connection lost failing transaction");
        !           513: 	    FailTransaction($ActiveTransactions{$Socket});
        !           514: 	}
        !           515: 	$Watcher->cancel();
        !           516: 	KillSocket($Socket, 1);
        !           517: 	return;
1.1       foxr      518:     }
                    519:     SocketDump(6,$Socket);
                    520: 
                    521:     $State = $Socket->GetState(); # Update in case of transition.
                    522:     &Debug(6, "After read, state is ".$State);
                    523: 
                    524:    if($State eq "Initialized") {
                    525: 
                    526: 
                    527:     } elsif ($State eq "ChallengeReceived") {
                    528: 	#  The challenge must be echoed back;  The state machine
                    529: 	# in the connection takes care of setting that up.  Just
                    530: 	# need to transition to writable:
                    531: 
                    532: 	$Watcher->poll("w");
                    533: 	$Watcher->cb(\&LondWritable);
                    534: 
                    535:     } elsif ($State eq "ChallengeReplied") {
                    536: 
                    537: 
                    538:     } elsif ($State eq "RequestingKey") {
                    539: 	#  The ok was received.  Now we need to request the key
                    540: 	#  That requires us to be writable:
                    541: 
                    542: 	$Watcher->poll("w");
                    543: 	$Watcher->cb(\&LondWritable);
                    544: 
                    545:     } elsif ($State eq "ReceivingKey") {
                    546: 
                    547:     } elsif ($State eq "Idle") {
                    548: 	# If necessary, complete a transaction and then go into the
                    549: 	# idle queue.
                    550: 	if(exists($ActiveTransactions{$Socket})) {
                    551: 	    Debug(8,"Completing transaction!!");
                    552: 	    CompleteTransaction($Socket, 
                    553: 				$ActiveTransactions{$Socket});
                    554: 	}
                    555: 	$Watcher->cancel();
                    556: 	ServerToIdle($Socket);	# Next work unit or idle.
                    557: 
                    558:     } elsif ($State eq "SendingRequest") {
                    559: 	#  We need to be writable for this and probably don't belong
                    560: 	#  here inthe first place.
                    561: 
                    562: 	Deubg(6, "SendingRequest state encountered in readable");
                    563: 	$Watcher->poll("w");
                    564: 	$Watcher->cb(\&LondWritable);
                    565: 
                    566:     } elsif ($State eq "ReceivingReply") {
                    567: 
                    568: 
                    569:     } else {
                    570: 	 # Invalid state.
                    571: 	Debug(4, "Invalid state in LondReadable");
                    572:     }
                    573: }
1.3       albertel  574: 
1.1       foxr      575: =pod
1.3       albertel  576: 
1.1       foxr      577: =head2 LondWritable
1.3       albertel  578: 
1.1       foxr      579: This function is called whenever a lond connection
                    580: becomes writable while there is a writeable monitoring
                    581: event.  The action taken is very state dependent:
1.3       albertel  582: 
1.1       foxr      583: =head3 State = Connected 
1.3       albertel  584: 
                    585: The connection is in the process of sending the 'init' hailing to the
                    586: lond on the remote end.  The connection object''s Writable member is
                    587: called.  On error, ConnectionError is called to destroy the connection
                    588: and remove it from the ActiveConnections hash
                    589: 
1.1       foxr      590: =head3 Initialized
1.3       albertel  591: 
                    592: 'init' has been sent, writability monitoring is removed and
                    593: readability monitoring is started with LondReadable as the callback.
                    594: 
1.1       foxr      595: =head3 ChallengeReceived
1.3       albertel  596: 
                    597: The connection has received the who are you challenge from the remote
                    598: system, and is in the process of sending the challenge
                    599: response. Writable is called.
                    600: 
1.1       foxr      601: =head3 ChallengeReplied
1.3       albertel  602: 
                    603: The connection has replied to the initial challenge The we switch to
                    604: monitoring readability looking for the server to reply with 'ok'.
                    605: 
1.1       foxr      606: =head3 RequestingKey
1.3       albertel  607: 
                    608: The connection is in the process of requesting its encryption key.
                    609: Writable is called.
                    610: 
1.1       foxr      611: =head3 ReceivingKey
1.3       albertel  612: 
                    613: The connection has sent the request for a key.  Switch to readability
                    614: monitoring to accept the key
                    615: 
1.1       foxr      616: =head3 SendingRequest
1.3       albertel  617: 
                    618: The connection is in the process of sending a request to the server.
                    619: This request is part of a client transaction.  All the states until
                    620: now represent the client setup protocol. Writable is called.
                    621: 
1.1       foxr      622: =head3 ReceivingReply
                    623: 
1.3       albertel  624: The connection has sent a request.  Now it must receive a reply.
                    625: Readability monitoring is requested.
                    626: 
                    627: This function is an event handler and therefore receives as
1.1       foxr      628: a parameter the event that has fired.  The data for the watcher
                    629: of this event is a reference to a list of one or two elements,
                    630: depending on state. The first (and possibly only) element is the
                    631: socket.  The second (present only if a request is in progress)
                    632: is the socket on which to return a reply to the caller.
                    633: 
                    634: =cut
1.3       albertel  635: 
1.1       foxr      636: sub LondWritable {
                    637:     my $Event   = shift;
                    638:     my $Watcher = $Event->w;
                    639:     my @data    = $Watcher->data;
                    640:     Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n");
                    641: 
                    642:     my $Socket  = $data[0];	# I know there's at least a socket.
                    643: 
                    644:     #  Figure out what to do depending on the state of the socket:
                    645:     
                    646: 
                    647:     my $State   = $Socket->GetState();
                    648: 
                    649: 
                    650:     SocketDump(6,$Socket);
                    651: 
                    652:     if      ($State eq "Connected")         {
                    653: 
                    654: 	if ($Socket->Writable() != 0) {
                    655: 	    #  The write resulted in an error.
1.4     ! foxr      656: 	    # We'll treat this as if the socket got disconnected:
        !           657: 	    if(exists($ActiveTransactions{$Socket})) {
        !           658: 		Debug(3, "Lond connection lost, failing transactions");
        !           659: 		FailTransaction($ActiveTransactions{$Socket});
        !           660: 	    }
        !           661: 	    $Watcher->cancel();
        !           662: 	    KillSocket($Socket, 1);
        !           663: 	    return;
1.1       foxr      664: 	}
1.4     ! foxr      665: 	#  "init" is being sent...
        !           666: 
1.1       foxr      667: 	
                    668:     } elsif ($State eq "Initialized")       {
                    669: 
                    670: 	# Now that init was sent, we switch 
                    671: 	# to watching for readability:
                    672: 
                    673: 	$Watcher->poll("r");
                    674: 	$Watcher->cb(\&LondReadable);
                    675: 
                    676:     } elsif ($State eq "ChallengeReceived") {
                    677: 	# We received the challenge, now we 
                    678: 	# are echoing it back. This is a no-op,
                    679: 	# we're waiting for the state to change
                    680: 	
                    681: 	if($Socket->Writable() != 0) {
                    682: 	    # Write of the next chunk resulted in an error.
                    683: 	}
                    684: 	
                    685:     } elsif ($State eq "ChallengeReplied")  {
                    686: 	# The echo was sent back, so we switch
                    687: 	# to watching readability.
                    688: 
                    689: 	$Watcher->poll("r");
                    690: 	$Watcher->cb(\&LondReadable);
                    691: 
                    692:     } elsif ($State eq "RequestingKey")     {
                    693: 	# At this time we're requesting the key.
                    694: 	# again, this is essentially a no-op.
                    695: 	# we'll write the next chunk until the
                    696: 	# state changes.
                    697: 
                    698: 	if($Socket->Writable() != 0) {
                    699: 	    # Write resulted in an error.
                    700: 	}
                    701: 
                    702:     } elsif ($State eq "ReceivingKey")      {
                    703: 	# Now we need to wait for the key
                    704: 	# to come back from the peer:
                    705: 
                    706: 	$Watcher->poll("r");
                    707: 	$Watcher->cb(\&LondReadable);
                    708: 
                    709:     } elsif ($State eq "SendingRequest")    {
                    710: 	# At this time we are sending a request to the
                    711: 	# peer... write the next chunk:
                    712: 
                    713: 	if($Socket->Writable() != 0) {
                    714: 	    # Write resulted in an error.
                    715: 
                    716: 	}
                    717: 
                    718:     } elsif ($State eq "ReceivingReply")    {
                    719: 	# The send has completed.  Wait for the
                    720: 	# data to come in for a reply.
                    721: 	Debug(8,"Writable sent request/receiving reply");
                    722: 	$Watcher->poll("r");
                    723: 	$Watcher->cb(\&LondReadable);
                    724: 
                    725:     } else {
                    726: 	#  Control only passes here on an error: 
                    727: 	#  the socket state does not match any
                    728: 	#  of the known states... so an error
                    729: 	#  must be logged.
                    730: 
                    731: 	&Debug(4, "Invalid socket state ".$State."\n");
                    732:     }
                    733:     
                    734: }
                    735: 
                    736: =pod
1.3       albertel  737: 
1.1       foxr      738: =head2 MakeLondConnection
1.3       albertel  739: 
                    740: Create a new lond connection object, and start it towards its initial
                    741: idleness.  Once idle, it becomes elligible to receive transactions
                    742: from the work queue.  If the work queue is not empty when the
                    743: connection is completed and becomes idle, it will dequeue an entry and
                    744: start off on it.
                    745: 
1.1       foxr      746: =cut
1.3       albertel  747: 
1.1       foxr      748: sub MakeLondConnection {     
                    749:     Debug(4,"MakeLondConnection to ".GetServerHost()." on port "
                    750: 	  .GetServerPort());
                    751: 
                    752:     my $Connection = LondConnection->new(&GetServerHost(),
                    753: 					 &GetServerPort());
                    754: 
                    755:     if($Connection == undef) {	# Needs to be more robust later.
                    756: 	die "Failed to make a connection!!".$!."\n";
                    757: 	
                    758:     } 
                    759:     # The connection needs to have writability 
                    760:     # monitored in order to send the init sequence
                    761:     # that starts the whole authentication/key
                    762:     # exchange underway.
                    763:     #
                    764:     my $Socket = $Connection->GetSocket();
                    765:     if($Socket == undef) {
                    766: 	die "did not get a socket from the connection";
                    767:     } else {
                    768: 	&Debug(9,"MakeLondConnection got socket: ".$Socket);
                    769:     }
                    770: 
                    771:     
                    772:     $event = Event->io(fd       => $Socket,
                    773: 		       poll     => 'w',
                    774: 		       cb       => \&LondWritable,
                    775: 		       data     => ($Connection, undef),
                    776: 		       desc => 'Connection to lond server');
1.4     ! foxr      777:     $ActiveConnections{$Connection} = $event;
1.1       foxr      778: 
                    779:     $ConnectionCount++;
                    780:    
                    781:     
                    782: }
1.3       albertel  783: 
1.1       foxr      784: =pod
1.3       albertel  785: 
1.1       foxr      786: =head2 StartRequest
1.3       albertel  787: 
                    788: Starts a lond request going on a specified lond connection.
                    789: parameters are:
                    790: 
                    791: =item $Lond
                    792: 
                    793: Connection to the lond that will send the transaction and receive the
                    794: reply.
                    795: 
                    796: =item $Client
                    797: 
                    798: Connection to the client that is making this request We got the
                    799: request from this socket, and when the request has been relayed to
                    800: lond and we get a reply back from lond it will get sent to this
                    801: socket.
                    802: 
                    803: =item $Request
                    804: 
                    805: The text of the request to send.
                    806: 
1.1       foxr      807: =cut
                    808: 
                    809: sub StartRequest {
                    810:     my $Lond     = shift;
                    811:     my $Client   = shift;
                    812:     my $Request  = shift;
                    813:     
                    814:     Debug(6, "StartRequest: ".$Request);
                    815: 
                    816:     my $Socket = $Lond->GetSocket();
                    817:     
                    818:     $ActiveTransactions{$Lond} = $Client; # Socket to relay to client.
                    819: 
                    820:     $Lond->InitiateTransaction($Request);
                    821:     $event = Event->io(fd      => $Lond->GetSocket(),
                    822: 		       poll    => "w",
                    823: 		       cb      => \&LondWritable,
                    824: 		       data    => $Lond,
                    825: 		       desc    => "lond transaction connection");
                    826:     $ActiveConnections{$Lond} = $event;
                    827:     Debug(8," Start Request made watcher data with ".$event->data."\n");
                    828: }
                    829: 
                    830: =pod
1.3       albertel  831: 
1.1       foxr      832: =head2 QueueTransaction
1.3       albertel  833: 
                    834: If there is an idle lond connection, it is put to work doing this
                    835: transaction.  Otherwise, the transaction is placed in the work queue.
                    836: If placed in the work queue and the maximum number of connections has
                    837: not yet been created, a new connection will be started.  Our goal is
                    838: to eventually have a sufficient number of connections that the work
                    839: queue will typically be empty.  parameters are:
                    840: 
                    841: =item Socket
                    842: 
                    843: open on the lonc client.
                    844: 
                    845: =item Request
                    846: 
                    847: data to send to the lond.
1.1       foxr      848: 
                    849: =cut
1.3       albertel  850: 
1.1       foxr      851: sub QueueTransaction {
                    852:     my $requestSocket = shift;
                    853:     my $requestData   = shift;
                    854: 
                    855:     Debug(6,"QueueTransaction: ".$requestData);
                    856: 
                    857:     my $LondSocket    = $IdleConnections->pop();
                    858:     if(!defined $LondSocket) {	# Need to queue request.
                    859: 	Debug(8,"Must queue...");
                    860: 	$ClientQueue->enqueue($requestSocket);
                    861: 	$WorkQueue->enqueue($requestData);
                    862: 	if($ConnectionCount < $MaxConnectionCount) {
                    863: 	    Debug(4,"Starting additional lond connection");
                    864: 	    MakeLondConnection();
                    865: 	}
                    866:     } else {			# Can start the request:
                    867: 	Debug(8,"Can start...");
                    868: 	StartRequest($LondSocket, $requestSocket, $requestData);
                    869:     }
                    870: }
                    871: 
                    872: #-------------------------- Lonc UNIX socket handling ---------------------
1.3       albertel  873: 
1.1       foxr      874: =pod
1.3       albertel  875: 
1.1       foxr      876: =head2 ClientRequest
1.3       albertel  877: 
                    878: Callback that is called when data can be read from the UNIX domain
                    879: socket connecting us with an apache server process.
1.1       foxr      880: 
                    881: =cut
                    882: 
                    883: sub ClientRequest {
                    884:     Debug(6, "ClientRequest");
                    885:     my $event   = shift;
                    886:     my $watcher = $event->w;
                    887:     my $socket  = $watcher->fd;
                    888:     my $data    = $watcher->data;
                    889:     my $thisread;
                    890: 
                    891:     Debug(9, "  Watcher named: ".$watcher->desc);
                    892: 
                    893:     my $rv = $socket->recv($thisread, POSIX::BUFSIZ, 0);
                    894:     Debug(8, "rcv:  data length = ".length($thisread)
                    895: 	  ." read =".$thisread);
                    896:     unless (defined $rv && length($thisread)) {
                    897: 	 # Likely eof on socket.
                    898: 	Debug(5,"Client Socket closed on lonc for ".$RemoteHost);
                    899: 	close($socket);
                    900: 	$watcher->cancel();
                    901: 	delete($ActiveClients{$socket});
                    902:     }
                    903:     Debug(8,"Data: ".$data." this read: ".$thisread);
                    904:     $data = $data.$thisread;	# Append new data.
                    905:     $watcher->data($data);
                    906:     if($data =~ /(.*\n)/) {	# Request entirely read.
                    907: 	Debug(8, "Complete transaction received: ".$data);
                    908: 	QueueTransaction($socket, $data);
                    909: 	$watcher->cancel();	# Done looking for input data.
                    910:     }
                    911: 
                    912: }
                    913: 
                    914: 
                    915: =pod
1.3       albertel  916: 
1.1       foxr      917: =head2  NewClient
1.3       albertel  918: 
                    919: Callback that is called when a connection is received on the unix
                    920: socket for a new client of lonc.  The callback is parameterized by the
                    921: event.. which is a-priori assumed to be an io event, and therefore has
                    922: an fd member that is the Listener socket.  We Accept the connection
                    923: and register a new event on the readability of that socket:
                    924: 
1.1       foxr      925: =cut
1.3       albertel  926: 
1.1       foxr      927: sub NewClient {
                    928:     Debug(6, "NewClient");
                    929:     my $event      = shift;		# Get the event parameters.
                    930:     my $watcher    = $event->w; 
                    931:     my $socket     = $watcher->fd;	# Get the event' socket.
                    932:     my $connection = $socket->accept();	# Accept the client connection.
                    933:     Debug(8,"Connection request accepted from "
                    934: 	  .GetPeername($connection, AF_UNIX));
                    935: 
                    936: 
                    937:     my $description = sprintf("Connection to lonc client %d",
                    938: 			      $ClientConnection);
                    939:     Debug(9, "Creating event named: ".$description);
                    940:     Event->io(cb      => \&ClientRequest,
                    941: 	      poll    => 'r',
                    942: 	      desc    => $description,
                    943: 	      data    => "",
                    944: 	      fd      => $connection);
                    945:     $ActiveClients{$connection} = $ClientConnection;
                    946:     $ClientConnection++;
                    947: }
1.3       albertel  948: 
                    949: =pod
                    950: 
                    951: =head2 GetLoncSocketPath
                    952: 
                    953: Returns the name of the UNIX socket on which to listen for client
                    954: connections.
1.1       foxr      955: 
                    956: =cut
1.3       albertel  957: 
1.1       foxr      958: sub GetLoncSocketPath {
                    959:     return $UnixSocketDir."/".GetServerHost();
                    960: }
                    961: 
1.3       albertel  962: =pod
                    963: 
                    964: =head2 GetServerHost
                    965: 
                    966: Returns the host whose lond we talk with.
                    967: 
1.1       foxr      968: =cut
1.3       albertel  969: 
1.1       foxr      970: sub GetServerHost {		# Stub - get this from config.
                    971:     return $RemoteHost;		# Setup by the fork.
                    972: }
1.3       albertel  973: 
                    974: =pod
                    975: 
                    976: =head2 GetServerPort
                    977: 
                    978: Returns the lond port number.
                    979: 
1.1       foxr      980: =cut
1.3       albertel  981: 
1.1       foxr      982: sub GetServerPort {		# Stub - get this from config.
                    983:     return $perlvar{londPort};
                    984: }
1.3       albertel  985: 
                    986: =pod
                    987: 
                    988: =head2 SetupLoncListener
                    989: 
                    990: Setup a lonc listener event.  The event is called when the socket
                    991: becomes readable.. that corresponds to the receipt of a new
                    992: connection.  The event handler established will accept the connection
                    993: (creating a communcations channel), that int turn will establish
                    994: another event handler to subess requests.
1.1       foxr      995: 
                    996: =cut
1.3       albertel  997: 
1.1       foxr      998: sub SetupLoncListener {
                    999: 
                   1000:     my $socket;
                   1001:     my $SocketName = GetLoncSocketPath();
                   1002:     unlink($SocketName);
                   1003:     unless ($socket = IO::Socket::UNIX->new(Local  => $SocketName,
                   1004: 					    Listen => 10, 
                   1005: 					    Type   => SOCK_STREAM)) {
                   1006: 	die "Failed to create a lonc listner socket";
                   1007:     }
                   1008:     Event->io(cb     => \&NewClient,
                   1009: 	      poll   => 'r',
                   1010: 	      desc   => 'Lonc listener Unix Socket',
                   1011: 	      fd     => $socket);
                   1012: }
                   1013: 
                   1014: =pod
1.3       albertel 1015: 
1.1       foxr     1016: =head2 ChildProcess
                   1017: 
                   1018: This sub implements a child process for a single lonc daemon.
                   1019: 
                   1020: =cut
                   1021: 
                   1022: sub ChildProcess {
                   1023: 
                   1024:     print "Loncnew\n";
                   1025: 
                   1026:     # For now turn off signals.
                   1027:     
                   1028:     $SIG{QUIT}  = IGNORE;
                   1029:     $SIG{HUP}   = IGNORE;
                   1030:     $SIG{USR1}  = IGNORE;
                   1031:     $SIG{INT}   = IGNORE;
                   1032:     $SIG{CHLD}  = IGNORE;
                   1033:     $SIG{__DIE__}  = IGNORE;
                   1034: 
                   1035:     SetupTimer();
                   1036:     
                   1037:     SetupLoncListener();
                   1038:     
                   1039:     $Event::Debuglevel = $DebugLevel;
                   1040:     
                   1041:     Debug(9, "Making initial lond connection for ".$RemoteHost);
                   1042: 
                   1043: # Setup the initial server connection:
                   1044:     
                   1045:     &MakeLondConnection();
                   1046:     
                   1047:     Debug(9,"Entering event loop");
                   1048:     my $ret = Event::loop();		#  Start the main event loop.
                   1049:     
                   1050:     
                   1051:     die "Main event loop exited!!!";
                   1052: }
                   1053: 
                   1054: #  Create a new child for host passed in:
                   1055: 
                   1056: sub CreateChild {
                   1057:     my $host = shift;
                   1058:     $RemoteHost = $host;
                   1059:     Debug(3, "Forking off child for ".$RemoteHost);
                   1060:     sleep(5);
                   1061:     $pid          = fork;
                   1062:     if($pid) {			# Parent
                   1063: 	$ChildHash{$pid} = $RemoteHost;
                   1064:     } else {			# child.
                   1065: 	ChildProcess;
                   1066:     }
                   1067: 
                   1068: }
                   1069: #
                   1070: #  Parent process logic pass 1:
                   1071: #   For each entry in the hosts table, we will
                   1072: #  fork off an instance of ChildProcess to service the transactions
                   1073: #  to that host.  Each pid will be entered in a global hash
                   1074: #  with the value of the key, the host.
                   1075: #  The parent will then enter a loop to wait for process exits.
                   1076: #  Each exit gets logged and the child gets restarted.
                   1077: #
                   1078: 
                   1079: my $HostIterator = LondConnection::GetHostIterator;
                   1080: while (! $HostIterator->end()) {
                   1081: 
                   1082:     $hostentryref = $HostIterator->get();
                   1083:     CreateChild($hostentryref->[0]);
                   1084:     $HostIterator->next();
                   1085: }
                   1086: 
                   1087: # Maintain the population:
                   1088: 
                   1089: while(1) {
                   1090:     $deadchild = wait();
                   1091:     if(exists $ChildHash{$deadchild}) {	# need to restart.
                   1092: 	$deadhost = $ChildHash{$deadchild};
                   1093: 	delete($ChildHash{$deadchild});
                   1094: 	Debug(4,"Lost child pid= ".$deadchild.
                   1095: 	      "Connected to host ".$deadhost);
                   1096: 	CreateChild($deadhost);
                   1097:     }
                   1098: }
                   1099: 
                   1100: =head1 Theory
1.3       albertel 1101: 
                   1102: The event class is used to build this as a single process with an
                   1103: event driven model.  The following events are handled:
1.1       foxr     1104: 
                   1105: =item UNIX Socket connection Received
                   1106: 
                   1107: =item Request data arrives on UNIX data transfer socket.
                   1108: 
                   1109: =item lond connection becomes writable.
                   1110: 
                   1111: =item timer fires at 1 second intervals.
                   1112: 
                   1113: All sockets are run in non-blocking mode.  Timeouts managed by the timer
                   1114: handler prevents hung connections.
                   1115: 
                   1116: Key data structures:
                   1117: 
1.3       albertel 1118: =item RequestQueue
                   1119: 
                   1120: A queue of requests received from UNIX sockets that are
                   1121: waiting for a chance to be forwarded on a lond connection socket.
                   1122: 
                   1123: =item ActiveConnections
                   1124: 
                   1125: A hash of lond connections that have transactions in process that are
                   1126: available to be timed out.
                   1127: 
                   1128: =item ActiveTransactions
                   1129: 
                   1130: A hash indexed by lond connections that contain the client reply
                   1131: socket for each connection that has an active transaction on it.
                   1132: 
                   1133: =item IdleConnections
                   1134: 
                   1135: A hash of lond connections that have no work to do.  These connections
                   1136: can be closed if they are idle for a long enough time.
1.1       foxr     1137: 
                   1138: =cut

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>