Problem / Background
An IPv6 address literal cannot be passed to -H/--hosts in any form. Verified against main at commit 56abe143 with a debug build:
-H value |
Result |
[::1] |
Error: Failed to expand host expression: [::1] / unmatched closing bracket in '[::1]' |
[::1]:22 |
Error: Failed to expand host expression: [::1]:22 |
user@[::1] |
Error: Failed to expand host expression: user@[::1] |
user@[::1]:22 |
Error: Failed to expand host expression: user@[::1]:22 |
::1 (unbracketed) |
Reaches the connect path but fails resolution: Invalid address was provided: failed to lookup address information: nodename nor servname provided, or not known |
localhost, ip6-localhost (hostnames) |
Work correctly, resolution succeeds |
The bracketed forms die in the hostlist expander before any connection is attempted. The unbracketed form dies in to_socket_addrs() because a bare ::1 cannot be parsed as host:port. Hostnames that resolve to IPv6 are unaffected and work fine.
Root cause
The hostlist expander uses [ and ] as the delimiters for its range expression syntax (node[1-3]), so a bracketed IPv6 literal collides with that syntax.
src/hostlist/parser.rs already has partial IPv6 awareness, but it is incomplete. In parse_host_pattern, the '[' arm detects a possible IPv6 literal via is_ipv6_start and takes a fast path that pushes [ into current_literal and continues, but it never sets bracket_depth. When the matching ] is reached, the ']' arm sees bracket_depth == 0 and returns HostlistError::UnmatchedBracket, which is exactly the observed unmatched closing bracket in '[::1]'. The closing ] is also never appended to the literal, so even if the depth check were bypassed the reassembled host string would be malformed.
A second gap: is_ipv6_start returns true only when the character right after [ is :. A literal such as [2001:db8::1] starts with 2, so it is routed into parse_range_expression and fails with a different, equally unhelpful error.
Also investigate whether the config-file host path (clusters: entries in YAML, expanded in src/app/nodes.rs) accepts IPv6 literals even though -H does not, since the two may take different code paths.
Why this matters now
PR #247 (issue #246) made -4/-6 functional. Before that they parsed and did nothing, so there was no reason to reach for an IPv6 literal on the command line. Now bssh -6 -H '[::1]' ping is a natural thing to type, and it fails with unmatched closing bracket, which gives no hint that the bracket syntax is the problem or what the working alternative is.
This is pre-existing and not a regression from #247. Confirmed: neither merge commit b79ab196 (#247) nor 56abe143 (#250) touched anything under src/hostlist/.
Proposed Solution
Make the hostlist parser recognize a bracketed IPv6 literal as a single literal segment rather than a range expression, carry the closing bracket into the literal, and keep the resulting host string in a form the connect path can hand to address resolution with an explicit port. Extend is_ipv6_start past the leading-colon heuristic so it also accepts literals beginning with a hex digit, and define an unambiguous rule for separating an IPv6 literal from a range expression.
Acceptance Criteria
Technical Considerations
Relevant code:
src/hostlist/parser.rs: parse_host_pattern, is_ipv6_start, parse_range_expression
src/hostlist/error.rs: UnmatchedBracket, UnclosedBracket, NestedBrackets, EmptyBracket
src/main.rs and src/app/nodes.rs: the two call sites that wrap expander failures in Failed to expand host expression
The user@host:port split has to run against the bracketed form without treating the colons inside the brackets as the port separator. Only a colon after the closing ] is a port separator.
Context: #246, #247.
Problem / Background
An IPv6 address literal cannot be passed to
-H/--hostsin any form. Verified againstmainat commit56abe143with a debug build:-Hvalue[::1]Error: Failed to expand host expression: [::1]/unmatched closing bracket in '[::1]'[::1]:22Error: Failed to expand host expression: [::1]:22user@[::1]Error: Failed to expand host expression: user@[::1]user@[::1]:22Error: Failed to expand host expression: user@[::1]:22::1(unbracketed)Invalid address was provided: failed to lookup address information: nodename nor servname provided, or not knownlocalhost,ip6-localhost(hostnames)The bracketed forms die in the hostlist expander before any connection is attempted. The unbracketed form dies in
to_socket_addrs()because a bare::1cannot be parsed ashost:port. Hostnames that resolve to IPv6 are unaffected and work fine.Root cause
The hostlist expander uses
[and]as the delimiters for its range expression syntax (node[1-3]), so a bracketed IPv6 literal collides with that syntax.src/hostlist/parser.rsalready has partial IPv6 awareness, but it is incomplete. Inparse_host_pattern, the'['arm detects a possible IPv6 literal viais_ipv6_startand takes a fast path that pushes[intocurrent_literaland continues, but it never setsbracket_depth. When the matching]is reached, the']'arm seesbracket_depth == 0and returnsHostlistError::UnmatchedBracket, which is exactly the observedunmatched closing bracket in '[::1]'. The closing]is also never appended to the literal, so even if the depth check were bypassed the reassembled host string would be malformed.A second gap:
is_ipv6_startreturns true only when the character right after[is:. A literal such as[2001:db8::1]starts with2, so it is routed intoparse_range_expressionand fails with a different, equally unhelpful error.Also investigate whether the config-file host path (
clusters:entries in YAML, expanded insrc/app/nodes.rs) accepts IPv6 literals even though-Hdoes not, since the two may take different code paths.Why this matters now
PR #247 (issue #246) made
-4/-6functional. Before that they parsed and did nothing, so there was no reason to reach for an IPv6 literal on the command line. Nowbssh -6 -H '[::1]' pingis a natural thing to type, and it fails withunmatched closing bracket, which gives no hint that the bracket syntax is the problem or what the working alternative is.This is pre-existing and not a regression from #247. Confirmed: neither merge commit
b79ab196(#247) nor56abe143(#250) touched anything undersrc/hostlist/.Proposed Solution
Make the hostlist parser recognize a bracketed IPv6 literal as a single literal segment rather than a range expression, carry the closing bracket into the literal, and keep the resulting host string in a form the connect path can hand to address resolution with an explicit port. Extend
is_ipv6_startpast the leading-colon heuristic so it also accepts literals beginning with a hex digit, and define an unambiguous rule for separating an IPv6 literal from a range expression.Acceptance Criteria
-Haccepts IPv6 literals in the standard bracketed forms:[::1],[::1]:2222,user@[::1],user@[::1]:2222.node[1-3]hostlist range syntax, with the disambiguation rule stated in the code and in the docs. Decide and document what happens for genuinely ambiguous input.::1either works or fails with a message that names the bracket requirement, instead of the current generic resolution error.node[1-3]range expansion keeps working unchanged, covered by a regression test.docs/man/bssh.1documents the accepted IPv6 literal syntax for-H.-Hand the config host path use, not added as a standalone helper.Technical Considerations
Relevant code:
src/hostlist/parser.rs:parse_host_pattern,is_ipv6_start,parse_range_expressionsrc/hostlist/error.rs:UnmatchedBracket,UnclosedBracket,NestedBrackets,EmptyBracketsrc/main.rsandsrc/app/nodes.rs: the two call sites that wrap expander failures inFailed to expand host expressionThe
user@host:portsplit has to run against the bracketed form without treating the colons inside the brackets as the port separator. Only a colon after the closing]is a port separator.Context: #246, #247.