174 lines
3.7 KiB
V
174 lines
3.7 KiB
V
// ©AngelaMos | 2026
|
|
// common.v
|
|
|
|
module parser
|
|
|
|
import src.models { Action, ChainType, ConnState, NetworkAddr, PortSpec, Protocol, RuleSource, Table }
|
|
|
|
pub fn parse_network_addr(s string) !NetworkAddr {
|
|
mut input := s.trim_space()
|
|
mut negated := false
|
|
if input.starts_with('!') {
|
|
negated = true
|
|
input = input[1..].trim_space()
|
|
}
|
|
if input.contains('/') {
|
|
parts := input.split('/')
|
|
if parts.len != 2 {
|
|
return error('invalid CIDR notation: ${s}')
|
|
}
|
|
cidr := parts[1].int()
|
|
if cidr < 0 || cidr > 128 {
|
|
return error('invalid CIDR prefix length: ${parts[1]}')
|
|
}
|
|
return NetworkAddr{
|
|
address: parts[0]
|
|
cidr: cidr
|
|
negated: negated
|
|
}
|
|
}
|
|
return NetworkAddr{
|
|
address: input
|
|
cidr: 32
|
|
negated: negated
|
|
}
|
|
}
|
|
|
|
pub fn parse_port_spec(s string) !PortSpec {
|
|
mut input := s.trim_space()
|
|
mut negated := false
|
|
if input.starts_with('!') {
|
|
negated = true
|
|
input = input[1..].trim_space()
|
|
}
|
|
if input.contains(':') {
|
|
parts := input.split(':')
|
|
if parts.len != 2 {
|
|
return error('invalid port range: ${s}')
|
|
}
|
|
start := parts[0].int()
|
|
end := parts[1].int()
|
|
if start < 0 || start > 65535 || end < 0 || end > 65535 {
|
|
return error('port out of range: ${s}')
|
|
}
|
|
return PortSpec{
|
|
start: start
|
|
end: end
|
|
negated: negated
|
|
}
|
|
}
|
|
port := input.int()
|
|
if port < 0 || port > 65535 {
|
|
return error('port out of range: ${s}')
|
|
}
|
|
return PortSpec{
|
|
start: port
|
|
end: -1
|
|
negated: negated
|
|
}
|
|
}
|
|
|
|
pub fn parse_port_list(s string) ![]PortSpec {
|
|
mut result := []PortSpec{}
|
|
parts := s.split(',')
|
|
for part in parts {
|
|
trimmed := part.trim_space()
|
|
if trimmed.len == 0 {
|
|
continue
|
|
}
|
|
result << parse_port_spec(trimmed)!
|
|
}
|
|
return result
|
|
}
|
|
|
|
pub fn parse_protocol(s string) !Protocol {
|
|
return match s.to_lower().trim_space() {
|
|
'tcp', '6' { .tcp }
|
|
'udp', '17' { .udp }
|
|
'icmp', '1' { .icmp }
|
|
'icmpv6', 'ipv6-icmp', '58' { .icmpv6 }
|
|
'all', '0' { .all }
|
|
'sctp', '132' { .sctp }
|
|
'gre', '47' { .gre }
|
|
else { error('unknown protocol: ${s}') }
|
|
}
|
|
}
|
|
|
|
pub fn parse_action(s string) !Action {
|
|
return match s.to_upper().trim_space() {
|
|
'ACCEPT' { .accept }
|
|
'DROP' { .drop }
|
|
'REJECT' { .reject }
|
|
'LOG' { .log }
|
|
'MASQUERADE' { .masquerade }
|
|
'SNAT' { .snat }
|
|
'DNAT' { .dnat }
|
|
'RETURN' { .return_action }
|
|
'JUMP' { .jump }
|
|
'QUEUE' { .queue }
|
|
else { error('unknown action: ${s}') }
|
|
}
|
|
}
|
|
|
|
pub fn parse_table(s string) !Table {
|
|
return match s.to_lower().trim_space() {
|
|
'filter' { .filter }
|
|
'nat' { .nat }
|
|
'mangle' { .mangle }
|
|
'raw' { .raw }
|
|
'security' { .security }
|
|
else { error('unknown table: ${s}') }
|
|
}
|
|
}
|
|
|
|
pub fn parse_chain_type(s string) ChainType {
|
|
return match s.to_upper().trim_space() {
|
|
'INPUT' { .input }
|
|
'OUTPUT' { .output }
|
|
'FORWARD' { .forward }
|
|
'PREROUTING' { .prerouting }
|
|
'POSTROUTING' { .postrouting }
|
|
else { .custom }
|
|
}
|
|
}
|
|
|
|
pub fn parse_conn_states(s string) ConnState {
|
|
mut result := ConnState.zero()
|
|
parts := s.to_upper().split(',')
|
|
for part in parts {
|
|
match part.trim_space() {
|
|
'NEW' { result.set(.new_conn) }
|
|
'ESTABLISHED' { result.set(.established) }
|
|
'RELATED' { result.set(.related) }
|
|
'INVALID' { result.set(.invalid) }
|
|
'UNTRACKED' { result.set(.untracked) }
|
|
else {}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
pub fn detect_format(content string) !RuleSource {
|
|
lines := content.split('\n')
|
|
for line in lines {
|
|
trimmed := line.trim_space()
|
|
if trimmed.len == 0 || trimmed.starts_with('#') {
|
|
continue
|
|
}
|
|
if trimmed.starts_with('*') {
|
|
return .iptables
|
|
}
|
|
if trimmed.starts_with('table') {
|
|
return .nftables
|
|
}
|
|
if trimmed.starts_with(':') {
|
|
return .iptables
|
|
}
|
|
if trimmed.starts_with('-A') || trimmed.starts_with('-I') {
|
|
return .iptables
|
|
}
|
|
break
|
|
}
|
|
return error('unable to detect ruleset format')
|
|
}
|