public ConfigurationProperty(ConfigurationBase configuration, ConfigurationProperty dependentProperty = null) =>
public ClientConfiguration(ClientConfigurationList parentList) { _parentList = parentList; // Client properties PrivateKeyProperty.TargetTypes.Add(GetType()); DnsProperty.TargetTypes.Add(GetType()); AddressProperty.TargetTypes.Add(GetType()); // Server properties PresharedKeyProperty.TargetTypes.Add(typeof(ServerConfiguration)); PublicKeyProperty.TargetTypes.Add(typeof(ServerConfiguration)); ServerPersistentKeepaliveProperty.TargetTypes.Add(typeof(ServerConfiguration)); var serverConfiguration = new ServerConfiguration().Load <ServerConfiguration>(Configuration.LoadFromFile(ServerConfigurationPrerequisite.ServerDataPath)); string serverIp = serverConfiguration.AddressProperty.Value; // Add support for generating client IP AddressProperty.Action = new ConfigurationPropertyAction(this) { Name = nameof(Resources.GenerateFromServerAction), Description = string.Format(Resources.GenerateClientAddressActionDescription, serverIp), DependentProperty = serverConfiguration.AddressProperty, DependencySatisfiedFunc = prop => string.IsNullOrEmpty(prop.Validation?.Validate?.Invoke(prop)), Action = (conf, prop) => { IPNetwork serverNetwork = IPNetwork.Parse(serverConfiguration.AddressProperty.Value); var possibleAddresses = serverNetwork.ListIPAddress().Skip(2).SkipLast(1).ToList(); // Skip reserved .0 and .1 and .255. // If the current address is already in range, we're done if (possibleAddresses.Select(a => a.ToString()).Contains(prop.Value)) { return; } WaitCursor.SetOverrideCursor(Cursors.Wait); var existingAddresses = parentList.List.Select(c => c.AddressProperty.Value); // Find the first address that isn't used by another client prop.Value = possibleAddresses.FirstOrDefault(a => existingAddresses.Contains(a.ToString()) == false)?.ToString(); WaitCursor.SetOverrideCursor(null); } }; // Do custom validation on the Address (we want a specific IP or a CIDR with /32) AddressProperty.Validation = new ConfigurationPropertyValidation { Validate = obj => { string result = default; // First, try parsing with IPNetwork to see if it's in CIDR format if (IPNetwork.TryParse(obj.Value, out var network)) { // At this point, we know it's a valid network. Let's see how many addresses are in range if (network.Usable > 1) { // It's CIDR, but it defines more than one address. // However, IPNetwork has a quirk that parses single addresses (without mask) as a range. // So now let's see if it's a single address if (IPAddress.TryParse(obj.Value, out _) == false) { // If we get here, it passed CIDR parsing, but it defined more than one address (i.e., had a mask). It's bad! result = Resources.ClientAddressValidationError; } // Else, it's a single address as parsed by IPAddress, so we're good! } // Else // It's in CIDR notation and only defines a single address (/32) so we're good! } else { // Not even IPNetwork could parse it, so it's really bad! result = Resources.ClientAddressValidationError; } return(result); } }; // The client generates the PSK PresharedKeyProperty.Action = new ConfigurationPropertyAction(this) { Name = $"{nameof(PresharedKeyProperty)}{nameof(ConfigurationProperty.Action)}", Action = (conf, prop) => { WaitCursor.SetOverrideCursor(Cursors.Wait); prop.Value = new WireGuardExe().ExecuteCommand(new GeneratePresharedKeyCommand()); WaitCursor.SetOverrideCursor(null); } }; // Allowed IPs is special, because for the server, it's the same as the Address property for the client var allowedIpsProperty = new ConfigurationProperty(this) { PersistentPropertyName = "AllowedIPs", Value = AddressProperty.Value, IsHidden = true }; allowedIpsProperty.TargetTypes.Add(typeof(ServerConfiguration)); AddressProperty.PropertyChanged += (_, args) => { if (args.PropertyName == nameof(AddressProperty.Value)) { allowedIpsProperty.Value = AddressProperty.Value; } }; Properties.Add(allowedIpsProperty); // Adjust index of properties and resort AddressProperty.Index = 1; DnsProperty.Index = 2; SortProperties(); Properties.ForEach(p => p.PropertyChanged += (_, args) => { if (args.PropertyName == nameof(p.Value)) { GenerateQrCodeAction.RaisePropertyChanged(nameof(GenerateQrCodeAction.DependencySatisfied)); ExportConfigurationFileAction.RaisePropertyChanged(nameof(ExportConfigurationFileAction.DependencySatisfied)); } }); }