diff --git a/BGPLite.Api/BgpDbContext.cs b/BGPLite.Api/BgpDbContext.cs index fb447d7..d2b2180 100644 --- a/BGPLite.Api/BgpDbContext.cs +++ b/BGPLite.Api/BgpDbContext.cs @@ -5,30 +5,17 @@ namespace BGPLite.Api; public class BgpDbContext : DbContext { - private readonly string _dbPath; - public DbSet Peers => Set(); - public bool IsNewDatabase { get; } - public BgpDbContext(string dbPath) + public BgpDbContext(DbContextOptions options) : base(options) { } + + public static void Initialize(BgpDbContext db) { - _dbPath = dbPath; - var dir = Path.GetDirectoryName(dbPath); - if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - IsNewDatabase = !File.Exists(dbPath); - Database.EnsureCreated(); - - Peers.Where(p => p.Status == "active").ExecuteUpdate( + db.Database.EnsureCreated(); + db.Peers.Where(p => p.Status == "active").ExecuteUpdate( s => s.SetProperty(p => p.Status, "inactive")); } - protected override void OnConfiguring(DbContextOptionsBuilder options) - { - options.UseSqlite($"Data Source={_dbPath}"); - } - protected override void OnModelCreating(ModelBuilder model) { model.Entity(e => diff --git a/BGPLite.Api/ManagementApi.cs b/BGPLite.Api/ManagementApi.cs index 8048a14..347e3b3 100644 --- a/BGPLite.Api/ManagementApi.cs +++ b/BGPLite.Api/ManagementApi.cs @@ -229,7 +229,7 @@ public sealed class ManagementApi : IHostedService, IDisposable private ApiResponse HandleGetMe(HttpListenerContext ctx) { - var clientIp = ctx.Request.RemoteEndPoint?.Address.ToString() ?? "unknown"; + var clientIp = GetClientIp(ctx); var peerInfo = _store.GetPeerByIp(clientIp); if (peerInfo is null) @@ -568,6 +568,21 @@ public sealed class ManagementApi : IHostedService, IDisposable #region Helpers + private static string GetClientIp(HttpListenerContext ctx) + { + var realIp = ctx.Request.Headers["X-Real-IP"]; + if (!string.IsNullOrEmpty(realIp)) return realIp; + + var forwarded = ctx.Request.Headers["X-Forwarded-For"]; + if (!string.IsNullOrEmpty(forwarded)) + { + var first = forwarded.Split(',')[0].Trim(); + if (first.Length > 0) return first; + } + + return ctx.Request.RemoteEndPoint?.Address.ToString() ?? "unknown"; + } + private static uint ParseCommunity(string community) { var colon = community.IndexOf(':'); diff --git a/BGPLite.Api/PeerStore.cs b/BGPLite.Api/PeerStore.cs index 31c2af3..081d5be 100644 --- a/BGPLite.Api/PeerStore.cs +++ b/BGPLite.Api/PeerStore.cs @@ -6,33 +6,35 @@ namespace BGPLite.Api; public sealed class PeerStore : IPeerStore { - private readonly BgpDbContext _db; + private readonly IDbContextFactory _dbFactory; - public PeerStore(BgpDbContext db) => _db = db; + public PeerStore(IDbContextFactory dbFactory) => _dbFactory = dbFactory; public string CreatePeer(string ip, uint asn, string? description) { - var existing = _db.Peers.FirstOrDefault(p => p.Ip == ip); + using var db = _dbFactory.CreateDbContext(); + var existing = db.Peers.FirstOrDefault(p => p.Ip == ip); if (existing is not null) { existing.Asn = asn; existing.Description = description; - _db.SaveChanges(); + db.SaveChanges(); return existing.Id; } var peer = new Peer { Ip = ip, Asn = asn, Description = description }; - _db.Peers.Add(peer); - _db.SaveChanges(); + db.Peers.Add(peer); + db.SaveChanges(); return peer.Id; } public void UpsertPeer(string ip, uint asn) { - var peer = _db.Peers.FirstOrDefault(p => p.Ip == ip); + using var db = _dbFactory.CreateDbContext(); + var peer = db.Peers.FirstOrDefault(p => p.Ip == ip); if (peer is null) { - _db.Peers.Add(new Peer { Ip = ip, Asn = asn, Status = "active" }); + db.Peers.Add(new Peer { Ip = ip, Asn = asn, Status = "active" }); } else { @@ -40,102 +42,128 @@ public sealed class PeerStore : IPeerStore peer.Status = "active"; peer.LastSessionAt = DateTime.UtcNow; } - _db.SaveChanges(); + db.SaveChanges(); } public void UpdateSessionStatus(string ip, bool active) { - var peer = _db.Peers.FirstOrDefault(p => p.Ip == ip); + using var db = _dbFactory.CreateDbContext(); + var peer = db.Peers.FirstOrDefault(p => p.Ip == ip); if (peer is null) return; peer.Status = active ? "active" : "inactive"; if (active) peer.LastSessionAt = DateTime.UtcNow; - _db.SaveChanges(); + db.SaveChanges(); } public void DeletePeer(string id) { - _db.Peers.Where(p => p.Id == id).ExecuteDelete(); + using var db = _dbFactory.CreateDbContext(); + db.Peers.Where(p => p.Id == id).ExecuteDelete(); } - public List GetAllPeers() => - _db.Peers.Include(p => p.Communities).ToList(); + public List GetAllPeers() + { + using var db = _dbFactory.CreateDbContext(); + return db.Peers.Include(p => p.Communities).ToList(); + } - public Peer? GetDbPeerById(string id) => - _db.Peers.Include(p => p.Communities).FirstOrDefault(p => p.Id == id); + public Peer? GetDbPeerById(string id) + { + using var db = _dbFactory.CreateDbContext(); + return db.Peers.Include(p => p.Communities).FirstOrDefault(p => p.Id == id); + } PeerInfo? IPeerStore.GetPeerById(string id) { - var peer = GetDbPeerById(id); + using var db = _dbFactory.CreateDbContext(); + var peer = db.Peers.Include(p => p.Communities).FirstOrDefault(p => p.Id == id); return peer is null ? null : MapToInfo(peer); } public PeerInfo? GetPeerByIp(string ip) { - var peer = _db.Peers.Include(p => p.Communities).FirstOrDefault(p => p.Ip == ip); + using var db = _dbFactory.CreateDbContext(); + var peer = db.Peers.Include(p => p.Communities).FirstOrDefault(p => p.Ip == ip); return peer is null ? null : MapToInfo(peer); } public void SetDescription(string id, string description) { - _db.Peers.Where(p => p.Id == id).ExecuteUpdate( + using var db = _dbFactory.CreateDbContext(); + db.Peers.Where(p => p.Id == id).ExecuteUpdate( s => s.SetProperty(p => p.Description, description)); } - public HashSet GetCommunities(string peerId) => - _db.Peers.Include(p => p.Communities) + public HashSet GetCommunities(string peerId) + { + using var db = _dbFactory.CreateDbContext(); + return db.Peers.Include(p => p.Communities) .Where(p => p.Id == peerId) .SelectMany(p => p.Communities) .Select(c => (uint)c.Community) .ToHashSet(); + } - public HashSet GetCommunitiesByIp(string ip) => - _db.Peers.Include(p => p.Communities) + public HashSet GetCommunitiesByIp(string ip) + { + using var db = _dbFactory.CreateDbContext(); + return db.Peers.Include(p => p.Communities) .Where(p => p.Ip == ip) .SelectMany(p => p.Communities) .Select(c => (uint)c.Community) .ToHashSet(); + } public void SetCommunities(string peerId, HashSet communities) { - _db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); - _db.Set().AddRange( + using var db = _dbFactory.CreateDbContext(); + db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); + db.Set().AddRange( communities.Select(c => new PeerCommunity { PeerId = peerId, Community = c })); - _db.SaveChanges(); + db.SaveChanges(); } public void ClearCommunities(string peerId) { - _db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); + using var db = _dbFactory.CreateDbContext(); + db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); } - public List GetSubscriptions(string peerId) => - _db.Set() + public List GetSubscriptions(string peerId) + { + using var db = _dbFactory.CreateDbContext(); + return db.Set() .Where(s => s.PeerId == peerId) .Select(s => s.AsnListName) .ToList(); + } public void SetSubscriptions(string peerId, List asnListNames) { - _db.Set().Where(s => s.PeerId == peerId).ExecuteDelete(); - _db.ChangeTracker.Clear(); - _db.Set().AddRange( + using var db = _dbFactory.CreateDbContext(); + db.Set().Where(s => s.PeerId == peerId).ExecuteDelete(); + db.Set().AddRange( asnListNames.Select(n => new PeerSubscription { PeerId = peerId, AsnListName = n })); - _db.SaveChanges(); + db.SaveChanges(); } - public List GetCustomPrefixes(string peerId) => - _db.Set() + public List GetCustomPrefixes(string peerId) + { + using var db = _dbFactory.CreateDbContext(); + return db.Set() .Where(c => c.PeerId == peerId) .Select(c => c.Prefix + "/" + c.PrefixLength) .ToList(); + } public void SetCustomPrefixes(string peerId, List<(string Prefix, byte Length)> prefixes) { - _db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); - _db.Set().AddRange( + using var db = _dbFactory.CreateDbContext(); + db.Set().Where(c => c.PeerId == peerId).ExecuteDelete(); + db.Set().AddRange( prefixes.Select(p => new PeerCustomPrefix { PeerId = peerId, Prefix = p.Prefix, PrefixLength = p.Length })); - _db.SaveChanges(); + db.SaveChanges(); } private static PeerInfo MapToInfo(Peer peer) => new() diff --git a/BGPLite/Program.cs b/BGPLite/Program.cs index ae639f9..440068e 100644 --- a/BGPLite/Program.cs +++ b/BGPLite/Program.cs @@ -5,6 +5,7 @@ using BGPLite.Protocol; using BGPLite.Providers; using BGPLite.Routing; using BGPLite.Server; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Http; using Microsoft.Extensions.Hosting; @@ -61,14 +62,12 @@ builder.Services.AddSingleton(config); builder.Services.AddSingleton(config.Bgp); builder.Services.AddSingleton(routeTable); -var db = new BgpDbContext(dbPath); -var peerCount = db.Peers.Count(); -if (db.IsNewDatabase) - Console.WriteLine($"Created new database ({peerCount} peers)"); -else - Console.WriteLine($"Database loaded: {peerCount} peer(s)"); +builder.Services.AddDbContextFactory(options => + options.UseSqlite($"Data Source={dbPath}")); + +builder.Services.AddDbContext(options => + options.UseSqlite($"Data Source={dbPath}"), ServiceLifetime.Scoped); -builder.Services.AddSingleton(db); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => { @@ -117,6 +116,21 @@ if (config.RipeStat is { AsnLists.Count: > 0 }) var host = builder.Build(); +// Initialize DB +var dir = Path.GetDirectoryName(dbPath); +if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + Directory.CreateDirectory(dir); + +using (var scope = host.Services.CreateScope()) +{ + var db = scope.ServiceProvider.GetRequiredService(); + BgpDbContext.Initialize(db); + var peerCount = db.Peers.Count(); + Console.WriteLine(peerCount == 0 + ? "Created new database" + : $"Database loaded: {peerCount} peer(s)"); +} + var logger = host.Services.GetRequiredService>(); logger.LogInformation("BGPLite starting — ASN={Asn}, RouterId={RouterId}", config.Bgp.Asn, config.Bgp.RouterId); logger.LogInformation("Loaded routes: {RouteCount}", routeTable.Count); diff --git a/Dockerfile b/Dockerfile index cb17447..b8fd296 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,15 +2,14 @@ FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build WORKDIR /src COPY BGPLite.sln . -COPY global.json . COPY BGPLite/BGPLite.csproj BGPLite/ COPY BGPLite.Protocol/BGPLite.Protocol.csproj BGPLite.Protocol/ COPY BGPLite.Server/BGPLite.Server.csproj BGPLite.Server/ COPY BGPLite.Routing/BGPLite.Routing.csproj BGPLite.Routing/ COPY BGPLite.Configuration/BGPLite.Configuration.csproj BGPLite.Configuration/ COPY BGPLite.Api/BGPLite.Api.csproj BGPLite.Api/ -COPY BGPLite.Tests/BGPLite.Tests.csproj BGPLite.Tests/ -RUN dotnet restore +COPY BGPLite.Providers/BGPLite.Providers.csproj BGPLite.Providers/ +RUN dotnet restore BGPLite/BGPLite.csproj COPY . . RUN dotnet publish BGPLite/BGPLite.csproj -c Release -o /app/publish @@ -21,6 +20,6 @@ WORKDIR /app COPY --from=build /app/publish . RUN mkdir -p /app/data -EXPOSE 179/tcp 5000/tcp +EXPOSE 179/tcp 5001/tcp ENTRYPOINT ["dotnet", "BGPLite.dll"]