namespace DodoSSH.Domain; /// /// A relay ticket that has been redeemed. /// /// /// /// The replay guard, and the only state the relay shares between nodes — see ADR 0004. A ticket is /// verified without the database; what the database adds is that it is verified once, by a /// unique insert on that a second redemption loses whichever node it reaches. /// /// /// A row is worth keeping only while its ticket could still verify, so is /// stored to let redemption sweep the rows that can no longer matter. Nothing else reads this table, /// and it carries nothing about the target: the audit record is . /// /// public sealed class RelayTicketUse { /// The ticket's own id. Primary key, and the whole of the guard. public Guid TicketId { get; set; } /// When the ticket stops verifying, after which this row guards nothing. public DateTimeOffset ExpiresAtUtc { get; set; } /// When it was redeemed. public DateTimeOffset UsedAtUtc { get; set; } } /// /// The audit record of one relay session. /// /// /// /// What ADR 0004 says remains auditable in relay mode, and nothing more: who, to where, for how long, /// how many bytes each way and why it ended. The relay forwards SSH ciphertext, so there is no content /// to record and no column that pretends otherwise. /// /// /// Written twice — once when the target answers, once when the session ends — each time from a scope /// opened for that write alone. A session lasts hours and a scope held across it is a pooled database /// connection held across it, which is the failure ADR 0004 records. /// /// /// No foreign keys to the host or the vault, deliberately. An audit row has to outlive what it is /// about: a host deleted a minute after a session is exactly the host an operator will want the /// session for. /// /// public sealed class RelaySession { /// The session id, minted with the ticket and returned to the client. Primary key. public Guid Id { get; set; } /// Who opened it. public Guid UserId { get; set; } /// The host it reached. public Guid HostId { get; set; } /// The vault the host was in when the ticket was issued. public Guid VaultId { get; set; } /// The hostname as the host row carried it at issue. public string TargetHost { get; set; } = string.Empty; /// The address actually dialled, which is the one that answered. /// Null only for a session whose target never answered. public string? TargetAddress { get; set; } /// The port dialled. public int TargetPort { get; set; } /// The client's address as this node saw it, for the audit ADR 0004 promises. public string? ClientAddress { get; set; } /// When the socket was accepted. public DateTimeOffset StartedAtUtc { get; set; } /// When it closed, or null while it is open — or if the node died under it. public DateTimeOffset? EndedAtUtc { get; set; } /// Bytes forwarded from the client to the target. public long BytesClientToTarget { get; set; } /// Bytes forwarded from the target to the client. public long BytesTargetToClient { get; set; } /// Why it ended. while open. public RelayCloseReason CloseReason { get; set; } } /// Why a relay session ended. /// /// Numbered as RelaySessionCloseReason on the wire is, and held to it by a test rather than by /// hope: the endpoint converts between the two by value. /// public enum RelayCloseReason { /// Not a legal value for a closed session; what an open one carries. Unspecified = 0, /// The client closed the connection. ClientClosed = 1, /// The target closed the connection. TargetClosed = 2, /// No traffic within the idle timeout. IdleTimeout = 3, /// The maximum session duration was reached. DurationLimit = 4, /// The server is shutting down and drained the session. ServerShutdown = 5, /// The connection to the target failed. TargetUnreachable = 6, /// An error ended the session. Error = 7, }