Skip to content

Commit d838630

Browse files
committed
keylime: Implement a simple IP parser to remove brackets
The goal of the parser is to support IPv6 addresses to be used in the configuration file with or without brackets. The provided IP addresses are validated using the standard implementation of the IP parser. Signed-off-by: Anderson Toshiyuki Sasaki <[email protected]>
1 parent dcc6f92 commit d838630

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

keylime/src/ip.pest

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
WHITESPACE = _{ " " | NEWLINE | "\t" }
2+
3+
ipv4 = { (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT+ ~ ".") ~ (ASCII_DIGIT)+ }
4+
5+
ipv6 = { (ASCII_HEX_DIGIT* ~ ":")? ~ (ASCII_HEX_DIGIT+ ~ ":")* ~
6+
(ASCII_HEX_DIGIT+)? ~ (":" ~ ASCII_HEX_DIGIT+)* ~ (":" ~ ASCII_HEX_DIGIT*)?}
7+
8+
unbracketed = { ipv4 | ipv6 }
9+
bracketed = { "[" ~ unbracketed ~ "]" }
10+
11+
ip = { SOI ~ (bracketed | unbracketed) ~ EOI }

keylime/src/ip_parser.rs

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Copyright 2022 Keylime Authors
3+
4+
use pest::{iterators::Pair, Parser};
5+
use pest_derive::Parser;
6+
use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr};
7+
use thiserror::Error;
8+
9+
#[derive(Parser)]
10+
#[grammar = "ip.pest"]
11+
pub struct IpParser;
12+
13+
#[derive(Error, Debug)]
14+
pub enum IpParsingError {
15+
#[error("Invalid input {0}")]
16+
InvalidInput(String),
17+
18+
#[error("Invalid IP")]
19+
InvalidIP(#[from] AddrParseError),
20+
21+
#[error("failed to parse the input {input}")]
22+
ParseError {
23+
input: String,
24+
source: Box<pest::error::Error<Rule>>,
25+
},
26+
27+
#[error("Unexpected end of input")]
28+
UnexpectedEOI,
29+
}
30+
31+
fn get_inner_ip(pair: Pair<Rule>) -> Result<&str, IpParsingError> {
32+
let Some(item) = pair.into_inner().next() else {
33+
unreachable!()
34+
};
35+
36+
match item.as_rule() {
37+
Rule::ip | Rule::bracketed | Rule::unbracketed => get_inner_ip(item),
38+
Rule::ipv4 => {
39+
// Validate the IP using the standard parser
40+
let _parsed_ipv4 = item.as_str().parse::<Ipv4Addr>()?;
41+
Ok(item.as_str())
42+
}
43+
Rule::ipv6 => {
44+
// Validate the IP using the standard parser
45+
let _parsed_ipv6 = item.as_str().parse::<Ipv6Addr>()?;
46+
Ok(item.as_str())
47+
}
48+
Rule::EOI => Err(IpParsingError::UnexpectedEOI),
49+
_ => {
50+
unreachable!()
51+
}
52+
}
53+
}
54+
55+
/// Parses an ip address from a string slice removing eventual brackets.
56+
/// This is mostly to remove brackets when using IPv6
57+
///
58+
/// Both IPv4 and IPv6 are supported:
59+
///
60+
/// * The IPv4 and IPv6 can be inside square brackets ("[]") or not
61+
///
62+
/// # Arguments
63+
///
64+
/// * `ip` the string to be parsed
65+
///
66+
/// # Returns
67+
///
68+
/// The obtained ip as a &str without brackets, if they were present
69+
///
70+
/// # Examples
71+
///
72+
/// Valid input lists, and respective result:
73+
///
74+
/// * `127.0.0.1` => `127.0.0.1`
75+
/// * `::1` => `::1`
76+
/// * `[127.0.0.1]` => `127.0.0.1
77+
/// * `[::1]` => `::1`
78+
pub fn parse_ip(ip: &str) -> Result<&str, IpParsingError> {
79+
let Some(pair) = IpParser::parse(Rule::ip, ip)
80+
.map_err(|e| IpParsingError::ParseError {
81+
input: ip.to_string(),
82+
source: Box::new(e),
83+
})?
84+
.next()
85+
else {
86+
return Err(IpParsingError::InvalidInput(ip.to_string()));
87+
};
88+
return get_inner_ip(pair);
89+
}
90+
91+
// Unit Testing
92+
#[cfg(test)]
93+
mod tests {
94+
use super::*;
95+
96+
#[test]
97+
fn test_parse_ip() {
98+
// Sanity: most common case
99+
assert_eq!(parse_ip("127.0.0.1").unwrap(), "127.0.0.1"); //#[allow_ci]
100+
assert_eq!(parse_ip("[127.0.0.1]").unwrap(), "127.0.0.1"); //#[allow_ci]
101+
assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci]
102+
assert_eq!(parse_ip("[::1]").unwrap(), "::1"); //#[allow_ci]
103+
104+
// More advanced cases
105+
assert_eq!(parse_ip("::").unwrap(), "::"); //#[allow_ci]
106+
assert_eq!(parse_ip("::1").unwrap(), "::1"); //#[allow_ci]
107+
assert_eq!(parse_ip("1::").unwrap(), "1::"); //#[allow_ci]
108+
assert_eq!(parse_ip("1::1").unwrap(), "1::1"); //#[allow_ci]
109+
assert_eq!(parse_ip("[1::1]").unwrap(), "1::1"); //#[allow_ci]
110+
assert_eq!(parse_ip("1::2:3:4").unwrap(), "1::2:3:4"); //#[allow_ci]
111+
assert_eq!(parse_ip("1:2::3:4").unwrap(), "1:2::3:4"); //#[allow_ci]
112+
assert_eq!(parse_ip("1:2:3::4").unwrap(), "1:2:3::4"); //#[allow_ci]
113+
assert_eq!(parse_ip("1:2:3:4:5:6:7:8").unwrap(), "1:2:3:4:5:6:7:8"); //#[allow_ci]
114+
115+
// Invalid input
116+
assert!(parse_ip("1").is_err());
117+
assert!(parse_ip("1.2").is_err());
118+
assert!(parse_ip("1.2.3.4.5").is_err());
119+
assert!(parse_ip("1:2:3").is_err());
120+
assert!(parse_ip("1::2::3").is_err());
121+
assert!(parse_ip("1:2::3:4::5:6").is_err());
122+
}
123+
}

keylime/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod algorithms;
22
pub mod crypto;
33
pub mod ima;
4+
pub mod ip_parser;
45
pub mod list_parser;
56
pub mod tpm;
67

0 commit comments

Comments
 (0)