///|
fn usage() -> String {
(
#|proxyproto-tool --help
#|proxyproto-tool decode-v1 TEXT
#|proxyproto-tool decode-v2 --hex HEX
#|proxyproto-tool decode-auto --hex HEX
#|proxyproto-tool encode-v1 UNKNOWN
#|proxyproto-tool encode-v1 TCP4|TCP6 SOURCE DESTINATION SOURCE_PORT DESTINATION_PORT
#|proxyproto-tool encode-v2 LOCAL
#|proxyproto-tool encode-v2 TCP4|UDP4|TCP6|UDP6 SOURCE DESTINATION SOURCE_PORT DESTINATION_PORT
#|proxyproto-tool inspect-tlv --hex HEX
#|proxyproto-tool verify-crc32c --hex HEX
#|proxyproto-tool split-test --hex HEX
#|proxyproto-tool vector-check
#|
#|decode-auto 仅用于离线诊断,不应作为公开网络监听端的协议自动探测策略。
)
}
///|
fn fail(message : String) -> Unit {
println("{\"error\":\"" + message + "\"}")
abort("proxyproto-tool failed")
}
///|
fn cli_port(text : String) -> Int {
let bytes = @utf8.encode(text, bom=false)
if bytes.length() == 0 {
fail("invalid_port")
return 0
}
let mut value = 0
for byte in bytes {
if byte < b'0' || byte > b'9' {
fail("invalid_port")
return 0
}
value = value * 10 + byte.to_int() - b'0'.to_int()
if value > 65535 {
fail("invalid_port")
return 0
}
}
value
}
///|
fn tlvs_json(tlvs : Array[@proxyproto.RawTlv]) -> String {
let mut output = "["
for index = 0; index < tlvs.length(); index = index + 1 {
if index != 0 {
output = output + ","
}
let tlv = tlvs[index]
output = output +
"{\"type\":\"" +
@proxyproto.tlv_type_name(tlv.type_code) +
"\",\"code\":" +
tlv.type_code.to_int().to_string() +
",\"length\":" +
tlv.value.length().to_string() +
",\"value_hex\":\"" +
@proxyproto.hex_encode(tlv.value) +
"\"}"
}
output + "]"
}
///|
fn frame_addresses(
header : @proxyproto.ProxyHeader,
) -> (String, String, String, String, Int) {
match header.address {
NoAddress => ("", "", "null", "null", 0)
RawUnsupported(raw) =>
(@proxyproto.hex_encode(raw), "", "null", "null", raw.length())
Ipv4(endpoints) =>
(
@proxyproto.format_ipv4(endpoints.source_address),
@proxyproto.format_ipv4(endpoints.destination_address),
endpoints.source_port.to_string(),
endpoints.destination_port.to_string(),
12,
)
Ipv6(endpoints) =>
(
@proxyproto.format_ipv6(endpoints.source_address),
@proxyproto.format_ipv6(endpoints.destination_address),
endpoints.source_port.to_string(),
endpoints.destination_port.to_string(),
36,
)
UnixAddress(endpoints) =>
(
@proxyproto.hex_encode(endpoints.source),
@proxyproto.hex_encode(endpoints.destination),
"null",
"null",
216,
)
}
}
///|
fn print_frame(frame : @proxyproto.DecodedFrame) -> Unit {
let version = match frame.header.version {
V1 => "v1"
V2 => "v2"
}
let command = match frame.header.command {
Local => "local"
Proxy => "proxy"
}
let family = match frame.header.family {
Unspec => "unspec"
Inet => "inet"
Inet6 => "inet6"
Unix => "unix"
}
let transport = match frame.header.transport {
Unspec => "unspec"
Stream => "stream"
Datagram => "datagram"
}
let addresses = frame_addresses(frame.header)
let crc_status = if @proxyproto.find_tlv(
frame.header.tlvs,
@proxyproto.TLV_CRC32C,
)
is Some(_) {
"valid"
} else {
"not_present"
}
println(
"{\"version\":\"" +
version +
"\",\"command\":\"" +
command +
"\",\"family\":\"" +
family +
"\",\"transport\":\"" +
transport +
"\",\"source_address\":\"" +
addresses.0 +
"\",\"destination_address\":\"" +
addresses.1 +
"\",\"source_port\":" +
addresses.2 +
",\"destination_port\":" +
addresses.3 +
",\"address_length\":" +
addresses.4.to_string() +
",\"header_bytes\":" +
frame.consumed.to_string() +
",\"remaining_payload_bytes\":" +
frame.remaining.length().to_string() +
",\"remaining_payload_hex_prefix\":\"" +
@proxyproto.hex_encode(frame.remaining) +
"\",\"crc32c_status\":\"" +
crc_status +
"\",\"tlvs\":" +
tlvs_json(frame.header.tlvs) +
"}",
)
}
///|
fn decode_hex_arg(args : Array[String]) -> Bytes {
if args.length() != 4 || args[2] != "--hex" {
fail("expected_--hex_HEX")
return Bytes::new(0)
}
match @proxyproto.hex_decode(args[3]) {
Ok(value) => value
Err(_) => {
fail("invalid_hex")
Bytes::new(0)
}
}
}
///|
fn run_decode_v1(args : Array[String]) -> Unit {
if args.length() != 3 {
fail("decode-v1_requires_TEXT")
return
}
let wire = @utf8.encode(args[2] + "\r\n", bom=false)
match @proxyproto.decode_v1(wire, @proxyproto.DecodePolicy::v1_only()) {
Ok(frame) => print_frame(frame)
Err(_) => fail("decode_v1_failed")
}
}
///|
fn run_decode_v2(args : Array[String]) -> Unit {
let wire = decode_hex_arg(args)
match @proxyproto.decode_v2(wire, @proxyproto.DecodePolicy::v2_only()) {
Ok(frame) => print_frame(frame)
Err(_) => fail("decode_v2_failed")
}
}
///|
fn run_decode_auto(args : Array[String]) -> Unit {
let wire = decode_hex_arg(args)
if wire.length() == 0 {
fail("empty_input")
return
}
let result = if wire[0] == b'P' {
@proxyproto.decode_v1(wire, @proxyproto.DecodePolicy::v1_only())
} else {
@proxyproto.decode_v2(wire, @proxyproto.DecodePolicy::v2_only())
}
match result {
Ok(frame) => print_frame(frame)
Err(_) => fail("decode_auto_failed")
}
}
///|
fn run_encode_v1(args : Array[String]) -> Unit {
let text = if args.length() == 3 && args[2] == "UNKNOWN" {
"PROXY UNKNOWN\r\n"
} else if args.length() == 7 && (args[2] == "TCP4" || args[2] == "TCP6") {
"PROXY " +
args[2] +
" " +
args[3] +
" " +
args[4] +
" " +
args[5] +
" " +
args[6] +
"\r\n"
} else {
fail("invalid_encode_v1_arguments")
return
}
match
@proxyproto.decode_v1(
@utf8.encode(text, bom=false),
@proxyproto.DecodePolicy::v1_only(),
) {
Ok(frame) =>
match @proxyproto.encode_v1(frame.header) {
Ok(encoded) =>
println("{\"hex\":\"" + @proxyproto.hex_encode(encoded) + "\"}")
Err(_) => fail("encode_v1_failed")
}
Err(_) => fail("invalid_v1_model")
}
}
///|
fn run_encode_v2(args : Array[String]) -> Unit {
let header = if args.length() == 3 && args[2] == "LOCAL" {
@proxyproto.ProxyHeader::new(V2, Local, Unspec, Unspec, NoAddress, []).unwrap()
} else if args.length() == 7 {
let kind = args[2]
let transport : @proxyproto.Transport = if kind == "UDP4" || kind == "UDP6" {
Datagram
} else {
Stream
}
if kind == "TCP4" || kind == "UDP4" {
@proxyproto.ProxyHeader::new(
V2,
Proxy,
Inet,
transport,
Ipv4({
source_address: @proxyproto.parse_ipv4_text(args[3]).unwrap(),
destination_address: @proxyproto.parse_ipv4_text(args[4]).unwrap(),
source_port: cli_port(args[5]),
destination_port: cli_port(args[6]),
}),
[],
).unwrap()
} else if kind == "TCP6" || kind == "UDP6" {
@proxyproto.ProxyHeader::new(
V2,
Proxy,
Inet6,
transport,
Ipv6({
source_address: @proxyproto.parse_ipv6_text(args[3]).unwrap(),
destination_address: @proxyproto.parse_ipv6_text(args[4]).unwrap(),
source_port: cli_port(args[5]),
destination_port: cli_port(args[6]),
}),
[],
).unwrap()
} else {
fail("invalid_v2_kind")
return
}
} else {
fail("invalid_encode_v2_arguments")
return
}
match @proxyproto.encode_v2(header) {
Ok(encoded) =>
println("{\"hex\":\"" + @proxyproto.hex_encode(encoded) + "\"}")
Err(_) => fail("encode_v2_failed")
}
}
///|
fn run_inspect_tlv(args : Array[String]) -> Unit {
let wire = decode_hex_arg(args)
match @proxyproto.parse_tlvs(wire) {
Ok(tlvs) =>
match @proxyproto.interpret_all_tlvs(tlvs) {
Ok(_) => println("{\"tlvs\":" + tlvs_json(tlvs) + "}")
Err(_) => fail("tlv_semantic_validation_failed")
}
Err(_) => fail("tlv_parse_failed")
}
}
///|
fn run_verify_crc(args : Array[String]) -> Unit {
let wire = decode_hex_arg(args)
match @proxyproto.verify_proxy_crc32c(wire) {
Ok(_) => println("{\"crc32c_status\":\"valid\"}")
Err(_) => fail("crc32c_invalid")
}
}
///|
fn run_split_test(args : Array[String]) -> Unit {
let wire = decode_hex_arg(args)
let policy = if wire.length() > 0 && wire[0] == b'P' {
@proxyproto.DecodePolicy::v1_only()
} else {
@proxyproto.DecodePolicy::v2_only()
}
let count = @proxyproto.assert_all_single_splits(wire, policy)
println("{\"single_split_cases\":" + count.to_string() + "}")
}
///|
fn main {
let args = @env.args()
if args.length() <= 1 || args[1] == "--help" || args[1] == "-h" {
println(usage())
return
}
match args[1] {
"decode-v1" => run_decode_v1(args)
"decode-v2" => run_decode_v2(args)
"decode-auto" => run_decode_auto(args)
"encode-v1" => run_encode_v1(args)
"encode-v2" => run_encode_v2(args)
"inspect-tlv" => run_inspect_tlv(args)
"verify-crc32c" => run_verify_crc(args)
"split-test" => run_split_test(args)
"vector-check" =>
match @proxyproto.verify_generated_vectors() {
Ok(count) =>
println(
"{\"vector_count\":" +
count.to_string() +
",\"status\":\"verified\"}",
)
Err(_) => fail("vector_verification_failed")
}
_ => fail("unknown_command")
}
}