Skip to content

Commit ec50c5e

Browse files
committed
pkg/portutil/procnet: support big-endian hosts in ParseAddress
The /proc/net/{tcp,tcp6} address bytes are written in the host's native byte order. ParseAddress unconditionally reversed each 4-byte group, which is correct on little-endian hosts but mangles the address on big-endian hosts such as s390x (e.g. 7F000001 parsed to 1.0.0.127 instead of 127.0.0.1). Add ParseAddressWithByteOrder, reading each group with the given binary.ByteOrder and writing it back in network order; ParseAddress passes binary.NativeEndian. Little-endian behavior is unchanged. Fixes #4465 Signed-off-by: Saleh <root@lr0.org>
1 parent 5638af9 commit ec50c5e

2 files changed

Lines changed: 37 additions & 8 deletions

File tree

pkg/portutil/procnet/procnet.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package procnet
1818

1919
import (
20+
"encoding/binary"
2021
"encoding/hex"
2122
"fmt"
2223
"net"
@@ -72,12 +73,20 @@ func removeEmpty(array []string) (results []string) {
7273
//
7374
// See https://serverfault.com/questions/592574/why-does-proc-net-tcp6-represents-1-as-1000
7475
//
75-
// ParseAddress is expected to be used for /proc/net/{tcp,tcp6} entries on
76-
// little endian machines.
77-
// Not sure how those entries look like on big endian machines.
78-
// All the code below is copied from the lima project in https://github.com/lima-vm/lima/blob/v0.8.3/pkg/guestagent/procnettcp/procnettcp.go#L95-L137
76+
// ParseAddress parses the entry using the current host's native byte order.
77+
// Use ParseAddressWithByteOrder to parse an entry whose byte order is known.
78+
// The parsing logic is derived from the lima project in https://github.com/lima-vm/lima/blob/v0.8.3/pkg/guestagent/procnettcp/procnettcp.go#L95-L137
7979
// and is licensed under the Apache License, Version 2.0
8080
func ParseAddress(s string) (net.IP, uint16, error) {
81+
return ParseAddressWithByteOrder(s, binary.NativeEndian)
82+
}
83+
84+
// ParseAddressWithByteOrder is like ParseAddress but takes the byte order of
85+
// the /proc data explicitly. The kernel writes each 4-byte group of the address
86+
// in the host's native byte order (little-endian on x86/arm64, big-endian on
87+
// s390x), so each group is read with that order and rewritten in network order
88+
// to build the net.IP.
89+
func ParseAddressWithByteOrder(s string, order binary.ByteOrder) (net.IP, uint16, error) {
8190
split := strings.SplitN(s, ":", 2)
8291
if len(split) != 2 {
8392
return nil, 0, fmt.Errorf("unparsable address %q", s)
@@ -92,13 +101,11 @@ func ParseAddress(s string) (net.IP, uint16, error) {
92101
ipBytes := make([]byte, len(split[0])/2) // 4 bytes (8 chars) or 16 bytes (32 chars)
93102
for i := 0; i < len(split[0])/8; i++ {
94103
quartet := split[0][8*i : 8*(i+1)]
95-
quartetLE, err := hex.DecodeString(quartet) // surprisingly little endian, per 4 bytes
104+
quartetBytes, err := hex.DecodeString(quartet)
96105
if err != nil {
97106
return nil, 0, fmt.Errorf("unparsable address %q: unparsable quartet %q: %w", s, quartet, err)
98107
}
99-
for j := 0; j < len(quartetLE); j++ {
100-
ipBytes[4*i+len(quartetLE)-1-j] = quartetLE[j]
101-
}
108+
binary.BigEndian.PutUint32(ipBytes[4*i:], order.Uint32(quartetBytes))
102109
}
103110
ip := net.IP(ipBytes)
104111

pkg/portutil/procnet/procnetd_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package procnet
1818

1919
import (
20+
"encoding/binary"
2021
"net"
2122
"testing"
2223

@@ -67,3 +68,24 @@ func TestParseTCP6Zero(t *testing.T) {
6768
assert.Check(t, net.IPv6zero.Equal(entries[0].LocalIP))
6869
assert.Equal(t, uint64(22), entries[0].LocalPort)
6970
}
71+
72+
func TestParseAddressWithByteOrder(t *testing.T) {
73+
// Big-endian hosts (e.g. s390x) keep each 4-byte group as written.
74+
ip, port, err := ParseAddressWithByteOrder("7F000001:0050", binary.BigEndian)
75+
assert.NilError(t, err)
76+
assert.Check(t, net.ParseIP("127.0.0.1").Equal(ip))
77+
assert.Equal(t, uint16(80), port)
78+
79+
ip, _, err = ParseAddressWithByteOrder("FE8000000000000070A657FFFE71C75D:0050", binary.BigEndian)
80+
assert.NilError(t, err)
81+
assert.Check(t, net.ParseIP("fe80::70a6:57ff:fe71:c75d").Equal(ip))
82+
83+
// Little-endian hosts (x86, arm64) reverse each 4-byte group.
84+
ip, _, err = ParseAddressWithByteOrder("0100007F:0050", binary.LittleEndian)
85+
assert.NilError(t, err)
86+
assert.Check(t, net.ParseIP("127.0.0.1").Equal(ip))
87+
88+
ip, _, err = ParseAddressWithByteOrder("000080FE00000000FF57A6705DC771FE:0050", binary.LittleEndian)
89+
assert.NilError(t, err)
90+
assert.Check(t, net.ParseIP("fe80::70a6:57ff:fe71:c75d").Equal(ip))
91+
}

0 commit comments

Comments
 (0)