Skip to content

Commit

Permalink
netip: parse partial IPv4 (fixes golang#36822)
Browse files Browse the repository at this point in the history
  • Loading branch information
tebeka committed Nov 26, 2023
1 parent 0c7e5d3 commit ee6060b
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 17 deletions.
1 change: 0 additions & 1 deletion src/net/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,6 @@ var revAddrTests = []struct {
{"1::", "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.1.0.0.0.ip6.arpa.", ""},
{"1234:567::89a:bcde", "e.d.c.b.a.9.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
{"1234:567:fefe:bcbc:adad:9e4a:89a:bcde", "e.d.c.b.a.9.8.0.a.4.e.9.d.a.d.a.c.b.c.b.e.f.e.f.7.6.5.0.4.3.2.1.ip6.arpa.", ""},
{"1.2.3", "", "unrecognized address"},
{"1.2.3.4.5", "", "unrecognized address"},
{"1234:567:bcbca::89a:bcde", "", "unrecognized address"},
{"1234:567::bcbc:adad::89a:bcde", "", "unrecognized address"},
Expand Down
63 changes: 52 additions & 11 deletions src/net/netip/netip.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,13 @@ func ParseAddr(s string) (Addr, error) {
return Addr{}, parseAddrError{in: s, msg: "missing IPv6 address"}
}
}

// Single number IP
addr, err := parseIPv4(s)
if err == nil {
return addr, nil
}

return Addr{}, parseAddrError{in: s, msg: "unable to parse IP"}
}

Expand Down Expand Up @@ -154,19 +161,20 @@ func (err parseAddrError) Error() string {

// parseIPv4 parses s as an IPv4 address (in form "192.168.0.1").
func parseIPv4(s string) (ip Addr, err error) {
var fields [4]uint8
var val, pos int
if len(s) == 0 {
return Addr{}, parseAddrError{in: s, msg: "unable to parse IP"}
}

var fields [4]uint
var val, pos uint
var digLen int // number of digits in current octet
for i := 0; i < len(s); i++ {
if s[i] >= '0' && s[i] <= '9' {
if digLen == 1 && val == 0 {
return Addr{}, parseAddrError{in: s, msg: "IPv4 field has octet with leading zero"}
}
val = val*10 + int(s[i]) - '0'
val = val*10 + uint(s[i]) - '0'
digLen++
if val > 255 {
return Addr{}, parseAddrError{in: s, msg: "IPv4 field has value >255"}
}
} else if s[i] == '.' {
// .1.2.3
// 1.2.3.
Expand All @@ -178,19 +186,52 @@ func parseIPv4(s string) (ip Addr, err error) {
if pos == 3 {
return Addr{}, parseAddrError{in: s, msg: "IPv4 address too long"}
}
fields[pos] = uint8(val)
fields[pos] = val
pos++
val = 0
digLen = 0
} else {
return Addr{}, parseAddrError{in: s, msg: "unexpected character", at: s[i:]}
}
}
if pos < 3 {
return Addr{}, parseAddrError{in: s, msg: "IPv4 address too short"}
fields[pos] = val

switch pos {
// a.b.c.d Each of the four numeric parts specifies a byte of the address
case 3:
// NOP
// a.b.c Parts a and b specify the first two bytes of the binary address.
// Part c is interpreted as a 16-bit value that defines the rightmost two bytes of the binary address
case 2:
c := fields[2]
fields[3] = c & 0xFF
fields[2] = (c >> 8)
// a.b Part a specifies the first byte of the binary address.
// Part b is interpreted as a 24-bit value that defines the rightmost three bytes of the binary address.
case 1:
b := fields[1]
fields[3] = b & 0xFF
fields[2] = (b >> 8) & 0xFF
fields[1] = b >> 16
// a The value a is interpreted as a 32-bit value that is stored directly into the binary address
// without any byte rearrangement.
case 0:
a := fields[0]
fields[3] = a & 0xFF
fields[2] = (a >> 8) & 0xFF
fields[1] = (a >> 16) & 0xFF
fields[0] = a >> 24
}

var bFields [4]uint8
for i, f := range fields {
if f > 0xFF {
return Addr{}, parseAddrError{in: s, msg: "IPv4 field has value >255"}
}
bFields[i] = uint8(f)
}
fields[3] = uint8(val)
return AddrFrom4(fields), nil

return AddrFrom4(bFields), nil
}

// parseIPv6 parses s as an IPv6 address (in form "2001:db8::68").
Expand Down
68 changes: 63 additions & 5 deletions src/net/netip/netip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func TestParseAddr(t *testing.T) {
"bad",
// Single number. Some parsers accept this as an IPv4 address in
// big-endian uint32 form, but we don't.
"1234",
// "1234",
// IPv4 with a zone specifier
"1.2.3.4%eth0",
// IPv4 field must have at least one digit
Expand All @@ -232,15 +232,12 @@ func TestParseAddr(t *testing.T) {
// IPv4 in dotted hex form
"0xc0.0xa8.0x8c.0xff",
// IPv4 in class B form
"192.168.12345",
// "192.168.12345",
// IPv4 in class B form, with a small enough number to be
// parseable as a regular dotted decimal field.
"127.0.1",
// IPv4 in class A form
"192.1234567",
// IPv4 in class A form, with a small enough number to be
// parseable as a regular dotted decimal field.
"127.1",
// IPv4 field has value >255
"192.168.300.1",
// IPv4 with too many fields
Expand Down Expand Up @@ -302,6 +299,67 @@ func TestParseAddr(t *testing.T) {
}
}

type shortIPCase struct {
in string
out string
}

func genShortIP(count, a, b, c, d int) shortIPCase {
test := shortIPCase{
out: fmt.Sprintf("%d.%d.%d.%d", a, b, c, d),
}

switch count {
case 3:
test.in = fmt.Sprintf("%d.%d.%d", a, b, (c<<8)|d)
case 2:
test.in = fmt.Sprintf("%d.%d", a, (b<<16)|(c<<8)|d)
case 1:
v := (a << 24) | (b << 16) | (c << 8) | d
test.in = fmt.Sprintf("%d", v)
}

return test
}

func TestParseIPV4Short(t *testing.T) {

var shortIPCases = []shortIPCase{
genShortIP(3, 10, 20, 30, 40),
genShortIP(2, 10, 20, 30, 40),
genShortIP(1, 10, 20, 30, 40),
}

for _, test := range shortIPCases {
t.Run(test.in, func(t *testing.T) {
ip, err := ParseAddr(test.in)
if err != nil {
t.Errorf("unexpected error: %s", err)
return
}

if got := ip.String(); got != test.out {
t.Errorf("ParseAddr(%q): got %s, expected %s", test.in, got, test.out)
}
})
}

var badIPCases = []string{
"10.20.65536",
"10.16777216",
"4294967296",
}

for _, s := range badIPCases {
t.Run(s, func(t *testing.T) {
ip, err := ParseAddr(s)
if err == nil {
t.Errorf("ParseAddr(%q): expected error, got %v", s, ip)
}
})
}
}

func TestAddrFromSlice(t *testing.T) {
tests := []struct {
ip []byte
Expand Down

0 comments on commit ee6060b

Please sign in to comment.