Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Should I use threads? Perl/DHCP/Radius

by Anonymous Monk
on Aug 23, 2010 at 16:05 UTC ( [id://856740]=perlquestion: print w/replies, xml ) Need Help??

Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear monks ,

I have a working script which does the following.

  1. Reads /var/log/messages in real time for DHCP leases and extracts the MAC
  2. Uses the MAC to contact a http API (through LWP) which responds with status 200 (yes) or 404(no) . This decides if the MAC is allowed to do a RADIUS Auth.
  3. If 200, then contact a local RADIUS server through a http script using LWP and do a RADIUS Auth.
  4. Independent of DHCP lease: Every 5 minutes, check (another http call through LWP ) which MACs are offline and delete them.

Throughout the script, I use a single %clients hash that monitors all the MACs. For example, after step 1 above , I check if the MAC exists in the %clients hash first. If it exists and has a value 0, this MAC has failed Radius Auth earlier. I prevent it from going to step 2 in this case. If it exists and has a value 1, this MAC has passed RAdius Auth earlier and therefore I avoid step 2 and jump to step 3 directly for a new session.

All this works fine. Tested and all. Now , here is where I am unclear about. Is it possible to make the above faster through threads? What if I use threads to do the call to the API and the local RADIUS server. Will it speed up my process when I enable new DHCP leases to create new threads for the API and the RADIUS call.

Here is a small script i wrote to test the API call part using threads. It is a little bit different to the status codes of %clients mentioned earlier. But the concept is the same.

#!/usr/bin/perl use strict; use LWP::UserAgent; use HTTP::Request; use threads; use threads::shared; use strict; # A list of MACs extracted from DHCP leases. my @maclist = qw ( 7D:6D:62:C6:B4:3D 01:12:79:3E:14:2E 80:27:E4:EA:60: +74 E8:06:88:7F:8C:83 01:26:08:E8:B6:5D 34:15:9E:5D:E6:49 03:1E:64:CE:25: +88 01:0C:29:3F:67:1B 01:22:69:76:5D:F4 64:B9:E8:10:BF:20 01:1D:FE:D +4:7F:E0 ); my %clients :shared; foreach my $mac (@maclist){ async( sub{ my $macAuthURL = "https://XXXXXXXX/api/macCheck/"; my ($request, $ua, $response, $respcon, $rval); my ($returnkey,$returnval); print "MAC: ".$mac." Contacting $macAuthURL$mac.\n"; print "This is ".threads->tid()."\n"; # Wrap call to API with eval eval { local $SIG{ALRM} = sub { die "Alarm\n" }; alarm(16); my $macr = $macAuthURL.$mac; $request = HTTP::Request->new(GET => $macr); $ua = LWP::UserAgent->new(); $response = $ua->request($request); $respcon = $response->content(); alarm(0); }; # We check the response object's status if($response->status_line =~ /^500/ ){ print "MAC: ".$mac." Network Delay/Error."; { lock (%clients); $clients{$mac} = 0; } } print "MAC: ".$mac." Server response: $respcon \n\n\n" ; # All OK #Ensure that sent mac is returned back on success. if( $response->{_rc} == 200 ){ $response->{_content} =~ s/[\{\} \"]//g; ($returnkey,$returnval) = split(/:/,$response->{_c +ontent},2); print "MAC: ".$mac." returned with status: $respo +nse->{_rc} "; if( $mac eq uc($returnval) ){ { lock (%clients); $clients{$mac} = 1; } }else{ # Malformed URL { lock (%clients); $clients{$mac} = 0; } } }else{ { lock (%clients); $clients{$mac} = 0; } } sleep 1; }, $_ )->join; # async block join. #)->detach; }

JOIN:
The join part is where I am having trouble. What I want to have is that as several DHCP leases come in a short time, I create threads for each MAC that check with the API without waiting for the earlier MAC . That is, as MAC1 and MAC2 arrive close to each other in the DHCP lease, I do not want the script to wait for the response from the API for MAC1. I want the script to launch a thread to make a call to the API for MAC2 even as MAC1 is getting processed and is updating %clients. The join , however , waits for MAC1 to finish before starting off MAC2. That makes it sequential!. join is blocking until the first thread completes.
DETACH:
But detach does not seem to help either. With detach, the eval block does not seem to complete at all. And naturally, ehen I print the %clients hash at the end, all MACS have no value

How can I make several MACs make calls to the API concurrently and all updating the %clients hash safely. Am I even attempting a threadable problem? I have spent quite some time reading perlmonks, perldocs to no avail.

I do hope I made myself clear.

Replies are listed 'Best First'.
Re: Should I use threads? Perl/DHCP/Radius
by BrowserUk (Patriarch) on Aug 23, 2010 at 16:28 UTC

    I think your main problem is that you are using async. It is useful for small blocks of code , but once the subroutine gets to your level of complexity, it is better to name it and use threads->create.

    Once you make that change, it then becomes clear that you have a third option to immediately calling either join() or detach(). You can simply save the thread handles and defer that until later. See how you get on with this:

    #!/usr/bin/perl use strict; use LWP::UserAgent; use HTTP::Request; use threads; use threads::shared; use strict; my %clients :shared; sub doit{ my $mac = shift; my $macAuthURL = "https://XXXXXXXX/api/macCheck/"; my ($request, $ua, $response, $respcon, $rval); my ($returnkey,$returnval); print "MAC: ".$mac." Contacting $macAuthURL$mac.\n"; print "This is ".threads->tid()."\n"; # Wrap call to API with eval eval { local $SIG{ALRM} = sub { die "Alarm\n" }; alarm(16); my $macr = $macAuthURL.$mac; $request = HTTP::Request->new(GET => $macr); $ua = LWP::UserAgent->new(); $response = $ua->request($request); $respcon = $response->content(); alarm(0); }; # We check the response object's status if($response->status_line =~ /^500/ ){ print "MAC: ".$mac." Network Delay/Error."; { lock (%clients); $clients{$mac} = 0; } } print "MAC: ".$mac." Server response: $respcon \n\n\n" ; # All OK - Ensure that sent mac is returned back on success. if( $response->{_rc} == 200 ){ $response->{_content} =~ s/[\{\} \"]//g; ( $returnkey, $returnval ) = split(/:/,$response->{_content},2 +); print "MAC: ".$mac." returned with status: $response->{_rc} " +; if( $mac eq uc($returnval) ){ { lock (%clients); $clients{$mac} = 1; } } else{ # Malformed URL { lock (%clients); $clients{$mac} = 0; } } } else{ { lock (%clients); $clients{$mac} = 0; } } sleep 1; } # A list of MACs extracted from DHCP leases. my @maclist = qw ( 7D:6D:62:C6:B4:3D 01:12:79:3E:14:2E 80:27:E4:EA:60:74 E8:06:88:7F:8C:83 01:26:08:E8:B6:5D 34:15:9E:5D:E6:49 03:1E:64:CE:25:88 01:0C:29:3F:67:1B 01:22:69:76:5D:F4 64:B9:E8:10:BF:20 01:1D:FE:D4:7F:E0 ); my @threads; foreach my $mac (@maclist){ push @threads, threads->create( \&doit, $mac ); } $_->join for @threads;

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: Should I use threads? Perl/DHCP/Radius
by zentara (Archbishop) on Aug 23, 2010 at 18:07 UTC
    How can I make several MACs make calls to the API concurrently and all updating the %clients hash safely. Am I even attempting a threadable problem? I have spent quite some time reading perlmonks, perldocs to no avail.

    Threads are the perfect solution for this type of problem, where you need some realtime sharing between the threads. The only thing I would add, to yours and BrowserUk's solutions, would be to add an eventloop system in your main program, which can utilize timers to check shared variables on a regular schedule, and do what needs to be done. The threads:shared code already has locking shared variable locking available to you, but if you think about it, you can give each thread it's own hash slice to write to to avoid locking, then have the timer in the main program loop do periodic checking of the hash. Event loops can be done with POE, Tk, Gtk2, GLib, Wx, and many other eventloop systems.

    For instance, see Tk-with-worker-threads and Threads-w-Perl/Gtk2 demo and the POE Cookbook. For an example of sharing socket filehandles amoung threads, see Simple threaded chat server


    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku flash japh
      The only thing I would add, to yours and BrowserUk's solutions, would be to add an eventloop system in your main program, which can utilize timers to check shared variables on a regular schedule, and do what needs to be done.

      This is completely and utterly useless advice. There is absolutely no advantage to using an event loop in the main thread for this.

      If there was a requirement to "have the timer in the main program loop do periodic checking of the hash.", that is trivial:

      my @maclist = qw ( 7D:6D:62:C6:B4:3D 01:12:79:3E:14:2E 80:27:E4:EA:60:74 E8:06:88:7F:8C:83 01:26:08:E8:B6:5D 34:15:9E:5D:E6:49 03:1E:64:CE:25:88 01:0C:29:3F:67:1B 01:22:69:76:5D:F4 64:B9:E8:10:BF:20 01:1D:FE:D4:7F:E0 ); my @threads; foreach my $mac (@maclist){ push @threads, threads->create( \&doit, $mac ); } while( sleep 1 ) { ## check %clients and do things. } $_->join for @threads;

      Recommending the addition of huge, monocultural behemoths like POE or GTK2+glib solely to respond to artificial events generated by a timer is asinine. Like hiring a chainsaw to trim your toenails.

      You seem to be suffering from the 'When you have a lumphammer' syndrome.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        I respect your aversion to using timers or the fileevent/select methods of eventloop toolkits, but once you learn to use them, they allow much more flexibility in design. Those
        while( sleep 1){ do complex hash checking logic }
        loops can get very cumbersome as the complexity of the hash checking increases. Timers and fileevents make things easier. Also, Glib is only a 260k download, less than most of the pictures you download now-a-days. Glib also works on Win32 and Linux type systems. Glib is the basic class of the Firefox browser out there. Are you suggesting Firefox should use while loops instead?

        Maybe you just an old dog, who is averse to learning "new tricks"? :-)


        I'm not really a human, but I play one on earth.
        Old Perl Programmer Haiku flash japh

        If there was a requirement to "have the timer in the main program loop do periodic checking of the hash.", that is trivial:

        Is what I am doing. But thanks for your advice folks! I am currently figuring out how to synchronize the thread for each MAC with the main thread. Should be interesting!
Re: Should I use threads? Perl/DHCP/Radius
by MonkeyMonk (Sexton) on Aug 24, 2010 at 13:47 UTC
    BrowserUK, I have gone through several of your posts here and also perldocs / google. Apparently, references or notes on Perl threads concurrency and how to safely handle events after the thread has been completed is rarely dealt with. I do understand that this will take several pages or even a whole book. But I think for people new to threads, it would be a eye-opener!
      how to safely handle events after the thread has been completed

      If you would describe the complete application, it would be far easier to suggest a good approach. What may work well for one type of application, or even appear correct for some partial description; may prove less good once the full picture is available,

        Please refer to the code you suggested and my OP.

        I constantly read /var/log/messages for DHCPACK. When I find one, I parse the MAC and send the API request in a separate thread. The creation of the threads is not a problem. The issue is how can I bring back the result as quickly as possible back to the main process.

        Example: I get 10 such DHCP requests in less than 1 second. My code luanches all the threads in less than a second. . But bringing back the threads into the main process is causing delays with the logic below/

        The rough logic here:
        for(;;){ while ( <DHCP> ) { # New ACK found. # $macthreads{$mac} = new thread(\&launch, l_opts) }#while foreach $mac (keys(%macthreads)){ if($macthreads{$mac}->is_joinable { $macthreads{$mac}->join; delete $macthreads{$mac}; } }#for

        I used the Time::HiRes module. The first thread completes in 1 second. The last in more than 8 seconds.

Re: Should I use threads? Perl/DHCP/Radius
by BrowserUk (Patriarch) on Aug 26, 2010 at 13:25 UTC

    I've moved this back from the deeply-nested sub-thread and quote your /msgs below for clarity.

    The API response determines if the MAC is a "known" MAC. We then allow it to RADUIS auth. After RADIUS auth, the MAC is allowed into a network .

    Another http service reports idle MACs based on n/w packets. I delete them from %clients forcing them to do API / RAdius Auth again.

    Basically we continously allow MACs to come into the n/w , auth them in hte background and drop when no activity is seen

    If I read that correctly, you retain the %client hash of MACs so that you can avoid re-authing them if they are already authed.

    And you need to query another server that tracks MAC activity by inspecting packets.

    And when that other server replies:"this MAC has been inactive for a while"

    You delete that MAC from the %clients hash so the next time it turns up in the logs, it has to re-authorise.


    If the above is a true reflection of the process, then I would suggest the following modification to the process to avoid having to constantly poll the Auth server with every known MAC.

    The file reading loop remains the same:

    while( <LOG> ) { next unless m[ACK (...)]i; async( \&checkMAC, $1 )->detach; }

    But the logic in checkMAC() gets modified:

    sub checkMAC{ my $mac = shift; ## If this is a known MAC & it has previously authorised successfu +lly if( exists $clients{ $mac } and $clients{ $mac } ) { ## Check with the activity monitor to see if it has fallen ina +ctive my $active = get "http://activityServer/check.pl?mac=$mac"; ## If is has never fallen inactive, it is known, authorised, a +nd active ## and there is nothing to do return if $active; ## Not sure that this is really necessary, ## but it might save a little resource. lock %clients; delete $clients{ $mac }; } ## Otherwise, check http & Auth and modify status (or add) in %cli +ents my $httpRes = get ...; my $authRes = get ...; lock %clients; $clients{ $mac } = $httpRes && $authRes ? 1 : 0; return. }

    This way, you'd only contact the activity server each time a MAC appears in the log, rather than constant having to poll it for every known MAC.

Re: Should I use threads? Perl/DHCP/Radius
by zentara (Archbishop) on Aug 25, 2010 at 15:16 UTC
    If you want to see how Glib's eventloop works with threads, here is a little demo.
    #!/usr/bin/perl use warnings; use strict; use threads; use threads::shared; use Glib; use Glib qw/TRUE FALSE/; use Term::ReadKey; $|++; ReadMode('cbreak'); #use Gtk2 qw/-init -threads-init/; print "pid-> ",$$,"\n"; #Glib::Object->set_threadsafe (TRUE); #setup shared hash my %threads; my $count = 0; my $main_loop = Glib::MainLoop->new; Glib::Idle->add( sub{ my $char; if (defined ($char = ReadKey(0)) ) { #print "$char->", ord($char),"\n"; #process key presses here if($char eq 'c'){ $count++; print "count $count\n"; share $threads{$count}{'data'}; share $threads{$count}{'die'}; share $threads{$count}{'thread'}; $threads{$count}{'data'} = 0; $threads{$count}{'die'} = 0; $threads{$count}{'thread'} = threads->new(\&start_thread +,$count)->detach; } if($char eq 'x'){ #my $kill = shift @actives; my $kill = 1; if(defined $kill){ $threads{$kill}{'die'} = 1; print "thread $kill killed\n"; $threads{$kill}{'thread'} = undef; }else{print "no threads running\n";} } return TRUE; #keep this going } }); $main_loop->run; ReadMode('normal'); # restore normal tty settings sub start_thread { my $num = shift; my $self = threads->self; print "Thread ", $self->tid, " started\n"; print "idenitifier: $num\n"; while(1){ if( $threads{$num}{'die'} == 1){print "$num dying\n";return} else{ print ' ' x $num, $threads{$num}{'data'}++,"\n"; select(undef,undef,undef,.5); }; } }

    I'm not really a human, but I play one on earth.
    Old Perl Programmer Haiku ................... flash japh

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://856740]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (5)
As of 2024-04-18 18:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found