[RADIATOR] add Attributes when retrying to a new Host in AuthROUNDROBIN

David Zych dmrz at illinois.edu
Mon Aug 18 17:00:48 CDT 2014

I'm using Radiator with ntlm_auth (on linux) to authenticate against Active Directory.  Occasionally ntlm_auth is slow to return, causing a logjam in the request queue and making it hard for new wireless users to authenticate.  I've seen this happen in a variety of different ways, but some of those ways can be mitigated by using multiple instances of Radiator on each server: a single front-end instance proxies the final inner authentication piece to one of several back-end instances (each with its own separate ntlm_auth process).

When a stalled back-end instance becomes unstalled (after spending e.g. 6s waiting for ntlm_auth to respond to a single request), it's likely to have a bunch more requests already queued up, and some of those may already be old enough that there's no point in answering them (because the front-end instance will have already given up).  We want a way for the back-end instance to recognize and IGNORE those requests, so that we can quickly get back to processing more recent requests.

I've figured out a first approximation of this in my config snippets below: the front-end instance adds the current time as a custom request attribute (X-Timestamp) before proxying it, and each back-end instance uses a hook to compare X-Timestamp to the (new) current time and short-circuit IGNORE anything whose X-Timestamp is older than 5 seconds.  This works as desired for the _first_ back-end instance I proxy to, but if the first one times out, then the second back-end instance will still see the same X-Timestamp value and will also short-circuit IGNORE.  Of course I could invoke my hook with a higher tolerance (e.g. allow up to 7s) to allow a retry to succeed, but then the first back-end instance would waste effort answering 6s-old requests.

How can I set a new attribute value on a request _each_ time I attempt to proxy it using AuthRADIUS and friends?  I'm thinking a "PreForwardHook" would be ideal, but I don't see anything like that currently implemented.  Is there another solution I'm not seeing, or if not, would it be possible to add such a hook?

(note: for my use case it wouldn't matter whether the hook also gets called between successive retries for the same Host, all I care about is that it's called each time we switch to a new Host)

As an aside: is there any special reason that MaxTargetHosts is unique to AuthVOLUMEBALANCE?  I would think it would be equally applicable to all flavors of AuthRADIUS (and in particular I wish it was implemented in AuthROUNDROBIN).


Front-end instance:

# proxy inner auth to local sub-instances using simple round robin
# (since they are interchangeable and stateless).  IGNORE acct.
  Identifier wireless-authproxy
  include %D/private/localhost.secret
  # give up if sub-instance doesn't respond within this time
  RetryTimeout 5
  # do not retry against the same sub-instance
  Retries 0
  FailureBackoffTime 1
    AuthPort %{GlobalVar:radius.wireless1.authport}
    AuthPort %{GlobalVar:radius.wireless2.authport}
    AuthPort %{GlobalVar:radius.wireless3.authport}
    AuthPort %{GlobalVar:radius.wireless4.authport}
  # this attribute is not in the dictionary
  StripFromRequest ConvertedFromEAPMSCHAPV2
  AddToRequest X-Client-Identifier=%{Client:Identifier},X-Timestamp=%t

Back-end instances:

<AuthBy GROUP>
  AuthByPolicy ContinueWhileAccept

# ...snip...

  # IGNORE requests older than wireless-authproxy RetryTimeout
    Identifier wireless-ignoreExpired
    AuthHook sub { CITES::timestamp_within("X-Timestamp", 5, @_) }

  <AuthBy NTLM>
# ...snip...

# AuthHook which returns ACCEPT if request's $attrname attribute has
# an epoch timestamp value within $window seconds of the current time,
# and IGNORE otherwise.  Local sub-instances can use this with AuthBy
# INTERNAL (and a suitable AuthByPolicy) to proactively drop proxied
# requests that are already too old to be worth processing.
sub timestamp_within {
  my ($attrname,$window) = (shift,shift);
  my ($p,$rp)=@_;

  my $ts = $p->get_attr($attrname) or
    &main::log($main::LOG_ERR,"Missing $attrname in timestamp_within");
  my $age = time - ($ts || 0);
  return $main::ACCEPT if $age >= 0 && $age <= $window;
  &main::log($main::LOG_ERR,"Ignoring expired request for ".$p->getUserName().": $attrname is ${age}s old");
  return $main::IGNORE;

More information about the radiator mailing list