Skip to content

feat: add Proton VPN config parsing support#129

Merged
enesoztrk merged 1 commit intotiiuae:mainfrom
enesoztrk:feat/parse-proton-vpn
Jan 21, 2026
Merged

feat: add Proton VPN config parsing support#129
enesoztrk merged 1 commit intotiiuae:mainfrom
enesoztrk:feat/parse-proton-vpn

Conversation

@enesoztrk
Copy link
Copy Markdown
Collaborator

@enesoztrk enesoztrk commented Jan 20, 2026

Add support for parsing Proton VPN generated WireGuard config files:

  • Extract # Key for <user> as interface name
  • Extract first standalone comment after [Peer] as peer name (e.g., NL-FREE#241, AT#133)
  • Log unknown comment keys as warnings
  • Return errors for unknown attribute keys

Signed-off-by: Enes Öztürk <enes.ozturk@unikie.com>
Copy link
Copy Markdown

@slakkala slakkala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving, left some suggestions that might help make the code more readable.

// Handle comment lines (# key = value or # standalone comment)
if let Some((key, value)) = comment.split_once('=') {
let key = key.trim();
let value = value.trim().to_string();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is done for unknown keys now too; doesn't probably matter much though.

}
i => return Err(format!("Unexpected interface name {i}.")),
},
LineType::Comment(comment) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the below would simplify a bit with guards, LineType::Comment(comment) if is_in_interface => { ... } and same for is_in_peer. The nesting is getting somewhat deep as it is now.

enum LineType {
Section(String),
Attribute(String, String),
Comment(String),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since not implementing a generic ini parser, maybe also have ExtAttribute(String, String) for commented key-values.


// We can be either in interface section or in peer section
let mut is_in_interface = false;
let mut is_in_peer = false;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rather use an enum for this to make it clear that these are mutually exclusive, something like:

#[derive(Copy, Clone, PartialEq)]
enum Section {
    None,
    Interface,
    Peer,
}
let mut section = Section::None;

@@ -99,6 +99,7 @@ pub fn parse_config(s: &str) -> Result<WireguardConfig, String> {
enum LineType {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no real need to allocate all of this, could make it:

enum LineType<'a> {
    Section(&'a str),
    Attribute(&'a str, &'a str),
    ...
}

As a simplified middle-step could keep just the attribute keys as references, as their owned values are never used.

If making values references too, it would probably help to extend the MutOptionExt to have something like:

fn set(&mut self, new: impl Into<T>) {
    *self = Some(new.into());
}

@enesoztrk
Copy link
Copy Markdown
Collaborator Author

I have taken note of the comments, and I will address all of them in the next PR.

@enesoztrk enesoztrk merged commit d2873e2 into tiiuae:main Jan 21, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants