예제 #1
0
 public ConfigurationProperty(ConfigurationBase configuration, ConfigurationProperty dependentProperty = null) =>
예제 #2
0
        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));
                }
            });
        }