Annotation of loncom/loncnew, revision 1.3

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.3     ! albertel    5: # $Id: loncnew,v 1.2 2003/04/18 02:59:31 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;
                    103: 
                    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;
                    166:     Debug(6, "Tick");
                    167:     Debug(6, "    Current connection count: ".$ConnectionCount);
                    168:     foreach $client (keys %ActiveClients) {
                    169: 	Debug(7, "    Have client:  with id: ".$ActiveClients{$client});
                    170:     }
                    171: }
                    172: 
                    173: =pod
1.3     ! albertel  174: 
1.1       foxr      175: =head2 SetupTimer
                    176: 
1.3     ! albertel  177: Sets up a 1 per sec recurring timer event.  The event handler is used to:
1.1       foxr      178: 
1.3     ! albertel  179: =item
        !           180: 
        !           181: Trigger timeouts on communications along active sockets.
        !           182: 
        !           183: =item
        !           184: 
        !           185: Trigger disconnections of idle sockets.
1.1       foxr      186: 
                    187: =cut
                    188: 
                    189: sub SetupTimer {
                    190:     Debug(6, "SetupTimer");
                    191:     Event->timer(interval => 1, debug => 1, cb => \&Tick );
                    192: }
1.3     ! albertel  193: 
1.1       foxr      194: =pod
1.3     ! albertel  195: 
1.1       foxr      196: =head2 ServerToIdle
1.3     ! albertel  197: 
        !           198: This function is called when a connection to the server is
        !           199: ready for more work.
        !           200: 
        !           201: If there is work in the Work queue the top element is dequeued
1.1       foxr      202: and the connection will start to work on it.  If the work queue is
                    203: empty, the connection is pushed on the idle connection stack where
                    204: it will either get another work unit, or alternatively, if it sits there
                    205: long enough, it will be shut down and released.
                    206: 
1.3     ! albertel  207: =cut
1.1       foxr      208: 
                    209: sub ServerToIdle {
                    210:     my $Socket   = shift;	# Get the socket.
                    211: 
                    212:     &Debug(6, "Server to idle");
                    213: 
                    214:     #  If there's work to do, start the transaction:
                    215: 
                    216:     $reqdata = $WorkQueue->dequeue();
                    217:     Debug(9, "Queue gave request data: ".$reqdata);
                    218:     unless($reqdata eq undef)  {
                    219: 	my $unixSocket = $ClientQueue->dequeue();
                    220: 	&Debug(6, "Starting new work request");
                    221: 	&Debug(7, "Request: ".$reqdata);
                    222: 	
                    223: 	&StartRequest($Socket, $unixSocket, $reqdata);
                    224:     } else {
                    225: 	
                    226:     #  There's no work waiting, so push the server to idle list.
                    227: 	&Debug(8, "No new work requests, server connection going idle");
                    228: 	delete($ActiveTransactions{$Socket});
                    229: 	$IdleConnections->push($Socket);
                    230:     }
                    231: }
1.3     ! albertel  232: 
1.1       foxr      233: =pod
1.3     ! albertel  234: 
1.1       foxr      235: =head2 ClientWritable
1.3     ! albertel  236: 
        !           237: Event callback for when a client socket is writable.
        !           238: 
        !           239: This callback is established when a transaction reponse is
        !           240: avaiable from lond.  The response is forwarded to the unix socket
        !           241: as it becomes writable in this sub.
        !           242: 
1.1       foxr      243: Parameters:
                    244: 
1.3     ! albertel  245: =item Event
        !           246: 
        !           247: The event that has been triggered. Event->w->data is
        !           248: the data and Event->w->fd is the socket to write.
1.1       foxr      249: 
                    250: =cut
1.3     ! albertel  251: 
1.1       foxr      252: sub ClientWritable {
                    253:     my $Event    = shift;
                    254:     my $Watcher  = $Event->w;
                    255:     my $Data     = $Watcher->data;
                    256:     my $Socket   = $Watcher->fd;
                    257: 
                    258:     # Try to send the data:
                    259: 
                    260:     &Debug(6, "ClientWritable writing".$Data);
                    261:     &Debug(9, "Socket is: ".$Socket);
                    262: 
                    263:     my $result = $Socket->send($Data, 0);
                    264: 
                    265:     # $result undefined: the write failed.
                    266:     # otherwise $result is the number of bytes written.
                    267:     # Remove that preceding string from the data.
                    268:     # If the resulting data is empty, destroy the watcher
                    269:     # and set up a read event handler to accept the next
                    270:     # request.
                    271: 
                    272:     &Debug(9,"Send result is ".$result." Defined: ".defined($result));
                    273:     if(defined($result)) {
                    274: 	&Debug(9, "send result was defined");
                    275: 	if($result == length($Data)) { # Entire string sent.
                    276: 	    &Debug(9, "ClientWritable data all written");
                    277: 	    $Watcher->cancel();
                    278: 	    #
                    279: 	    #  Set up to read next request from socket:
                    280: 	    
                    281: 	    my $descr     = sprintf("Connection to lonc client %d",
                    282: 				    $ActiveClients{$Socket});
                    283: 	    Event->io(cb    => \&ClientRequest,
                    284: 		      poll  => 'r',
                    285: 		      desc  => $descr,
                    286: 		      data  => "",
                    287: 		      fd    => $Socket);
                    288: 
                    289: 	} else {		# Partial string sent.
                    290: 	    $Watcher->data(substr($Data, $result));
                    291: 	}
                    292: 	
                    293:     } else {			# Error of some sort...
                    294: 
                    295: 	# Some errnos are possible:
                    296: 	my $errno = $!;
                    297: 	if($errno == POSIX::EWOULDBLOCK   ||
                    298: 	   $errno == POSIX::EAGAIN        ||
                    299: 	   $errno == POSIX::EINTR) {
                    300: 	    # No action taken?
                    301: 	} else {		# Unanticipated errno.
                    302: 	    &Debug(5,"ClientWritable error or peer shutdown".$RemoteHost);
                    303: 	    $Watcher->cancel;	# Stop the watcher.
                    304: 	    $Socket->shutdown(2); # Kill connection
                    305: 	    $Socket->close();	# Close the socket.
                    306: 	}
                    307: 	
                    308:     }
                    309: }
                    310: 
                    311: =pod
1.3     ! albertel  312: 
1.1       foxr      313: =head2 CompleteTransaction
1.3     ! albertel  314: 
        !           315: Called when the reply data has been received for a lond 
1.1       foxr      316: transaction.   The reply data must now be sent to the
                    317: ultimate client on the other end of the Unix socket.  This is
                    318: done by setting up a writable event for the socket with the
                    319: data the reply data.
1.3     ! albertel  320: 
1.1       foxr      321: Parameters:
1.3     ! albertel  322: 
        !           323: =item Socket
        !           324: 
        !           325: Socket on which the lond transaction occured.  This is a
        !           326: LondConnection. The data received is in the TransactionReply member.
        !           327: 
        !           328: =item Client
        !           329: 
        !           330: Unix domain socket open on the ultimate client.
1.1       foxr      331: 
                    332: =cut
1.3     ! albertel  333: 
1.1       foxr      334: sub CompleteTransaction {
                    335:     &Debug(6,"Complete transaction");
                    336:     my $Socket = shift;
                    337:     my $Client = shift;
                    338: 
                    339:     my $data   = $Socket->GetReply(); # Data to send.
                    340: 
                    341:     &Debug(8," Reply was: ".$data);
                    342:     my $Serial         = $ActiveClients{$Client};
                    343:     my $desc           = sprintf("Connection to lonc client %d",
                    344: 				 $Serial);
                    345:     Event->io(fd       => $Client,
                    346: 	      poll     => "w",
                    347: 	      desc     => $desc,
                    348: 	      cb       => \&ClientWritable,
                    349: 	      data     => $data);
                    350: }
                    351: 
                    352: 
                    353: =pod
1.3     ! albertel  354: 
1.1       foxr      355: =head2 LondReadable
1.3     ! albertel  356: 
1.1       foxr      357: This function is called whenever a lond connection
                    358: is readable.  The action is state dependent:
                    359: 
1.3     ! albertel  360: =head3 State=Initialized
        !           361: 
        !           362: We''re waiting for the challenge, this is a no-op until the
1.1       foxr      363: state changes.
1.3     ! albertel  364: 
1.1       foxr      365: =head3 State=Challenged 
1.3     ! albertel  366: 
        !           367: The challenge has arrived we need to transition to Writable.
1.1       foxr      368: The connection must echo the challenge back.
1.3     ! albertel  369: 
1.1       foxr      370: =head3 State=ChallengeReplied
1.3     ! albertel  371: 
        !           372: The challenge has been replied to.  The we are receiveing the 
1.1       foxr      373: 'ok' from the partner.
1.3     ! albertel  374: 
1.1       foxr      375: =head3 State=RequestingKey
1.3     ! albertel  376: 
        !           377: The ok has been received and we need to send the request for
1.1       foxr      378: an encryption key.  Transition to writable for that.
1.3     ! albertel  379: 
1.1       foxr      380: =head3 State=ReceivingKey
1.3     ! albertel  381: 
        !           382: The the key has been requested, now we are reading the new key.
        !           383: 
1.1       foxr      384: =head3 State=Idle 
1.3     ! albertel  385: 
        !           386: The encryption key has been negotiated or we have finished 
1.1       foxr      387: reading data from the a transaction.   If the callback data has
                    388: a client as well as the socket iformation, then we are 
                    389: doing a transaction and the data received is relayed to the client
                    390: before the socket is put on the idle list.
1.3     ! albertel  391: 
1.1       foxr      392: =head3 State=SendingRequest
1.3     ! albertel  393: 
        !           394: I do not think this state can be received here, but if it is,
1.1       foxr      395: the appropriate thing to do is to transition to writable, and send
                    396: the request.
1.3     ! albertel  397: 
1.1       foxr      398: =head3 State=ReceivingReply
1.3     ! albertel  399: 
        !           400: We finished sending the request to the server and now transition
1.1       foxr      401: to readable to receive the reply. 
                    402: 
                    403: The parameter to this function are:
1.3     ! albertel  404: 
1.1       foxr      405: The event. Implicit in this is the watcher and its data.  The data 
                    406: contains at least the lond connection object and, if a 
                    407: transaction is in progress, the socket attached to the local client.
                    408: 
1.3     ! albertel  409: =cut
1.1       foxr      410: 
                    411: sub LondReadable {
                    412:     my $Event      = shift;
                    413:     my $Watcher    = $Event->w;
                    414:     my $Socket     = $Watcher->data;
                    415:     my $client     = undef;
                    416: 
                    417: 
                    418:     my $State = $Socket->GetState(); # All action depends on the state.
                    419: 
                    420:     &Debug(6,"LondReadable called state = ".$State);
                    421:     SocketDump(6, $Socket);
                    422: 
                    423:     if($Socket->Readable() != 0) {
                    424: 	 # bad return from socket read.
                    425:     }
                    426:     SocketDump(6,$Socket);
                    427: 
                    428:     $State = $Socket->GetState(); # Update in case of transition.
                    429:     &Debug(6, "After read, state is ".$State);
                    430: 
                    431:    if($State eq "Initialized") {
                    432: 
                    433: 
                    434:     } elsif ($State eq "ChallengeReceived") {
                    435: 	#  The challenge must be echoed back;  The state machine
                    436: 	# in the connection takes care of setting that up.  Just
                    437: 	# need to transition to writable:
                    438: 
                    439: 	$Watcher->poll("w");
                    440: 	$Watcher->cb(\&LondWritable);
                    441: 
                    442:     } elsif ($State eq "ChallengeReplied") {
                    443: 
                    444: 
                    445:     } elsif ($State eq "RequestingKey") {
                    446: 	#  The ok was received.  Now we need to request the key
                    447: 	#  That requires us to be writable:
                    448: 
                    449: 	$Watcher->poll("w");
                    450: 	$Watcher->cb(\&LondWritable);
                    451: 
                    452:     } elsif ($State eq "ReceivingKey") {
                    453: 
                    454:     } elsif ($State eq "Idle") {
                    455: 	# If necessary, complete a transaction and then go into the
                    456: 	# idle queue.
                    457: 	if(exists($ActiveTransactions{$Socket})) {
                    458: 	    Debug(8,"Completing transaction!!");
                    459: 	    CompleteTransaction($Socket, 
                    460: 				$ActiveTransactions{$Socket});
                    461: 	}
                    462: 	$Watcher->cancel();
                    463: 	ServerToIdle($Socket);	# Next work unit or idle.
                    464: 
                    465:     } elsif ($State eq "SendingRequest") {
                    466: 	#  We need to be writable for this and probably don't belong
                    467: 	#  here inthe first place.
                    468: 
                    469: 	Deubg(6, "SendingRequest state encountered in readable");
                    470: 	$Watcher->poll("w");
                    471: 	$Watcher->cb(\&LondWritable);
                    472: 
                    473:     } elsif ($State eq "ReceivingReply") {
                    474: 
                    475: 
                    476:     } else {
                    477: 	 # Invalid state.
                    478: 	Debug(4, "Invalid state in LondReadable");
                    479:     }
                    480: }
1.3     ! albertel  481: 
1.1       foxr      482: =pod
1.3     ! albertel  483: 
1.1       foxr      484: =head2 LondWritable
1.3     ! albertel  485: 
1.1       foxr      486: This function is called whenever a lond connection
                    487: becomes writable while there is a writeable monitoring
                    488: event.  The action taken is very state dependent:
1.3     ! albertel  489: 
1.1       foxr      490: =head3 State = Connected 
1.3     ! albertel  491: 
        !           492: The connection is in the process of sending the 'init' hailing to the
        !           493: lond on the remote end.  The connection object''s Writable member is
        !           494: called.  On error, ConnectionError is called to destroy the connection
        !           495: and remove it from the ActiveConnections hash
        !           496: 
1.1       foxr      497: =head3 Initialized
1.3     ! albertel  498: 
        !           499: 'init' has been sent, writability monitoring is removed and
        !           500: readability monitoring is started with LondReadable as the callback.
        !           501: 
1.1       foxr      502: =head3 ChallengeReceived
1.3     ! albertel  503: 
        !           504: The connection has received the who are you challenge from the remote
        !           505: system, and is in the process of sending the challenge
        !           506: response. Writable is called.
        !           507: 
1.1       foxr      508: =head3 ChallengeReplied
1.3     ! albertel  509: 
        !           510: The connection has replied to the initial challenge The we switch to
        !           511: monitoring readability looking for the server to reply with 'ok'.
        !           512: 
1.1       foxr      513: =head3 RequestingKey
1.3     ! albertel  514: 
        !           515: The connection is in the process of requesting its encryption key.
        !           516: Writable is called.
        !           517: 
1.1       foxr      518: =head3 ReceivingKey
1.3     ! albertel  519: 
        !           520: The connection has sent the request for a key.  Switch to readability
        !           521: monitoring to accept the key
        !           522: 
1.1       foxr      523: =head3 SendingRequest
1.3     ! albertel  524: 
        !           525: The connection is in the process of sending a request to the server.
        !           526: This request is part of a client transaction.  All the states until
        !           527: now represent the client setup protocol. Writable is called.
        !           528: 
1.1       foxr      529: =head3 ReceivingReply
                    530: 
1.3     ! albertel  531: The connection has sent a request.  Now it must receive a reply.
        !           532: Readability monitoring is requested.
        !           533: 
        !           534: This function is an event handler and therefore receives as
1.1       foxr      535: a parameter the event that has fired.  The data for the watcher
                    536: of this event is a reference to a list of one or two elements,
                    537: depending on state. The first (and possibly only) element is the
                    538: socket.  The second (present only if a request is in progress)
                    539: is the socket on which to return a reply to the caller.
                    540: 
                    541: =cut
1.3     ! albertel  542: 
1.1       foxr      543: sub LondWritable {
                    544:     my $Event   = shift;
                    545:     my $Watcher = $Event->w;
                    546:     my @data    = $Watcher->data;
                    547:     Debug(6,"LondWritable State = ".$State." data has ".@data." elts.\n");
                    548: 
                    549:     my $Socket  = $data[0];	# I know there's at least a socket.
                    550: 
                    551:     #  Figure out what to do depending on the state of the socket:
                    552:     
                    553: 
                    554:     my $State   = $Socket->GetState();
                    555: 
                    556: 
                    557:     SocketDump(6,$Socket);
                    558: 
                    559:     if      ($State eq "Connected")         {
                    560: 	#  "init" is being sent...
                    561: 
                    562: 	if ($Socket->Writable() != 0) {
                    563: 	    #  The write resulted in an error.
                    564: 	}
                    565: 	
                    566:     } elsif ($State eq "Initialized")       {
                    567: 
                    568: 	# Now that init was sent, we switch 
                    569: 	# to watching for readability:
                    570: 
                    571: 	$Watcher->poll("r");
                    572: 	$Watcher->cb(\&LondReadable);
                    573: 
                    574:     } elsif ($State eq "ChallengeReceived") {
                    575: 	# We received the challenge, now we 
                    576: 	# are echoing it back. This is a no-op,
                    577: 	# we're waiting for the state to change
                    578: 	
                    579: 	if($Socket->Writable() != 0) {
                    580: 	    # Write of the next chunk resulted in an error.
                    581: 	}
                    582: 	
                    583:     } elsif ($State eq "ChallengeReplied")  {
                    584: 	# The echo was sent back, so we switch
                    585: 	# to watching readability.
                    586: 
                    587: 	$Watcher->poll("r");
                    588: 	$Watcher->cb(\&LondReadable);
                    589: 
                    590:     } elsif ($State eq "RequestingKey")     {
                    591: 	# At this time we're requesting the key.
                    592: 	# again, this is essentially a no-op.
                    593: 	# we'll write the next chunk until the
                    594: 	# state changes.
                    595: 
                    596: 	if($Socket->Writable() != 0) {
                    597: 	    # Write resulted in an error.
                    598: 	}
                    599: 
                    600:     } elsif ($State eq "ReceivingKey")      {
                    601: 	# Now we need to wait for the key
                    602: 	# to come back from the peer:
                    603: 
                    604: 	$Watcher->poll("r");
                    605: 	$Watcher->cb(\&LondReadable);
                    606: 
                    607:     } elsif ($State eq "SendingRequest")    {
                    608: 	# At this time we are sending a request to the
                    609: 	# peer... write the next chunk:
                    610: 
                    611: 	if($Socket->Writable() != 0) {
                    612: 	    # Write resulted in an error.
                    613: 
                    614: 	}
                    615: 
                    616:     } elsif ($State eq "ReceivingReply")    {
                    617: 	# The send has completed.  Wait for the
                    618: 	# data to come in for a reply.
                    619: 	Debug(8,"Writable sent request/receiving reply");
                    620: 	$Watcher->poll("r");
                    621: 	$Watcher->cb(\&LondReadable);
                    622: 
                    623:     } else {
                    624: 	#  Control only passes here on an error: 
                    625: 	#  the socket state does not match any
                    626: 	#  of the known states... so an error
                    627: 	#  must be logged.
                    628: 
                    629: 	&Debug(4, "Invalid socket state ".$State."\n");
                    630:     }
                    631:     
                    632: }
                    633: 
                    634: =pod
1.3     ! albertel  635: 
1.1       foxr      636: =head2 MakeLondConnection
1.3     ! albertel  637: 
        !           638: Create a new lond connection object, and start it towards its initial
        !           639: idleness.  Once idle, it becomes elligible to receive transactions
        !           640: from the work queue.  If the work queue is not empty when the
        !           641: connection is completed and becomes idle, it will dequeue an entry and
        !           642: start off on it.
        !           643: 
1.1       foxr      644: =cut
1.3     ! albertel  645: 
1.1       foxr      646: sub MakeLondConnection {     
                    647:     Debug(4,"MakeLondConnection to ".GetServerHost()." on port "
                    648: 	  .GetServerPort());
                    649: 
                    650:     my $Connection = LondConnection->new(&GetServerHost(),
                    651: 					 &GetServerPort());
                    652: 
                    653:     if($Connection == undef) {	# Needs to be more robust later.
                    654: 	die "Failed to make a connection!!".$!."\n";
                    655: 	
                    656:     } 
                    657:     # The connection needs to have writability 
                    658:     # monitored in order to send the init sequence
                    659:     # that starts the whole authentication/key
                    660:     # exchange underway.
                    661:     #
                    662:     my $Socket = $Connection->GetSocket();
                    663:     if($Socket == undef) {
                    664: 	die "did not get a socket from the connection";
                    665:     } else {
                    666: 	&Debug(9,"MakeLondConnection got socket: ".$Socket);
                    667:     }
                    668: 
                    669:     
                    670:     $event = Event->io(fd       => $Socket,
                    671: 		       poll     => 'w',
                    672: 		       cb       => \&LondWritable,
                    673: 		       data     => ($Connection, undef),
                    674: 		       desc => 'Connection to lond server');
                    675:     $ActiveConnections{$Lond} = $event;
                    676: 
                    677:     $ConnectionCount++;
                    678:    
                    679:     
                    680: }
1.3     ! albertel  681: 
1.1       foxr      682: =pod
1.3     ! albertel  683: 
1.1       foxr      684: =head2 StartRequest
1.3     ! albertel  685: 
        !           686: Starts a lond request going on a specified lond connection.
        !           687: parameters are:
        !           688: 
        !           689: =item $Lond
        !           690: 
        !           691: Connection to the lond that will send the transaction and receive the
        !           692: reply.
        !           693: 
        !           694: =item $Client
        !           695: 
        !           696: Connection to the client that is making this request We got the
        !           697: request from this socket, and when the request has been relayed to
        !           698: lond and we get a reply back from lond it will get sent to this
        !           699: socket.
        !           700: 
        !           701: =item $Request
        !           702: 
        !           703: The text of the request to send.
        !           704: 
1.1       foxr      705: =cut
                    706: 
                    707: sub StartRequest {
                    708:     my $Lond     = shift;
                    709:     my $Client   = shift;
                    710:     my $Request  = shift;
                    711:     
                    712:     Debug(6, "StartRequest: ".$Request);
                    713: 
                    714:     my $Socket = $Lond->GetSocket();
                    715:     
                    716:     $ActiveTransactions{$Lond} = $Client; # Socket to relay to client.
                    717: 
                    718:     $Lond->InitiateTransaction($Request);
                    719:     $event = Event->io(fd      => $Lond->GetSocket(),
                    720: 		       poll    => "w",
                    721: 		       cb      => \&LondWritable,
                    722: 		       data    => $Lond,
                    723: 		       desc    => "lond transaction connection");
                    724:     $ActiveConnections{$Lond} = $event;
                    725:     Debug(8," Start Request made watcher data with ".$event->data."\n");
                    726: }
                    727: 
                    728: =pod
1.3     ! albertel  729: 
1.1       foxr      730: =head2 QueueTransaction
1.3     ! albertel  731: 
        !           732: If there is an idle lond connection, it is put to work doing this
        !           733: transaction.  Otherwise, the transaction is placed in the work queue.
        !           734: If placed in the work queue and the maximum number of connections has
        !           735: not yet been created, a new connection will be started.  Our goal is
        !           736: to eventually have a sufficient number of connections that the work
        !           737: queue will typically be empty.  parameters are:
        !           738: 
        !           739: =item Socket
        !           740: 
        !           741: open on the lonc client.
        !           742: 
        !           743: =item Request
        !           744: 
        !           745: data to send to the lond.
1.1       foxr      746: 
                    747: =cut
1.3     ! albertel  748: 
1.1       foxr      749: sub QueueTransaction {
                    750:     my $requestSocket = shift;
                    751:     my $requestData   = shift;
                    752: 
                    753:     Debug(6,"QueueTransaction: ".$requestData);
                    754: 
                    755:     my $LondSocket    = $IdleConnections->pop();
                    756:     if(!defined $LondSocket) {	# Need to queue request.
                    757: 	Debug(8,"Must queue...");
                    758: 	$ClientQueue->enqueue($requestSocket);
                    759: 	$WorkQueue->enqueue($requestData);
                    760: 	if($ConnectionCount < $MaxConnectionCount) {
                    761: 	    Debug(4,"Starting additional lond connection");
                    762: 	    MakeLondConnection();
                    763: 	}
                    764:     } else {			# Can start the request:
                    765: 	Debug(8,"Can start...");
                    766: 	StartRequest($LondSocket, $requestSocket, $requestData);
                    767:     }
                    768: }
                    769: 
                    770: #-------------------------- Lonc UNIX socket handling ---------------------
1.3     ! albertel  771: 
1.1       foxr      772: =pod
1.3     ! albertel  773: 
1.1       foxr      774: =head2 ClientRequest
1.3     ! albertel  775: 
        !           776: Callback that is called when data can be read from the UNIX domain
        !           777: socket connecting us with an apache server process.
1.1       foxr      778: 
                    779: =cut
                    780: 
                    781: sub ClientRequest {
                    782:     Debug(6, "ClientRequest");
                    783:     my $event   = shift;
                    784:     my $watcher = $event->w;
                    785:     my $socket  = $watcher->fd;
                    786:     my $data    = $watcher->data;
                    787:     my $thisread;
                    788: 
                    789:     Debug(9, "  Watcher named: ".$watcher->desc);
                    790: 
                    791:     my $rv = $socket->recv($thisread, POSIX::BUFSIZ, 0);
                    792:     Debug(8, "rcv:  data length = ".length($thisread)
                    793: 	  ." read =".$thisread);
                    794:     unless (defined $rv && length($thisread)) {
                    795: 	 # Likely eof on socket.
                    796: 	Debug(5,"Client Socket closed on lonc for ".$RemoteHost);
                    797: 	close($socket);
                    798: 	$watcher->cancel();
                    799: 	delete($ActiveClients{$socket});
                    800:     }
                    801:     Debug(8,"Data: ".$data." this read: ".$thisread);
                    802:     $data = $data.$thisread;	# Append new data.
                    803:     $watcher->data($data);
                    804:     if($data =~ /(.*\n)/) {	# Request entirely read.
                    805: 	Debug(8, "Complete transaction received: ".$data);
                    806: 	QueueTransaction($socket, $data);
                    807: 	$watcher->cancel();	# Done looking for input data.
                    808:     }
                    809: 
                    810: }
                    811: 
                    812: 
                    813: =pod
1.3     ! albertel  814: 
1.1       foxr      815: =head2  NewClient
1.3     ! albertel  816: 
        !           817: Callback that is called when a connection is received on the unix
        !           818: socket for a new client of lonc.  The callback is parameterized by the
        !           819: event.. which is a-priori assumed to be an io event, and therefore has
        !           820: an fd member that is the Listener socket.  We Accept the connection
        !           821: and register a new event on the readability of that socket:
        !           822: 
1.1       foxr      823: =cut
1.3     ! albertel  824: 
1.1       foxr      825: sub NewClient {
                    826:     Debug(6, "NewClient");
                    827:     my $event      = shift;		# Get the event parameters.
                    828:     my $watcher    = $event->w; 
                    829:     my $socket     = $watcher->fd;	# Get the event' socket.
                    830:     my $connection = $socket->accept();	# Accept the client connection.
                    831:     Debug(8,"Connection request accepted from "
                    832: 	  .GetPeername($connection, AF_UNIX));
                    833: 
                    834: 
                    835:     my $description = sprintf("Connection to lonc client %d",
                    836: 			      $ClientConnection);
                    837:     Debug(9, "Creating event named: ".$description);
                    838:     Event->io(cb      => \&ClientRequest,
                    839: 	      poll    => 'r',
                    840: 	      desc    => $description,
                    841: 	      data    => "",
                    842: 	      fd      => $connection);
                    843:     $ActiveClients{$connection} = $ClientConnection;
                    844:     $ClientConnection++;
                    845: }
1.3     ! albertel  846: 
        !           847: =pod
        !           848: 
        !           849: =head2 GetLoncSocketPath
        !           850: 
        !           851: Returns the name of the UNIX socket on which to listen for client
        !           852: connections.
1.1       foxr      853: 
                    854: =cut
1.3     ! albertel  855: 
1.1       foxr      856: sub GetLoncSocketPath {
                    857:     return $UnixSocketDir."/".GetServerHost();
                    858: }
                    859: 
1.3     ! albertel  860: =pod
        !           861: 
        !           862: =head2 GetServerHost
        !           863: 
        !           864: Returns the host whose lond we talk with.
        !           865: 
1.1       foxr      866: =cut
1.3     ! albertel  867: 
1.1       foxr      868: sub GetServerHost {		# Stub - get this from config.
                    869:     return $RemoteHost;		# Setup by the fork.
                    870: }
1.3     ! albertel  871: 
        !           872: =pod
        !           873: 
        !           874: =head2 GetServerPort
        !           875: 
        !           876: Returns the lond port number.
        !           877: 
1.1       foxr      878: =cut
1.3     ! albertel  879: 
1.1       foxr      880: sub GetServerPort {		# Stub - get this from config.
                    881:     return $perlvar{londPort};
                    882: }
1.3     ! albertel  883: 
        !           884: =pod
        !           885: 
        !           886: =head2 SetupLoncListener
        !           887: 
        !           888: Setup a lonc listener event.  The event is called when the socket
        !           889: becomes readable.. that corresponds to the receipt of a new
        !           890: connection.  The event handler established will accept the connection
        !           891: (creating a communcations channel), that int turn will establish
        !           892: another event handler to subess requests.
1.1       foxr      893: 
                    894: =cut
1.3     ! albertel  895: 
1.1       foxr      896: sub SetupLoncListener {
                    897: 
                    898:     my $socket;
                    899:     my $SocketName = GetLoncSocketPath();
                    900:     unlink($SocketName);
                    901:     unless ($socket = IO::Socket::UNIX->new(Local  => $SocketName,
                    902: 					    Listen => 10, 
                    903: 					    Type   => SOCK_STREAM)) {
                    904: 	die "Failed to create a lonc listner socket";
                    905:     }
                    906:     Event->io(cb     => \&NewClient,
                    907: 	      poll   => 'r',
                    908: 	      desc   => 'Lonc listener Unix Socket',
                    909: 	      fd     => $socket);
                    910: }
                    911: 
                    912: =pod
1.3     ! albertel  913: 
1.1       foxr      914: =head2 ChildProcess
                    915: 
                    916: This sub implements a child process for a single lonc daemon.
                    917: 
                    918: =cut
                    919: 
                    920: sub ChildProcess {
                    921: 
                    922:     print "Loncnew\n";
                    923: 
                    924:     # For now turn off signals.
                    925:     
                    926:     $SIG{QUIT}  = IGNORE;
                    927:     $SIG{HUP}   = IGNORE;
                    928:     $SIG{USR1}  = IGNORE;
                    929:     $SIG{INT}   = IGNORE;
                    930:     $SIG{CHLD}  = IGNORE;
                    931:     $SIG{__DIE__}  = IGNORE;
                    932: 
                    933:     SetupTimer();
                    934:     
                    935:     SetupLoncListener();
                    936:     
                    937:     $Event::Debuglevel = $DebugLevel;
                    938:     
                    939:     Debug(9, "Making initial lond connection for ".$RemoteHost);
                    940: 
                    941: # Setup the initial server connection:
                    942:     
                    943:     &MakeLondConnection();
                    944:     
                    945:     Debug(9,"Entering event loop");
                    946:     my $ret = Event::loop();		#  Start the main event loop.
                    947:     
                    948:     
                    949:     die "Main event loop exited!!!";
                    950: }
                    951: 
                    952: #  Create a new child for host passed in:
                    953: 
                    954: sub CreateChild {
                    955:     my $host = shift;
                    956:     $RemoteHost = $host;
                    957:     Debug(3, "Forking off child for ".$RemoteHost);
                    958:     sleep(5);
                    959:     $pid          = fork;
                    960:     if($pid) {			# Parent
                    961: 	$ChildHash{$pid} = $RemoteHost;
                    962:     } else {			# child.
                    963: 	ChildProcess;
                    964:     }
                    965: 
                    966: }
                    967: #
                    968: #  Parent process logic pass 1:
                    969: #   For each entry in the hosts table, we will
                    970: #  fork off an instance of ChildProcess to service the transactions
                    971: #  to that host.  Each pid will be entered in a global hash
                    972: #  with the value of the key, the host.
                    973: #  The parent will then enter a loop to wait for process exits.
                    974: #  Each exit gets logged and the child gets restarted.
                    975: #
                    976: 
                    977: my $HostIterator = LondConnection::GetHostIterator;
                    978: while (! $HostIterator->end()) {
                    979: 
                    980:     $hostentryref = $HostIterator->get();
                    981:     CreateChild($hostentryref->[0]);
                    982:     $HostIterator->next();
                    983: }
                    984: 
                    985: # Maintain the population:
                    986: 
                    987: while(1) {
                    988:     $deadchild = wait();
                    989:     if(exists $ChildHash{$deadchild}) {	# need to restart.
                    990: 	$deadhost = $ChildHash{$deadchild};
                    991: 	delete($ChildHash{$deadchild});
                    992: 	Debug(4,"Lost child pid= ".$deadchild.
                    993: 	      "Connected to host ".$deadhost);
                    994: 	CreateChild($deadhost);
                    995:     }
                    996: }
                    997: 
                    998: =head1 Theory
1.3     ! albertel  999: 
        !          1000: The event class is used to build this as a single process with an
        !          1001: event driven model.  The following events are handled:
1.1       foxr     1002: 
                   1003: =item UNIX Socket connection Received
                   1004: 
                   1005: =item Request data arrives on UNIX data transfer socket.
                   1006: 
                   1007: =item lond connection becomes writable.
                   1008: 
                   1009: =item timer fires at 1 second intervals.
                   1010: 
                   1011: All sockets are run in non-blocking mode.  Timeouts managed by the timer
                   1012: handler prevents hung connections.
                   1013: 
                   1014: Key data structures:
                   1015: 
1.3     ! albertel 1016: =item RequestQueue
        !          1017: 
        !          1018: A queue of requests received from UNIX sockets that are
        !          1019: waiting for a chance to be forwarded on a lond connection socket.
        !          1020: 
        !          1021: =item ActiveConnections
        !          1022: 
        !          1023: A hash of lond connections that have transactions in process that are
        !          1024: available to be timed out.
        !          1025: 
        !          1026: =item ActiveTransactions
        !          1027: 
        !          1028: A hash indexed by lond connections that contain the client reply
        !          1029: socket for each connection that has an active transaction on it.
        !          1030: 
        !          1031: =item IdleConnections
        !          1032: 
        !          1033: A hash of lond connections that have no work to do.  These connections
        !          1034: can be closed if they are idle for a long enough time.
1.1       foxr     1035: 
                   1036: =cut

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