/// <summary> /// Authenticate request at Active Directory Domain with user-name and password /// </summary> private PacketCode ProcessActiveDirectoryAuthentication(PendingRequest request) { var userName = request.RequestPacket.UserName; var password = request.RequestPacket.UserPassword; if (string.IsNullOrEmpty(userName)) { _logger.Warning($"Can't find User-Name in message Id={request.RequestPacket.Identifier} from {request.RemoteEndpoint}"); return(PacketCode.AccessReject); } if (string.IsNullOrEmpty(password)) { _logger.Warning($"Can't find User-Password in message Id={request.RequestPacket.Identifier} from {request.RemoteEndpoint}"); return(PacketCode.AccessReject); } var isActiveDirectory = _configuration.FirstFactorAuthenticationSource == AuthenticationSource.ActiveDirectory; var ldapService = isActiveDirectory ? _activeDirectoryService : _ldapService; var isValid = ldapService.VerifyCredential(userName, password, request); return(isValid ? PacketCode.AccessAccept : PacketCode.AccessReject); }
/// <summary> /// Add authenticated request to local cache for otp/challenge /// </summary> private void AddStateChallengePendingRequest(string state, PendingRequest request) { if (!_stateChallengePendingRequests.TryAdd(state, request)) { _logger.Error("Unable to cache request id={id}", request.RequestPacket.Identifier); } }
private void RouterRequestProcessed(object sender, PendingRequest request) { if (request.ResponsePacket?.IsEapMessageChallenge == true) { //EAP authentication in process, just proxy response _logger.Debug($"Proxying EAP-Message Challenge to {request.RemoteEndpoint} Id={request.RequestPacket.Identifier}"); Send(request.ResponsePacket, request.RemoteEndpoint); return; //stop processing } var requestPacket = request.RequestPacket; var responsePacket = requestPacket.CreateResponsePacket(request.ResponseCode); if (request.ResponseCode == PacketCode.AccessAccept) { if (request.ResponsePacket != null) //copy from remote radius reply { request.ResponsePacket.CopyTo(responsePacket); } } //proxy echo required if (requestPacket.Attributes.ContainsKey("Proxy-State")) { if (!responsePacket.Attributes.ContainsKey("Proxy-State")) { responsePacket.Attributes.Add("Proxy-State", requestPacket.Attributes.SingleOrDefault(o => o.Key == "Proxy-State").Value); } } if (request.ResponseCode == PacketCode.AccessChallenge) { responsePacket.AddAttribute("Reply-Message", request.ReplyMessage ?? "Enter OTP code: "); responsePacket.AddAttribute("State", request.State); //state to match user authentication session } //add custom reply attributes if (request.ResponseCode == PacketCode.AccessAccept) { foreach (var attr in _configuration.RadiusReplyAttributes) { //check condition var matched = attr.Value.Where(val => val.IsMatch(request)).Select(val => val.Value); if (matched.Any()) { responsePacket.Attributes.Add(attr.Key, matched.ToList()); } } } Send(responsePacket, request.RemoteEndpoint); //request processed, clear all //GC.Collect(); }
public object[] GetValues(PendingRequest request) { if (IsMemberOf) { return(request.UserGroups.ToArray()); } if (FromLdap) { return(new object[] { request.LdapAttrs[LdapAttributeName] }); } return(new object[] { Value }); }
/// <summary> /// Authenticate request at MultiFactor with user-name only /// </summary> private PacketCode ProcessSecondAuthenticationFactor(PendingRequest request) { var userName = request.RequestPacket.UserName; if (string.IsNullOrEmpty(userName)) { _logger.Warning($"Can't find User-Name in message Id={request.RequestPacket.Identifier} from {request.RemoteEndpoint}"); return(PacketCode.AccessReject); } var response = _multifactorApiClient.CreateSecondFactorRequest(request); return(response); }
/// <summary> /// Authenticate request at LDAP/Active Directory Domain with user-name and password /// </summary> private async Task <PacketCode> ProcessLdapAuthentication(PendingRequest request, ClientConfiguration clientConfig) { var userName = request.RequestPacket.UserName; var password = request.RequestPacket.UserPassword; if (string.IsNullOrEmpty(userName)) { _logger.Warning("Can't find User-Name in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } if (string.IsNullOrEmpty(password)) { _logger.Warning("Can't find User-Password in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } LdapService _service; switch (clientConfig.FirstFactorAuthenticationSource) { case AuthenticationSource.ActiveDirectory: _service = new ActiveDirectoryService(_serviceConfiguration, _logger); break; case AuthenticationSource.Ldap: _service = new LdapService(_serviceConfiguration, _logger); break; default: throw new NotImplementedException(clientConfig.FirstFactorAuthenticationSource.ToString()); } //check all hosts var ldapUriList = clientConfig.ActiveDirectoryDomain.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); foreach (var ldapUri in ldapUriList) { var isValid = await _service.VerifyCredential(userName, password, ldapUri, request, clientConfig); if (isValid) { return(PacketCode.AccessAccept); } } return(PacketCode.AccessReject); }
/// <summary> /// Is match condition /// </summary> public bool IsMatch(PendingRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } //if exist ldap attr value if (FromLdap) { //if list of all groups if (IsMemberOf) { return(request.UserGroups?.Count > 0); } //just attribute return(request.LdapAttrs?[LdapAttributeName] != null); } //if matched user name condition if (!string.IsNullOrEmpty(UserNameCondition)) { var userName = request.RequestPacket.UserName; var isCanonical = Utils.IsCanicalUserName(UserNameCondition); if (isCanonical) { userName = Utils.CanonicalizeUserName(userName); } return(string.Compare(userName, UserNameCondition, StringComparison.InvariantCultureIgnoreCase) == 0); } //if matched user group condition if (!string.IsNullOrEmpty(UserGroupCondition)) { var isInGroup = request .UserGroups .Any(g => string.Compare(g, UserGroupCondition, StringComparison.InvariantCultureIgnoreCase) == 0); return(isInGroup); } return(true); //without conditions }
private async Task <PacketCode> ProcessFirstAuthenticationFactor(PendingRequest request, ClientConfiguration clientConfig) { switch (clientConfig.FirstFactorAuthenticationSource) { case AuthenticationSource.ActiveDirectory: //AD auth case AuthenticationSource.Ldap: //LDAP auth return(await ProcessLdapAuthentication(request, clientConfig)); case AuthenticationSource.Radius: //RADIUS auth return(await ProcessRadiusAuthentication(request, clientConfig)); case AuthenticationSource.None: return(PacketCode.AccessAccept); //first factor not required default: //unknown source throw new NotImplementedException(clientConfig.FirstFactorAuthenticationSource.ToString()); } }
private PacketCode ProcessFirstAuthenticationFactor(PendingRequest request) { switch (_configuration.FirstFactorAuthenticationSource) { case AuthenticationSource.ActiveDirectory: //AD auth case AuthenticationSource.Ldap: //LDAP auth return(ProcessActiveDirectoryAuthentication(request)); case AuthenticationSource.Radius: //RADIUS auth return(ProcessRadiusAuthentication(request)); case AuthenticationSource.None: return(PacketCode.AccessAccept); //first factor not required default: //unknown source throw new NotImplementedException(_configuration.FirstFactorAuthenticationSource.ToString()); } }
/// <summary> /// Parses a packet and gets a response packet from the handler /// </summary> internal void ParseAndProcess(byte[] packetBytes, IPEndPoint remoteEndpoint) { var requestPacket = _radiusPacketParser.Parse(packetBytes, Encoding.UTF8.GetBytes(_configuration.RadiusSharedSecret)); _logger.Debug($"Received {requestPacket.Code} from {remoteEndpoint} Id={requestPacket.Identifier}"); if (_cacheService.IsRetransmission(requestPacket, remoteEndpoint)) { _logger.Debug($"Retransmissed request from {remoteEndpoint} Id={requestPacket.Identifier}, ignoring"); return; } var request = new PendingRequest { RemoteEndpoint = remoteEndpoint, RequestPacket = requestPacket }; Task.Run(() => _router.HandleRequest(request)); }
/// <summary> /// Is match condition /// </summary> public bool IsMatch(PendingRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } if (string.IsNullOrEmpty(UserGroupCondition)) { return(true); //always on } var isInGroup = request .UserGroups .Any(g => string.Compare(g, UserGroupCondition, StringComparison.InvariantCultureIgnoreCase) == 0); return(isInGroup); }
/// <summary> /// Authenticate request at Network Policy Server with user-name and password /// </summary> private PacketCode ProcessRadiusAuthentication(PendingRequest request) { try { //sending request as is to Network Policy Server using (var client = new RadiusClient(_configuration.ServiceClientEndpoint, _logger)) { _logger.Debug($"Sending Access-Request message with Id={request.RequestPacket.Identifier} to Network Policy Server {_configuration.NpsServerEndpoint}"); var requestBytes = _packetParser.GetBytes(request.RequestPacket); var response = client.SendPacketAsync(request.RequestPacket.Identifier, requestBytes, _configuration.NpsServerEndpoint, TimeSpan.FromSeconds(5)).Result; if (response != null) { var responsePacket = _packetParser.Parse(response, request.RequestPacket.SharedSecret, request.RequestPacket.Authenticator); _logger.Debug($"Received {responsePacket.Code} message with Id={responsePacket.Identifier} from Network Policy Server"); if (responsePacket.Code == PacketCode.AccessAccept) { var userName = request.RequestPacket.UserName; _logger.Information($"User '{userName}' credential and status verified successfully at {_configuration.NpsServerEndpoint}"); } request.ResponsePacket = responsePacket; return(responsePacket.Code); //Code received from NPS } else { _logger.Warning($"Network Policy Server did not respond on message with Id={request.RequestPacket.Identifier}"); return(PacketCode.AccessReject); //reject by default } } } catch (Exception ex) { _logger.Error(ex, "Radius authentication error"); } return(PacketCode.AccessReject); //reject by default }
/// <summary> /// Authenticate request at Remote Radius Server with user-name and password /// </summary> private async Task <PacketCode> ProcessRadiusAuthentication(PendingRequest request, ClientConfiguration clientConfig) { try { //sending request as is to Remote Radius Server using (var client = new RadiusClient(clientConfig.ServiceClientEndpoint, _logger)) { _logger.Debug($"Sending AccessRequest message with id={{id}} to Remote Radius Server {clientConfig.NpsServerEndpoint}", request.RequestPacket.Identifier); var requestBytes = _packetParser.GetBytes(request.RequestPacket); var response = await client.SendPacketAsync(request.RequestPacket.Identifier, requestBytes, clientConfig.NpsServerEndpoint, TimeSpan.FromSeconds(5)); if (response != null) { var responsePacket = _packetParser.Parse(response, request.RequestPacket.SharedSecret, request.RequestPacket.Authenticator); _logger.Debug("Received {code:l} message with id={id} from Remote Radius Server", responsePacket.Code.ToString(), responsePacket.Identifier); if (responsePacket.Code == PacketCode.AccessAccept) { var userName = request.RequestPacket.UserName; _logger.Information($"User '{{user:l}}' credential and status verified successfully at {clientConfig.NpsServerEndpoint}", userName); } request.ResponsePacket = responsePacket; return(responsePacket.Code); //Code received from remote radius } else { _logger.Warning("Remote Radius Server did not respond on message with id={id}", request.RequestPacket.Identifier); return(PacketCode.AccessReject); //reject by default } } } catch (Exception ex) { _logger.Error(ex, "Radius authentication error"); } return(PacketCode.AccessReject); //reject by default }
/// <summary> /// Authenticate request at MultiFactor with user-name only /// </summary> private async Task <PacketCode> ProcessSecondAuthenticationFactor(PendingRequest request, ClientConfiguration clientConfig) { var userName = request.RequestPacket.UserName; if (string.IsNullOrEmpty(userName)) { _logger.Warning("Can't find User-Name in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } if (request.RequestPacket.IsVendorAclRequest == true) { //security check if (clientConfig.FirstFactorAuthenticationSource == AuthenticationSource.Radius) { _logger.Information("Bypass second factor for user {user:l}", userName); return(PacketCode.AccessAccept); } } var response = await _multifactorApiClient.CreateSecondFactorRequest(request, clientConfig); return(response); }
/// <summary> /// Verify one time password from user input /// </summary> private async Task <PacketCode> ProcessChallenge(PendingRequest request, ClientConfiguration clientConfig, string state) { var userName = request.RequestPacket.UserName; if (string.IsNullOrEmpty(userName)) { _logger.Warning("Can't find User-Name in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } PacketCode response; string userAnswer; switch (request.RequestPacket.AuthenticationType) { case AuthenticationType.PAP: //user-password attribute holds second request challenge from user userAnswer = request.RequestPacket.GetString("User-Password"); if (string.IsNullOrEmpty(userAnswer)) { _logger.Warning("Can't find User-Password with user response in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } break; case AuthenticationType.MSCHAP2: var msChapResponse = request.RequestPacket.GetAttribute <byte[]>("MS-CHAP2-Response"); if (msChapResponse == null) { _logger.Warning("Can't find MS-CHAP2-Response in message id={id} from {host:l}:{port}", request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } //forti behaviour var otpData = msChapResponse.Skip(2).Take(6).ToArray(); userAnswer = Encoding.ASCII.GetString(otpData); break; default: _logger.Warning("Unable to process {auth} challange in message id={id} from {host:l}:{port}", request.RequestPacket.AuthenticationType, request.RequestPacket.Identifier, request.RemoteEndpoint.Address, request.RemoteEndpoint.Port); return(PacketCode.AccessReject); } response = await _multifactorApiClient.Challenge(request, clientConfig, userName, userAnswer, state); switch (response) { case PacketCode.AccessAccept: var stateChallengePendingRequest = GetStateChallengeRequest(state); if (stateChallengePendingRequest != null) { request.UserGroups = stateChallengePendingRequest.UserGroups; request.ResponsePacket = stateChallengePendingRequest.ResponsePacket; request.LdapAttrs = stateChallengePendingRequest.LdapAttrs; } break; case PacketCode.AccessReject: RemoveStateChallengeRequest(state); break; } return(response); }
private void RouterRequestProcessed(object sender, PendingRequest request) { if (request.ResponsePacket?.IsEapMessageChallenge == true) { //EAP authentication in process, just proxy response _logger.Debug("Proxying EAP-Message Challenge to {host:l}:{port} id={id}", request.RemoteEndpoint.Address, request.RemoteEndpoint.Port, request.RequestPacket.Identifier); Send(request.ResponsePacket, request.RequestPacket?.UserName, request.RemoteEndpoint, request.ProxyEndpoint, true); return; //stop processing } if (request.RequestPacket.IsVendorAclRequest == true && request.ResponsePacket != null) { //ACL and other rules transfer, just proxy response _logger.Debug("Proxying #ACSACL# to {host:l}:{port} id={id}", request.RemoteEndpoint.Address, request.RemoteEndpoint.Port, request.RequestPacket.Identifier); Send(request.ResponsePacket, request.RequestPacket?.UserName, request.RemoteEndpoint, request.ProxyEndpoint, true); return; //stop processing } var requestPacket = request.RequestPacket; var responsePacket = requestPacket.CreateResponsePacket(request.ResponseCode); switch (request.ResponseCode) { case PacketCode.AccessAccept: if (request.ResponsePacket != null) //copy from remote radius reply { request.ResponsePacket.CopyTo(responsePacket); } if (request.RequestPacket.Code == PacketCode.StatusServer) { responsePacket.AddAttribute("Reply-Message", request.ReplyMessage); } var clientConfiguration = _serviceConfiguration.GetClient(request); //add custom reply attributes if (request.ResponseCode == PacketCode.AccessAccept) { foreach (var attr in clientConfiguration.RadiusReplyAttributes) { //check condition var matched = attr.Value.Where(val => val.IsMatch(request)).SelectMany(val => val.GetValues(request)); if (matched.Any()) { var convertedValues = new List <object>(); foreach (var val in matched.ToList()) { _logger.Debug("Added attribute '{attrname:l}:{attrval:l}' to reply", attr.Key, val); convertedValues.Add(ConvertType(attr.Key, val)); } responsePacket.Attributes.Add(attr.Key, convertedValues); } } } break; case PacketCode.AccessChallenge: responsePacket.AddAttribute("Reply-Message", request.ReplyMessage ?? "Enter OTP code: "); responsePacket.AddAttribute("State", request.State); //state to match user authentication session break; case PacketCode.AccessReject: if (request.ResponsePacket != null) //copy from remote radius reply { if (request.ResponsePacket.Code == PacketCode.AccessReject) //for mschap pwd change only { request.ResponsePacket.CopyTo(responsePacket); } } break; default: throw new NotImplementedException(request.ResponseCode.ToString()); } //proxy echo required if (requestPacket.Attributes.ContainsKey("Proxy-State")) { if (!responsePacket.Attributes.ContainsKey("Proxy-State")) { responsePacket.Attributes.Add("Proxy-State", requestPacket.Attributes.SingleOrDefault(o => o.Key == "Proxy-State").Value); } } var debugLog = request.RequestPacket.Code == PacketCode.StatusServer; Send(responsePacket, request.RequestPacket?.UserName, request.RemoteEndpoint, request.ProxyEndpoint, debugLog); //request processed, clear all //GC.Collect(); }
public void HandleRequest(PendingRequest request) { try { if (request.RequestPacket.Code != PacketCode.AccessRequest) { _logger.Warning($"Unprocessable packet type: {request.RequestPacket.Code}"); return; } if (request.RequestPacket.Attributes.ContainsKey("State")) //Access-Challenge response { var receivedState = request.RequestPacket.GetString("State"); if (_stateChallengePendingRequests.ContainsKey(receivedState)) { //second request with Multifactor challenge request.ResponseCode = ProcessChallenge(request, receivedState); request.State = receivedState; //state for Access-Challenge message if otp is wrong (3 times allowed) RequestProcessed?.Invoke(this, request); return; //stop authentication process after otp code verification } } var firstFactorAuthenticationResultCode = ProcessFirstAuthenticationFactor(request); if (firstFactorAuthenticationResultCode != PacketCode.AccessAccept) { //first factor authentication rejected request.ResponseCode = firstFactorAuthenticationResultCode; RequestProcessed?.Invoke(this, request); //stop authencation process return; } if (request.Bypass2Fa) { //second factor not trquired var userName = request.RequestPacket.UserName; _logger.Information($"Bypass second factor for user {userName}"); request.ResponseCode = PacketCode.AccessAccept; RequestProcessed?.Invoke(this, request); //stop authencation process return; } var secondFactorAuthenticationResultCode = ProcessSecondAuthenticationFactor(request); request.ResponseCode = secondFactorAuthenticationResultCode; if (request.ResponseCode == PacketCode.AccessChallenge) { AddStateChallengePendingRequest(request.State, request); } RequestProcessed?.Invoke(this, request); } catch (Exception ex) { _logger.Error(ex, "HandleRequest"); } }
public async Task HandleRequest(PendingRequest request, ClientConfiguration clientConfig) { try { if (request.RequestPacket.Code == PacketCode.StatusServer) { //status var uptime = (DateTime.Now - _startedAt); request.ReplyMessage = $"Server up {uptime.Days} days {uptime.ToString("hh\\:mm\\:ss")}"; request.ResponseCode = PacketCode.AccessAccept; RequestProcessed?.Invoke(this, request); return; } if (request.RequestPacket.Code != PacketCode.AccessRequest) { _logger.Warning("Unprocessable packet type: {code}", request.RequestPacket.Code); return; } if (request.RequestPacket.Attributes.ContainsKey("State")) //Access-Challenge response { var receivedState = request.RequestPacket.GetString("State"); if (_stateChallengePendingRequests.ContainsKey(receivedState)) { //second request with Multifactor challenge request.ResponseCode = await ProcessChallenge(request, clientConfig, receivedState); request.State = receivedState; //state for Access-Challenge message if otp is wrong (3 times allowed) RequestProcessed?.Invoke(this, request); return; //stop authentication process after otp code verification } } var firstFactorAuthenticationResultCode = await ProcessFirstAuthenticationFactor(request, clientConfig); if (firstFactorAuthenticationResultCode != PacketCode.AccessAccept) { //first factor authentication rejected request.ResponseCode = firstFactorAuthenticationResultCode; RequestProcessed?.Invoke(this, request); //stop authencation process return; } if (request.Bypass2Fa) { //second factor not trquired var userName = request.RequestPacket.UserName; _logger.Information("Bypass second factor for user '{user:l}'", userName); request.ResponseCode = PacketCode.AccessAccept; RequestProcessed?.Invoke(this, request); //stop authencation process return; } var secondFactorAuthenticationResultCode = await ProcessSecondAuthenticationFactor(request, clientConfig); request.ResponseCode = secondFactorAuthenticationResultCode; if (request.ResponseCode == PacketCode.AccessChallenge) { AddStateChallengePendingRequest(request.State, request); } RequestProcessed?.Invoke(this, request); } catch (Exception ex) { _logger.Error(ex, "HandleRequest"); } }
/// <summary> /// Parses a packet and gets a response packet from the handler /// </summary> internal void ParseAndProcess(byte[] packetBytes, IPEndPoint remoteEndpoint) { IPEndPoint proxyEndpoint = null; if (IsProxyProtocol(packetBytes, out var sourceEndpoint, out var requestWithoutProxyHeader)) { packetBytes = requestWithoutProxyHeader; proxyEndpoint = remoteEndpoint; remoteEndpoint = sourceEndpoint; } ClientConfiguration clientConfiguration = null; if (RadiusPacketNasIdentifierParser.TryParse(packetBytes, out var nasIdentifier)) { clientConfiguration = _serviceConfiguration.GetClient(nasIdentifier); } if (clientConfiguration == null) { clientConfiguration = _serviceConfiguration.GetClient(remoteEndpoint.Address); } if (clientConfiguration == null) { _logger.Warning("Received packet from unknown client {host:l}:{port}, ignoring", remoteEndpoint.Address, remoteEndpoint.Port); return; } var requestPacket = _radiusPacketParser.Parse(packetBytes, Encoding.UTF8.GetBytes(clientConfiguration.RadiusSharedSecret)); var isRetransmission = _cacheService.IsRetransmission(requestPacket, remoteEndpoint); if (isRetransmission) { _logger.Debug("Retransmissed request from {host:l}:{port} id={id} client '{client:l}', ignoring", remoteEndpoint.Address, remoteEndpoint.Port, requestPacket.Identifier, clientConfiguration.Name); return; } if (proxyEndpoint != null) { if (requestPacket.Code == PacketCode.StatusServer) { _logger.Information("Received {code:l} from {host:l}:{port} proxied by {proxyhost:l}:{proxyport} id={id} client '{client:l}'", requestPacket.Code.ToString(), remoteEndpoint.Address, remoteEndpoint.Port, proxyEndpoint.Address, proxyEndpoint.Port, requestPacket.Identifier, clientConfiguration.Name); } else { _logger.Information("Received {code:l} from {host:l}:{port} proxied by {proxyhost:l}:{proxyport} id={id} user='******' client '{client:l}'", requestPacket.Code.ToString(), remoteEndpoint.Address, remoteEndpoint.Port, proxyEndpoint.Address, proxyEndpoint.Port, requestPacket.Identifier, requestPacket.UserName, clientConfiguration.Name); } } else { if (requestPacket.Code == PacketCode.StatusServer) { _logger.Debug("Received {code:l} from {host:l}:{port} id={id} client '{client:l}'", requestPacket.Code.ToString(), remoteEndpoint.Address, remoteEndpoint.Port, requestPacket.Identifier, clientConfiguration.Name); } else { _logger.Information("Received {code:l} from {host:l}:{port} id={id} user='******' client '{client:l}'", requestPacket.Code.ToString(), remoteEndpoint.Address, remoteEndpoint.Port, requestPacket.Identifier, requestPacket.UserName, clientConfiguration.Name); } } var request = new PendingRequest { RemoteEndpoint = remoteEndpoint, ProxyEndpoint = proxyEndpoint, RequestPacket = requestPacket }; Task.Run(async() => await _router.HandleRequest(request, clientConfiguration)); }