feat: initialize database schema and implement core Radius entities, EF context, and account enforcement service.
Build and Push Docker Image to Gitea Container Registry / build-and-push (push) Successful in 1m12s
Build and Push Docker Image to Gitea Container Registry / build-and-push (push) Successful in 1m12s
This commit is contained in:
@@ -56,6 +56,7 @@ public class AccountEnforcementService : BackgroundService
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var disabledCount = 0;
|
||||
var changesMade = false;
|
||||
|
||||
foreach (var profile in activeProfiles)
|
||||
{
|
||||
@@ -68,12 +69,11 @@ public class AccountEnforcementService : BackgroundService
|
||||
reason = "Time expired";
|
||||
}
|
||||
|
||||
// 2. Check data cap: sum acctinputoctets + acctoutputoctets from radacct
|
||||
// 2. Check data cap: read from guest_data_usage table
|
||||
if (reason == null && profile.DataLimitMb > 0)
|
||||
{
|
||||
var totalOctets = await db.RadAcct
|
||||
.Where(ra => ra.Username == profile.Username)
|
||||
.SumAsync(ra => (ra.AcctInputOctets ?? 0) + (ra.AcctOutputOctets ?? 0), ct);
|
||||
var usage = await db.GuestDataUsage.FirstOrDefaultAsync(du => du.Username == profile.Username, ct);
|
||||
var totalOctets = (usage?.TotalInputOctets ?? 0) + (usage?.TotalOutputOctets ?? 0);
|
||||
|
||||
long dataLimitBytes = profile.DataLimitMb * 1024 * 1024;
|
||||
if (totalOctets >= dataLimitBytes)
|
||||
@@ -102,18 +102,57 @@ public class AccountEnforcementService : BackgroundService
|
||||
Value = "Reject"
|
||||
});
|
||||
|
||||
_logger.LogInformation(
|
||||
"Account '{Username}' disabled. Reason: {Reason}",
|
||||
profile.Username, reason);
|
||||
_logger.LogInformation("Account '{Username}' disabled. Reason: {Reason}", profile.Username, reason);
|
||||
|
||||
// Find active sessions to disconnect them instantly via RADIUS CoA (PoD)
|
||||
var activeSessions = await db.RadAcct
|
||||
.Where(ra => ra.Username == profile.Username && ra.AcctStopTime == null)
|
||||
.Select(ra => ra.NasIpAddress)
|
||||
.Distinct()
|
||||
.ToListAsync(ct);
|
||||
|
||||
var radiusSecret = Environment.GetEnvironmentVariable("RADIUS_SECRET") ?? "radpass";
|
||||
|
||||
foreach (var nasIp in activeSessions)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nasIp)) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = "sh",
|
||||
Arguments = $"-c \"echo 'User-Name={profile.Username}' | radclient -x {nasIp}:3799 disconnect '{radiusSecret}'\"",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
process.Start();
|
||||
await process.WaitForExitAsync(ct);
|
||||
_logger.LogInformation("Sent RADIUS Disconnect-Request to NAS {NasIp} for user {Username}", nasIp, profile.Username);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to send RADIUS Disconnect-Request to NAS {NasIp}", nasIp);
|
||||
}
|
||||
}
|
||||
|
||||
disabledCount++;
|
||||
changesMade = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (disabledCount > 0)
|
||||
if (changesMade)
|
||||
{
|
||||
await db.SaveChangesAsync(ct);
|
||||
_logger.LogInformation("Enforcement check complete. {Count} account(s) disabled.", disabledCount);
|
||||
if (disabledCount > 0)
|
||||
{
|
||||
_logger.LogInformation("Enforcement check complete. {Count} account(s) disabled.", disabledCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,25 +175,27 @@ public class RadiusService : IRadiusService
|
||||
.Where(rc => rc.Attribute == "Cleartext-Password")
|
||||
.ToDictionaryAsync(rc => rc.Username, rc => rc.Value);
|
||||
|
||||
// Group accounting data by username
|
||||
// Group accounting data by username (for session time and connection status)
|
||||
var acctStats = await _db.RadAcct
|
||||
.GroupBy(ra => ra.Username)
|
||||
.Select(g => new
|
||||
{
|
||||
Username = g.Key,
|
||||
TotalInput = g.Sum(x => x.AcctInputOctets ?? 0),
|
||||
TotalOutput = g.Sum(x => x.AcctOutputOctets ?? 0),
|
||||
TotalSessionTime = g.Sum(x => x.AcctSessionTime ?? 0),
|
||||
IsConnected = g.Any(x => x.AcctStopTime == null)
|
||||
})
|
||||
.ToDictionaryAsync(g => g.Username);
|
||||
|
||||
// Fetch data usage from the new dedicated table
|
||||
var dataUsage = await _db.GuestDataUsage.ToDictionaryAsync(du => du.Username);
|
||||
|
||||
var result = new List<UserUsageDto>();
|
||||
|
||||
foreach (var p in profiles)
|
||||
{
|
||||
passwords.TryGetValue(p.Username, out var pwd);
|
||||
acctStats.TryGetValue(p.Username, out var acct);
|
||||
dataUsage.TryGetValue(p.Username, out var usage);
|
||||
|
||||
result.Add(new UserUsageDto
|
||||
{
|
||||
@@ -205,8 +207,8 @@ public class RadiusService : IRadiusService
|
||||
SessionTimeMinutes = p.SessionTimeMinutes,
|
||||
DataLimitMb = p.DataLimitMb,
|
||||
Status = p.Status,
|
||||
TotalInputOctets = acct?.TotalInput ?? 0,
|
||||
TotalOutputOctets = acct?.TotalOutput ?? 0,
|
||||
TotalInputOctets = usage?.TotalInputOctets ?? 0,
|
||||
TotalOutputOctets = usage?.TotalOutputOctets ?? 0,
|
||||
TotalSessionTimeSeconds = acct?.TotalSessionTime ?? 0,
|
||||
IsCurrentlyConnected = acct?.IsConnected ?? false
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user