Implement /proc/net/tcp6

Fixes: #829

Signed-off-by: Jianfeng Tan <henry.tjf@antfin.com>
Signed-off-by: Jielong Zhou <jielong.zjl@antfin.com>
This commit is contained in:
Jianfeng Tan
2019-09-20 17:20:08 +00:00
parent e9af227a61
commit 329b6653ff
4 changed files with 343 additions and 46 deletions
+89 -46
View File
@@ -66,7 +66,7 @@ func (p *proc) newNetDir(ctx context.Context, k *kernel.Kernel, msrc *fs.MountSo
if s.SupportsIPv6() {
contents["if_inet6"] = seqfile.NewSeqFileInode(ctx, &ifinet6{s: s}, msrc)
contents["ipv6_route"] = newStaticProcInode(ctx, msrc, []byte(""))
contents["tcp6"] = newStaticProcInode(ctx, msrc, []byte(" sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode"))
contents["tcp6"] = seqfile.NewSeqFileInode(ctx, &netTCP6{k: k}, msrc)
contents["udp6"] = newStaticProcInode(ctx, msrc, []byte(" sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode"))
}
}
@@ -310,44 +310,51 @@ func networkToHost16(n uint16) uint16 {
return usermem.ByteOrder.Uint16(buf[:])
}
func writeInetAddr(w io.Writer, a linux.SockAddrInet) {
// linux.SockAddrInet.Port is stored in the network byte order and is
// printed like a number in host byte order. Note that all numbers in host
// byte order are printed with the most-significant byte first when
// formatted with %X. See get_tcp4_sock() and udp4_format_sock() in Linux.
port := networkToHost16(a.Port)
func writeInetAddr(w io.Writer, family int, i linux.SockAddr) {
switch family {
case linux.AF_INET:
var a linux.SockAddrInet
if i != nil {
a = *i.(*linux.SockAddrInet)
}
// linux.SockAddrInet.Addr is stored as a byte slice in big-endian order
// (i.e. most-significant byte in index 0). Linux represents this as a
// __be32 which is a typedef for an unsigned int, and is printed with
// %X. This means that for a little-endian machine, Linux prints the
// least-significant byte of the address first. To emulate this, we first
// invert the byte order for the address using usermem.ByteOrder.Uint32,
// which makes it have the equivalent encoding to a __be32 on a little
// endian machine. Note that this operation is a no-op on a big endian
// machine. Then similar to Linux, we format it with %X, which will print
// the most-significant byte of the __be32 address first, which is now
// actually the least-significant byte of the original address in
// linux.SockAddrInet.Addr on little endian machines, due to the conversion.
addr := usermem.ByteOrder.Uint32(a.Addr[:])
// linux.SockAddrInet.Port is stored in the network byte order and is
// printed like a number in host byte order. Note that all numbers in host
// byte order are printed with the most-significant byte first when
// formatted with %X. See get_tcp4_sock() and udp4_format_sock() in Linux.
port := networkToHost16(a.Port)
fmt.Fprintf(w, "%08X:%04X ", addr, port)
// linux.SockAddrInet.Addr is stored as a byte slice in big-endian order
// (i.e. most-significant byte in index 0). Linux represents this as a
// __be32 which is a typedef for an unsigned int, and is printed with
// %X. This means that for a little-endian machine, Linux prints the
// least-significant byte of the address first. To emulate this, we first
// invert the byte order for the address using usermem.ByteOrder.Uint32,
// which makes it have the equivalent encoding to a __be32 on a little
// endian machine. Note that this operation is a no-op on a big endian
// machine. Then similar to Linux, we format it with %X, which will print
// the most-significant byte of the __be32 address first, which is now
// actually the least-significant byte of the original address in
// linux.SockAddrInet.Addr on little endian machines, due to the conversion.
addr := usermem.ByteOrder.Uint32(a.Addr[:])
fmt.Fprintf(w, "%08X:%04X ", addr, port)
case linux.AF_INET6:
var a linux.SockAddrInet6
if i != nil {
a = *i.(*linux.SockAddrInet6)
}
port := networkToHost16(a.Port)
addr0 := usermem.ByteOrder.Uint32(a.Addr[0:4])
addr1 := usermem.ByteOrder.Uint32(a.Addr[4:8])
addr2 := usermem.ByteOrder.Uint32(a.Addr[8:12])
addr3 := usermem.ByteOrder.Uint32(a.Addr[12:16])
fmt.Fprintf(w, "%08X%08X%08X%08X:%04X ", addr0, addr1, addr2, addr3, port)
}
}
// netTCP implements seqfile.SeqSource for /proc/net/tcp.
//
// +stateify savable
type netTCP struct {
k *kernel.Kernel
}
// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate.
func (*netTCP) NeedsUpdate(generation int64) bool {
return true
}
// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData.
func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
func commonReadSeqFileDataTCP(n seqfile.SeqHandle, k *kernel.Kernel, ctx context.Context, h seqfile.SeqHandle, fa int, header []byte) ([]seqfile.SeqData, int64) {
// t may be nil here if our caller is not part of a task goroutine. This can
// happen for example if we're here for "sentryctl cat". When t is nil,
// degrade gracefully and retrieve what we can.
@@ -358,7 +365,7 @@ func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
}
var buf bytes.Buffer
for _, se := range n.k.ListSockets() {
for _, se := range k.ListSockets() {
s := se.Sock.Get()
if s == nil {
log.Debugf("Couldn't resolve weakref with ID %v in socket table, racing with destruction?", se.ID)
@@ -369,7 +376,7 @@ func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
if !ok {
panic(fmt.Sprintf("Found non-socket file in socket table: %+v", sfile))
}
if family, stype, _ := sops.Type(); !(family == linux.AF_INET && stype == linux.SOCK_STREAM) {
if family, stype, _ := sops.Type(); !(family == fa && stype == linux.SOCK_STREAM) {
s.DecRef()
// Not tcp4 sockets.
continue
@@ -384,22 +391,22 @@ func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
fmt.Fprintf(&buf, "%4d: ", se.ID)
// Field: local_adddress.
var localAddr linux.SockAddrInet
var localAddr linux.SockAddr
if t != nil {
if local, _, err := sops.GetSockName(t); err == nil {
localAddr = *local.(*linux.SockAddrInet)
localAddr = local
}
}
writeInetAddr(&buf, localAddr)
writeInetAddr(&buf, fa, localAddr)
// Field: rem_address.
var remoteAddr linux.SockAddrInet
var remoteAddr linux.SockAddr
if t != nil {
if remote, _, err := sops.GetPeerName(t); err == nil {
remoteAddr = *remote.(*linux.SockAddrInet)
remoteAddr = remote
}
}
writeInetAddr(&buf, remoteAddr)
writeInetAddr(&buf, fa, remoteAddr)
// Field: state; socket state.
fmt.Fprintf(&buf, "%02X ", sops.State())
@@ -465,7 +472,7 @@ func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
data := []seqfile.SeqData{
{
Buf: []byte(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode \n"),
Buf: header,
Handle: n,
},
{
@@ -476,6 +483,42 @@ func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
return data, 0
}
// netTCP implements seqfile.SeqSource for /proc/net/tcp.
//
// +stateify savable
type netTCP struct {
k *kernel.Kernel
}
// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate.
func (*netTCP) NeedsUpdate(generation int64) bool {
return true
}
// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData.
func (n *netTCP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
header := []byte(" sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode \n")
return commonReadSeqFileDataTCP(n, n.k, ctx, h, linux.AF_INET, header)
}
// netTCP6 implements seqfile.SeqSource for /proc/net/tcp6.
//
// +stateify savable
type netTCP6 struct {
k *kernel.Kernel
}
// NeedsUpdate implements seqfile.SeqSource.NeedsUpdate.
func (*netTCP6) NeedsUpdate(generation int64) bool {
return true
}
// ReadSeqFileData implements seqfile.SeqSource.ReadSeqFileData.
func (n *netTCP6) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]seqfile.SeqData, int64) {
header := []byte(" sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode\n")
return commonReadSeqFileDataTCP(n, n.k, ctx, h, linux.AF_INET6, header)
}
// netUDP implements seqfile.SeqSource for /proc/net/udp.
//
// +stateify savable
@@ -529,7 +572,7 @@ func (n *netUDP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
localAddr = *local.(*linux.SockAddrInet)
}
}
writeInetAddr(&buf, localAddr)
writeInetAddr(&buf, linux.AF_INET, &localAddr)
// Field: rem_address.
var remoteAddr linux.SockAddrInet
@@ -538,7 +581,7 @@ func (n *netUDP) ReadSeqFileData(ctx context.Context, h seqfile.SeqHandle) ([]se
remoteAddr = *remote.(*linux.SockAddrInet)
}
}
writeInetAddr(&buf, remoteAddr)
writeInetAddr(&buf, linux.AF_INET, &remoteAddr)
// Field: state; socket state.
fmt.Fprintf(&buf, "%02X ", sops.State())
@@ -60,6 +60,14 @@ SocketPairKind IPv6TCPAcceptBindSocketPair(int type) {
/* dual_stack = */ false)};
}
SocketKind IPv6TCPUnboundSocket(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "IPv6 TCP socket");
return SocketKind{
description, AF_INET6, type | SOCK_STREAM, IPPROTO_TCP,
UnboundSocketCreator(AF_INET6, type | SOCK_STREAM, IPPROTO_TCP)};
}
SocketPairKind IPv4TCPAcceptBindSocketPair(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "connected IPv4 TCP socket");
@@ -96,6 +96,10 @@ SocketKind IPv4UDPUnboundSocket(int type);
// a SimpleSocket created with AF_INET, SOCK_STREAM and the given type.
SocketKind IPv4TCPUnboundSocket(int type);
// IPv6TCPUnboundSocketPair returns a SocketKind that represents
// a SimpleSocket created with AF_INET6, SOCK_STREAM and the given type.
SocketKind IPv6TCPUnboundSocket(int type);
// IfAddrHelper is a helper class that determines the local interfaces present
// and provides functions to obtain their names, index numbers, and IP address.
class IfAddrHelper {
+242
View File
@@ -250,6 +250,248 @@ TEST(ProcNetTCP, State) {
EXPECT_EQ(accepted_entry.state, TCP_ESTABLISHED);
}
constexpr char kProcNetTCP6Header[] =
" sl local_address remote_address"
" st tx_queue rx_queue tr tm->when retrnsmt"
" uid timeout inode";
// TCP6Entry represents a single entry from /proc/net/tcp6.
struct TCP6Entry {
struct in6_addr local_addr;
uint16_t local_port;
struct in6_addr remote_addr;
uint16_t remote_port;
uint64_t state;
uint64_t uid;
uint64_t inode;
};
bool IPv6AddrEqual(const struct in6_addr* a1, const struct in6_addr* a2) {
if (memcmp(a1, a2, sizeof(struct in6_addr)) == 0)
return true;
return false;
}
// Finds the first entry in 'entries' for which 'predicate' returns true.
// Returns true on match, and sets 'match' to a copy of the matching entry. If
// 'match' is null, it's ignored.
bool FindBy6(const std::vector<TCP6Entry>& entries, TCP6Entry* match,
std::function<bool(const TCP6Entry&)> predicate) {
for (const TCP6Entry& entry : entries) {
if (predicate(entry)) {
if (match != nullptr) {
*match = entry;
}
return true;
}
}
return false;
}
const struct in6_addr* IP6FromInetSockaddr(const struct sockaddr* addr) {
auto* addr6 = reinterpret_cast<const struct sockaddr_in6*>(addr);
return &addr6->sin6_addr;
}
bool FindByLocalAddr6(const std::vector<TCP6Entry>& entries, TCP6Entry* match,
const struct sockaddr* addr) {
const struct in6_addr* local = IP6FromInetSockaddr(addr);
uint16_t port = PortFromInetSockaddr(addr);
return FindBy6(entries, match, [local, port](const TCP6Entry& e) {
return (IPv6AddrEqual(&e.local_addr, local) && e.local_port == port);
});
}
bool FindByRemoteAddr6(const std::vector<TCP6Entry>& entries, TCP6Entry* match,
const struct sockaddr* addr) {
const struct in6_addr* remote = IP6FromInetSockaddr(addr);
uint16_t port = PortFromInetSockaddr(addr);
return FindBy6(entries, match, [remote, port](const TCP6Entry& e) {
return (IPv6AddrEqual(&e.remote_addr, remote) && e.remote_port == port);
});
}
void ReadIPv6Address(std::string s, struct in6_addr* addr) {
uint32_t a0, a1, a2, a3;
const char *fmt = "%08X%08X%08X%08X";
EXPECT_EQ(sscanf(s.c_str(), fmt, &a0, &a1, &a2, &a3), 4);
uint8_t *b = addr->s6_addr;
*((uint32_t *)&b[0]) = a0;
*((uint32_t *)&b[4]) = a1;
*((uint32_t *)&b[8]) = a2;
*((uint32_t *)&b[12]) = a3;
}
// Returns a parsed representation of /proc/net/tcp6 entries.
PosixErrorOr<std::vector<TCP6Entry>> ProcNetTCP6Entries() {
std::string content;
RETURN_IF_ERRNO(GetContents("/proc/net/tcp6", &content));
bool found_header = false;
std::vector<TCP6Entry> entries;
std::vector<std::string> lines = StrSplit(content, '\n');
std::cerr << "<contents of /proc/net/tcp6>" << std::endl;
for (const std::string& line : lines) {
std::cerr << line << std::endl;
if (!found_header) {
EXPECT_EQ(line, kProcNetTCP6Header);
found_header = true;
continue;
}
if (line.empty()) {
continue;
}
// Parse a single entry from /proc/net/tcp6.
//
// Example entries:
//
// clang-format off
//
// sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
// 0: 00000000000000000000000000000000:1F90 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 876340 1 ffff8803da9c9380 100 0 0 10 0
// 1: 00000000000000000000000000000000:C350 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 0 0 876987 1 ffff8803ec408000 100 0 0 10 0
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
//
// clang-format on
TCP6Entry entry;
std::vector<std::string> fields =
StrSplit(line, absl::ByAnyChar(": "), absl::SkipEmpty());
ReadIPv6Address(fields[1], &entry.local_addr);
ASSIGN_OR_RETURN_ERRNO(entry.local_port, AtoiBase(fields[2], 16));
ReadIPv6Address(fields[3], &entry.remote_addr);
ASSIGN_OR_RETURN_ERRNO(entry.remote_port, AtoiBase(fields[4], 16));
ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16));
ASSIGN_OR_RETURN_ERRNO(entry.uid, Atoi<uint64_t>(fields[11]));
ASSIGN_OR_RETURN_ERRNO(entry.inode, Atoi<uint64_t>(fields[13]));
entries.push_back(entry);
}
std::cerr << "<end of /proc/net/tcp6>" << std::endl;
return entries;
}
TEST(ProcNetTCP6, Exists) {
const std::string content =
ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/tcp6"));
const std::string header_line = StrCat(kProcNetTCP6Header, "\n");
if (IsRunningOnGvisor()) {
// Should be just the header since we don't have any tcp sockets yet.
EXPECT_EQ(content, header_line);
} else {
// On a general linux machine, we could have abitrary sockets on the system,
// so just check the header.
EXPECT_THAT(content, ::testing::StartsWith(header_line));
}
}
TEST(ProcNetTCP6, EntryUID) {
auto sockets =
ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create());
std::vector<TCP6Entry> entries =
ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
TCP6Entry e;
ASSERT_TRUE(FindByLocalAddr6(entries, &e, sockets->first_addr()));
EXPECT_EQ(e.uid, geteuid());
ASSERT_TRUE(FindByRemoteAddr6(entries, &e, sockets->first_addr()));
EXPECT_EQ(e.uid, geteuid());
}
TEST(ProcNetTCP6, BindAcceptConnect) {
auto sockets =
ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create());
std::vector<TCP6Entry> entries =
ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
// We can only make assertions about the total number of entries if we control
// the entire "machine".
if (IsRunningOnGvisor()) {
EXPECT_EQ(entries.size(), 2);
}
EXPECT_TRUE(FindByLocalAddr6(entries, nullptr, sockets->first_addr()));
EXPECT_TRUE(FindByRemoteAddr6(entries, nullptr, sockets->first_addr()));
}
TEST(ProcNetTCP6, InodeReasonable) {
auto sockets =
ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPAcceptBindSocketPair(0).Create());
std::vector<TCP6Entry> entries =
ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
TCP6Entry accepted_entry;
ASSERT_TRUE(FindByLocalAddr6(entries, &accepted_entry, sockets->first_addr()));
EXPECT_NE(accepted_entry.inode, 0);
TCP6Entry client_entry;
ASSERT_TRUE(FindByRemoteAddr6(entries, &client_entry, sockets->first_addr()));
EXPECT_NE(client_entry.inode, 0);
EXPECT_NE(accepted_entry.inode, client_entry.inode);
}
TEST(ProcNetTCP6, State) {
std::unique_ptr<FileDescriptor> server =
ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPUnboundSocket(0).Create());
auto test_addr = V6Loopback();
ASSERT_THAT(
bind(server->get(), reinterpret_cast<struct sockaddr*>(&test_addr.addr),
test_addr.addr_len),
SyscallSucceeds());
struct sockaddr_in6 addr6;
socklen_t addrlen = sizeof(struct sockaddr_in6);
auto* addr = reinterpret_cast<struct sockaddr*>(&addr6);
ASSERT_THAT(getsockname(server->get(), addr, &addrlen), SyscallSucceeds());
ASSERT_EQ(addrlen, sizeof(struct sockaddr_in6));
ASSERT_THAT(listen(server->get(), 10), SyscallSucceeds());
std::vector<TCP6Entry> entries =
ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
TCP6Entry listen_entry;
ASSERT_TRUE(FindByLocalAddr6(entries, &listen_entry, addr));
EXPECT_EQ(listen_entry.state, TCP_LISTEN);
std::unique_ptr<FileDescriptor> client =
ASSERT_NO_ERRNO_AND_VALUE(IPv6TCPUnboundSocket(0).Create());
ASSERT_THAT(RetryEINTR(connect)(client->get(), addr, addrlen),
SyscallSucceeds());
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
ASSERT_TRUE(FindByLocalAddr6(entries, &listen_entry, addr));
EXPECT_EQ(listen_entry.state, TCP_LISTEN);
TCP6Entry client_entry;
ASSERT_TRUE(FindByRemoteAddr6(entries, &client_entry, addr));
EXPECT_EQ(client_entry.state, TCP_ESTABLISHED);
FileDescriptor accepted =
ASSERT_NO_ERRNO_AND_VALUE(Accept(server->get(), nullptr, nullptr));
const struct in6_addr* local = IP6FromInetSockaddr(addr);
const uint16_t accepted_local_port = PortFromInetSockaddr(addr);
entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetTCP6Entries());
TCP6Entry accepted_entry;
ASSERT_TRUE(FindBy6(entries, &accepted_entry,
[client_entry, local,
accepted_local_port](const TCP6Entry& e) {
return IPv6AddrEqual(&e.local_addr, local) &&
e.local_port == accepted_local_port &&
IPv6AddrEqual(&e.remote_addr, &client_entry.local_addr) &&
e.remote_port == client_entry.local_port;
}));
EXPECT_EQ(accepted_entry.state, TCP_ESTABLISHED);
}
} // namespace
} // namespace testing
} // namespace gvisor