diff --git a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs index e710eb3c76..df420f48a2 100644 --- a/Jellyfin.Networking/Configuration/NetworkConfiguration.cs +++ b/Jellyfin.Networking/Configuration/NetworkConfiguration.cs @@ -99,8 +99,7 @@ namespace Jellyfin.Networking.Configuration public bool UPnPCreateHttpPortMap { get; set; } /// - /// Gets or sets the UDPPortRange - /// Gets or sets client udp port range. + /// Gets or sets the UDPPortRange. /// public string UDPPortRange { get; set; } = string.Empty; @@ -115,8 +114,8 @@ namespace Jellyfin.Networking.Configuration public bool EnableIPV4 { get; set; } = true; /// - /// Gets or sets a value indicating whether detailed ssdp logs are sent to the console/log. - /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to work. + /// Gets or sets a value indicating whether detailed SSDP logs are sent to the console/log. + /// "Emby.Dlna": "Debug" must be set in logging.default.json for this property to have any effect. /// public bool EnableSSDPTracing { get; set; } @@ -143,7 +142,6 @@ namespace Jellyfin.Networking.Configuration public bool IgnoreVirtualInterfaces { get; set; } = true; /// - /// Gets or sets the VirtualInterfaceNames /// Gets or sets a value indicating the interfaces that should be ignored. The list can be comma separated. . /// public string VirtualInterfaceNames { get; set; } = "vEthernet*"; diff --git a/Jellyfin.Networking/Manager/NetworkManager.cs b/Jellyfin.Networking/Manager/NetworkManager.cs index 00711f1620..515ae669a1 100644 --- a/Jellyfin.Networking/Manager/NetworkManager.cs +++ b/Jellyfin.Networking/Manager/NetworkManager.cs @@ -182,7 +182,7 @@ namespace Jellyfin.Networking.Manager public IReadOnlyCollection GetMacAddresses() { // Populated in construction - so always has values. - return _macAddresses.AsReadOnly(); + return _macAddresses; } /// @@ -378,7 +378,7 @@ namespace Jellyfin.Networking.Manager } } - _logger.LogDebug("GetBindInterface: Source: {0}, External: {1}:", haveSource, isExternal); + _logger.LogDebug("GetBindInterface: Source: {HaveSource}, External: {IsExternal}:", haveSource, isExternal); // No preference given, so move on to bind addresses. if (MatchesBindInterface(source, isExternal, out string result)) @@ -408,20 +408,20 @@ namespace Jellyfin.Networking.Manager if (intf.Contains(source)) { result = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Has source, matched best internal interface on range. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Has source, matched best internal interface on range. {Result}", source, result); return result; } } } result = FormatIP6String(interfaces.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Matched first internal interface. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Matched first internal interface. {Result}", source, result); return result; } // There isn't any others, so we'll use the loopback. result = IsIP6Enabled ? "::" : "127.0.0.1"; - _logger.LogWarning("{0}: GetBindInterface: Loopback return.", source, result); + _logger.LogWarning("{Source}: GetBindInterface: Loopback {Result} returned.", source, result); return result; } @@ -552,7 +552,7 @@ namespace Jellyfin.Networking.Manager { result = new Collection(); - _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); + _logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token); // Replace interface tags with the interface IP's. foreach (IPNetAddress iface in _interfaceAddresses) @@ -575,7 +575,7 @@ namespace Jellyfin.Networking.Manager /// Reloads all settings and re-initialises the instance. /// /// The to use. - public void UpdateSettings(NetworkConfiguration configuration) + public void UpdateSettings(object configuration) { NetworkConfiguration config = (NetworkConfiguration)configuration ?? throw new ArgumentNullException(nameof(configuration)); @@ -740,7 +740,7 @@ namespace Jellyfin.Networking.Manager // Null check required here for automated testing. if (IsInterface(token, out int index)) { - _logger.LogInformation("Interface {0} used in settings. Using its interface addresses.", token); + _logger.LogInformation("Interface {Token} used in settings. Using its interface addresses.", token); // Replace interface tags with the interface IP's. foreach (IPNetAddress iface in _interfaceAddresses) @@ -780,7 +780,7 @@ namespace Jellyfin.Networking.Manager } else { - _logger.LogDebug("Invalid or unknown network {0}.", token); + _logger.LogDebug("Invalid or unknown network {Token}.", token); } } @@ -867,7 +867,7 @@ namespace Jellyfin.Networking.Manager var parts = entry.Split('='); if (parts.Length != 2) { - _logger.LogError("Unable to parse bind override. {0}", entry); + _logger.LogError("Unable to parse bind override: {Entry}", entry); } else { @@ -893,7 +893,7 @@ namespace Jellyfin.Networking.Manager } else { - _logger.LogError("Unable to parse bind ip address. {0}", parts[1]); + _logger.LogError("Unable to parse bind ip address. {Parts}", parts[1]); } } } @@ -905,30 +905,35 @@ namespace Jellyfin.Networking.Manager /// private void InitialiseBind(NetworkConfiguration config) { - string[] lanAddresses = config.LocalNetworkAddresses; - - // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334 - - if (lanAddresses.Length == 1 && lanAddresses[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1) + lock (_intLock) { - lanAddresses = lanAddresses[0].Split(','); + string[] lanAddresses = config.LocalNetworkAddresses; + + // TODO: remove when bug fixed: https://github.com/jellyfin/jellyfin-web/issues/1334 + + if (lanAddresses.Length == 1 && lanAddresses[0].IndexOf(',', StringComparison.OrdinalIgnoreCase) != -1) + { + lanAddresses = lanAddresses[0].Split(','); + } + + // TODO: end fix: https://github.com/jellyfin/jellyfin-web/issues/1334 + + // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. + if (config.IgnoreVirtualInterfaces) + { + var virtualInterfaceNames = config.VirtualInterfaceNames.Split(','); + var newList = new string[lanAddresses.Length + virtualInterfaceNames.Length]; + Array.Copy(lanAddresses, newList, lanAddresses.Length); + Array.Copy(virtualInterfaceNames, 0, newList, lanAddresses.Length, virtualInterfaceNames.Length); + lanAddresses = newList; + } + + // Read and parse bind addresses and exclusions, removing ones that don't exist. + _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); + _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); + _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); + _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); } - - // TODO: end fix: https://github.com/jellyfin/jellyfin-web/issues/1334 - - // Add virtual machine interface names to the list of bind exclusions, so that they are auto-excluded. - if (config.IgnoreVirtualInterfaces) - { - var newList = lanAddresses.ToList(); - newList.AddRange(config.VirtualInterfaceNames.Split(',').ToList()); - lanAddresses = newList.ToArray(); - } - - // Read and parse bind addresses and exclusions, removing ones that don't exist. - _bindAddresses = CreateIPCollection(lanAddresses).Union(_interfaceAddresses); - _bindExclusions = CreateIPCollection(lanAddresses, true).Union(_interfaceAddresses); - _logger.LogInformation("Using bind addresses: {0}", _bindAddresses.AsString()); - _logger.LogInformation("Using bind exclusions: {0}", _bindExclusions.AsString()); } /// @@ -936,7 +941,10 @@ namespace Jellyfin.Networking.Manager /// private void InitialiseRemote(NetworkConfiguration config) { - RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter); + lock (_intLock) + { + RemoteAddressFilter = CreateIPCollection(config.RemoteIPFilter); + } } /// @@ -959,7 +967,8 @@ namespace Jellyfin.Networking.Manager // If no LAN addresses are specified - all private subnets are deemed to be the LAN _usingPrivateAddresses = _lanSubnets.Count == 0; - // NOTE: The order of the commands in this statement matters. + // NOTE: The order of the commands generating the collection in this statement matters. + // Altering the order will cause the collections to be created incorrectly. if (_usingPrivateAddresses) { _logger.LogDebug("Using LAN interface addresses as user provided no LAN details."); @@ -1020,6 +1029,7 @@ namespace Jellyfin.Networking.Manager _interfaceNames.Clear(); _interfaceAddresses.Clear(); + _macAddresses.Clear(); try { @@ -1051,7 +1061,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) + if (ipProperties.GatewayAddresses.Count > 0 && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1072,7 +1082,7 @@ namespace Jellyfin.Networking.Manager }; int tag = nw.Tag; - if ((ipProperties.GatewayAddresses.Count > 0) && !nw.IsLoopback()) + if (ipProperties.GatewayAddresses.Count > 0 && !nw.IsLoopback()) { // -ve Tags signify the interface has a gateway. nw.Tag *= -1; @@ -1087,9 +1097,10 @@ namespace Jellyfin.Networking.Manager } } #pragma warning disable CA1031 // Do not catch general exception types - catch + catch (Exception ex) { // Ignore error, and attempt to continue. + _logger.LogError(ex, "Error encountered parsing interfaces."); } #pragma warning restore CA1031 // Do not catch general exception types } @@ -1100,8 +1111,7 @@ namespace Jellyfin.Networking.Manager // If for some reason we don't have an interface info, resolve our DNS name. if (_interfaceAddresses.Count == 0) { - _logger.LogWarning("No interfaces information available. Using loopback."); - + _logger.LogError("No interfaces information available. Resolving DNS name."); IPHost host = new IPHost(Dns.GetHostName()); foreach (var a in host.GetAddresses()) { @@ -1110,7 +1120,7 @@ namespace Jellyfin.Networking.Manager if (_interfaceAddresses.Count == 0) { - _logger.LogError("No interfaces information available. Resolving DNS name."); + _logger.LogWarning("No interfaces information available. Using loopback."); // Last ditch attempt - use loopback address. _interfaceAddresses.AddItem(IPNetAddress.IP4Loopback); if (IsIP6Enabled) @@ -1131,11 +1141,11 @@ namespace Jellyfin.Networking.Manager /// Attempts to match the source against a user defined bind interface. /// /// IP source address to use. - /// True if the source is in the external subnet. + /// True if the source is in the external subnet. /// The published server url that matches the source address. /// The resultant port, if one exists. /// true if a match is found, false otherwise. - private bool MatchesPublishedServerUrl(IPObject source, bool isExternal, out string bindPreference, out int? port) + private bool MatchesPublishedServerUrl(IPObject source, bool isInExternalSubnet, out string bindPreference, out int? port) { bindPreference = string.Empty; port = null; @@ -1144,12 +1154,12 @@ namespace Jellyfin.Networking.Manager foreach (var addr in _publishedServerUrls) { // Remaining. Match anything. - if (addr.Key.Equals(IPAddress.Broadcast)) + if (addr.Key.Address.Equals(IPAddress.Broadcast)) { bindPreference = addr.Value; break; } - else if ((addr.Key.Equals(IPAddress.Any) || addr.Key.Equals(IPAddress.IPv6Any)) && isExternal) + else if ((addr.Key.Address.Equals(IPAddress.Any) || addr.Key.Address.Equals(IPAddress.IPv6Any)) && isInExternalSubnet) { // External. bindPreference = addr.Value; @@ -1163,38 +1173,38 @@ namespace Jellyfin.Networking.Manager } } - if (!string.IsNullOrEmpty(bindPreference)) + if (string.IsNullOrEmpty(bindPreference)) { - // Has it got a port defined? - var parts = bindPreference.Split(':'); - if (parts.Length > 1) - { - if (int.TryParse(parts[1], out int p)) - { - bindPreference = parts[0]; - port = p; - } - } - - return true; + return false; } - return false; + // Has it got a port defined? + var parts = bindPreference.Split(':'); + if (parts.Length > 1) + { + if (int.TryParse(parts[1], out int p)) + { + bindPreference = parts[0]; + port = p; + } + } + + return true; } /// /// Attempts to match the source against a user defined bind interface. /// /// IP source address to use. - /// True if the source is in the external subnet. + /// True if the source is in the external subnet. /// The result, if a match is found. /// true if a match is found, false otherwise. - private bool MatchesBindInterface(IPObject source, bool isExternal, out string result) + private bool MatchesBindInterface(IPObject source, bool isInExternalSubnet, out string result) { result = string.Empty; - var nc = _bindAddresses.Exclude(_bindExclusions); + var addresses = _bindAddresses.Exclude(_bindExclusions); - int count = nc.Count; + int count = addresses.Count; if (count == 1 && (_bindAddresses[0].Equals(IPAddress.Any) || _bindAddresses[0].Equals(IPAddress.IPv6Any))) { // Ignore IPAny addresses. @@ -1205,26 +1215,34 @@ namespace Jellyfin.Networking.Manager { // Check to see if any of the bind interfaces are in the same subnet. - Collection bindResult; IPAddress? defaultGateway = null; - IPAddress? bindAddress; + IPAddress? bindAddress = null; - if (isExternal) + if (isInExternalSubnet) { // Find all external bind addresses. Store the default gateway, but check to see if there is a better match first. - bindResult = CreateCollection(nc - .Where(p => !IsInLocalNetwork(p)) - .OrderBy(p => p.Tag)); - defaultGateway = bindResult.FirstOrDefault()?.Address; - bindAddress = bindResult - .Where(p => p.Contains(source)) - .OrderBy(p => p.Tag) - .FirstOrDefault()?.Address; + foreach (var addr in addresses.OrderBy(p => p.Tag)) + { + if (defaultGateway == null && !IsInLocalNetwork(addr)) + { + defaultGateway = addr.Address; + } + + if (bindAddress == null && addr.Contains(source)) + { + bindAddress = addr.Address; + } + + if (defaultGateway != null && bindAddress != null) + { + break; + } + } } else { // Look for the best internal address. - bindAddress = nc + bindAddress = addresses .Where(p => IsInLocalNetwork(p) && (p.Contains(source) || p.Equals(IPAddress.None))) .OrderBy(p => p.Tag) .FirstOrDefault()?.Address; @@ -1233,23 +1251,23 @@ namespace Jellyfin.Networking.Manager if (bindAddress != null) { result = FormatIP6String(bindAddress); - _logger.LogDebug("{0}: GetBindInterface: Has source, found a match bind interface subnets. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Has source, found a match bind interface subnets. {Result}", source, result); return true; } - if (isExternal && defaultGateway != null) + if (isInExternalSubnet && defaultGateway != null) { result = FormatIP6String(defaultGateway); - _logger.LogDebug("{0}: GetBindInterface: Using first user defined external interface. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Using first user defined external interface. {Result}", source, result); return true; } - result = FormatIP6String(nc.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first user defined interface. {1}", source, result); + result = FormatIP6String(addresses[0].Address); + _logger.LogDebug("{Source}: GetBindInterface: Selected first user defined interface. {Result}", source, result); - if (isExternal) + if (isInExternalSubnet) { - _logger.LogWarning("{0}: External request received, however, only an internal interface bind found.", source); + _logger.LogWarning("{Source}: External request received, however, only an internal interface bind found.", source); } return true; @@ -1268,12 +1286,12 @@ namespace Jellyfin.Networking.Manager { result = string.Empty; // Get the first WAN interface address that isn't a loopback. - var extResult = CreateCollection(_interfaceAddresses + var extResult = _interfaceAddresses .Exclude(_bindExclusions) .Where(p => !IsInLocalNetwork(p)) - .OrderBy(p => p.Tag)); + .OrderBy(p => p.Tag); - if (extResult.Count > 0) + if (extResult.Any()) { // Does the request originate in one of the interface subnets? // (For systems with multiple internal network cards, and multiple subnets) @@ -1282,19 +1300,19 @@ namespace Jellyfin.Networking.Manager if (!IsInLocalNetwork(intf) && intf.Contains(source)) { result = FormatIP6String(intf.Address); - _logger.LogDebug("{0}: GetBindInterface: Selected best external on interface on range. {1}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Selected best external on interface on range. {Result}", source, result); return true; } } result = FormatIP6String(extResult.First().Address); - _logger.LogDebug("{0}: GetBindInterface: Selected first external interface. {0}", source, result); + _logger.LogDebug("{Source}: GetBindInterface: Selected first external interface. {Result}", source, result); return true; } // Have to return something, so return an internal address - _logger.LogWarning("{0}: External request received, however, no WAN interface found.", source); + _logger.LogWarning("{Source}: External request received, however, no WAN interface found.", source); return false; } } diff --git a/MediaBrowser.Common/Net/IPHost.cs b/MediaBrowser.Common/Net/IPHost.cs index f9e1568efc..4cede9ab16 100644 --- a/MediaBrowser.Common/Net/IPHost.cs +++ b/MediaBrowser.Common/Net/IPHost.cs @@ -1,5 +1,6 @@ #nullable enable using System; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Sockets; @@ -13,6 +14,11 @@ namespace MediaBrowser.Common.Net /// public class IPHost : IPObject { + /// + /// Gets or sets timeout value before resolve required, in minutes. + /// + public const int Timeout = 30; + /// /// Represents an IPHost that has no value. /// @@ -21,7 +27,7 @@ namespace MediaBrowser.Common.Net /// /// Time when last resolved in ticks. /// - private long _lastResolved; + private DateTime? _lastResolved = null; /// /// Gets the IP Addresses, attempting to resolve the name, if there are none. @@ -83,15 +89,9 @@ namespace MediaBrowser.Common.Net { // Not implemented, as a host object can only have a prefix length of 128 (IPv6) or 32 (IPv4) prefix length, // which is automatically determined by it's IP type. Anything else is meaningless. - throw new NotImplementedException("The prefix length on a host cannot be set."); } } - /// - /// Gets or sets timeout value before resolve required, in minutes. - /// - public byte Timeout { get; set; } = 30; - /// /// Gets a value indicating whether the address has a value. /// @@ -395,15 +395,15 @@ namespace MediaBrowser.Common.Net private bool ResolveHost() { // When was the last time we resolved? - if (_lastResolved == 0) + if (_lastResolved == null) { - _lastResolved = DateTime.UtcNow.Ticks; + _lastResolved = DateTime.UtcNow; } - // If we haven't resolved before, or out timer has run out... - if ((_addresses.Length == 0 && !Resolved) || (TimeSpan.FromTicks(DateTime.UtcNow.Ticks - _lastResolved).TotalMinutes > Timeout)) + // If we haven't resolved before, or our timer has run out... + if ((_addresses.Length == 0 && !Resolved) || (DateTime.UtcNow > _lastResolved?.AddMinutes(Timeout))) { - _lastResolved = DateTime.UtcNow.Ticks; + _lastResolved = DateTime.UtcNow; ResolveHostInternal().GetAwaiter().GetResult(); Resolved = true; } @@ -433,9 +433,10 @@ namespace MediaBrowser.Common.Net IPHostEntry ip = await Dns.GetHostEntryAsync(HostName).ConfigureAwait(false); _addresses = ip.AddressList; } - catch (SocketException) + catch (SocketException ex) { - // Ignore socket errors, as the result value will just be an empty array. + // Log and then ignore socket errors, as the result value will just be an empty array. + Debug.WriteLine("GetHostEntryAsync failed with {Message}.", ex.Message); } } } diff --git a/MediaBrowser.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs index 0d28c35cb8..a6f5fe4b37 100644 --- a/MediaBrowser.Common/Net/IPNetAddress.cs +++ b/MediaBrowser.Common/Net/IPNetAddress.cs @@ -18,17 +18,17 @@ namespace MediaBrowser.Common.Net /// /// IPv4 multicast address. /// - public static readonly IPAddress MulticastIPv4 = IPAddress.Parse("239.255.255.250"); + public static readonly IPAddress SSDPMulticastIPv4 = IPAddress.Parse("239.255.255.250"); /// /// IPv6 local link multicast address. /// - public static readonly IPAddress MulticastIPv6LinkLocal = IPAddress.Parse("ff02::C"); + public static readonly IPAddress SSDPMulticastIPv6LinkLocal = IPAddress.Parse("ff02::C"); /// /// IPv6 site local multicast address. /// - public static readonly IPAddress MulticastIPv6SiteLocal = IPAddress.Parse("ff05::C"); + public static readonly IPAddress SSDPMulticastIPv6SiteLocal = IPAddress.Parse("ff05::C"); /// /// IP4Loopback address host. @@ -235,7 +235,7 @@ namespace MediaBrowser.Common.Net /// /// Returns a textual representation of this object. /// - /// Set to true, if the subnet is to be included as part of the address. + /// Set to true, if the subnet is to be excluded as part of the address. /// String representation of this object. public string ToString(bool shortVersion) { diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs index d18ac98933..69cd57f8ae 100644 --- a/MediaBrowser.Common/Net/IPObject.cs +++ b/MediaBrowser.Common/Net/IPObject.cs @@ -86,7 +86,9 @@ namespace MediaBrowser.Common.Net // prefix length value. eg. /16 on a 4 octet ip4 address (192.168.2.240) will result in the 2 and the 240 being zeroed out. // Where there is not an exact boundary (eg /23), mod is used to calculate how many bits of this value are to be kept. - byte[] addressBytes = address.GetAddressBytes(); + // GetAddressBytes + Span addressBytes = stackalloc byte[address.AddressFamily == AddressFamily.InterNetwork ? 4 : 16]; + address.TryWriteBytes(addressBytes, out _); int div = prefixLength / 8; int mod = prefixLength % 8; @@ -170,14 +172,16 @@ namespace MediaBrowser.Common.Net if (!address.Equals(IPAddress.None)) { + if (address.IsIPv4MappedToIPv6) + { + address = address.MapToIPv4(); + } + if (address.AddressFamily == AddressFamily.InterNetwork) { - if (address.IsIPv4MappedToIPv6) - { - address = address.MapToIPv4(); - } - - byte[] octet = address.GetAddressBytes(); + // GetAddressBytes + Span octet = stackalloc byte[4]; + address.TryWriteBytes(octet, out _); return (octet[0] == 10) || (octet[0] == 172 && octet[1] >= 16 && octet[1] <= 31) // RFC1918 @@ -186,7 +190,10 @@ namespace MediaBrowser.Common.Net } else { - byte[] octet = address.GetAddressBytes(); + // GetAddressBytes + Span octet = stackalloc byte[16]; + address.TryWriteBytes(octet, out _); + uint word = (uint)(octet[0] << 8) + octet[1]; return (word >= 0xfe80 && word <= 0xfebf) // fe80::/10 :Local link. @@ -223,7 +230,9 @@ namespace MediaBrowser.Common.Net return false; } - byte[] octet = address.GetAddressBytes(); + // GetAddressBytes + Span octet = stackalloc byte[16]; + address.TryWriteBytes(octet, out _); uint word = (uint)(octet[0] << 8) + octet[1]; return word >= 0xfe80 && word <= 0xfebf; // fe80::/10 :Local link. @@ -261,7 +270,9 @@ namespace MediaBrowser.Common.Net byte cidrnet = 0; if (!mask.Equals(IPAddress.Any)) { - byte[] bytes = mask.GetAddressBytes(); + // GetAddressBytes + Span bytes = stackalloc byte[mask.AddressFamily == AddressFamily.InterNetwork ? 4 : 16]; + mask.TryWriteBytes(bytes, out _); var zeroed = false; for (var i = 0; i < bytes.Length; i++)