gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/proc_net_unix.cc (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include "gtest/gtest.h"
    16  #include "absl/strings/numbers.h"
    17  #include "absl/strings/str_format.h"
    18  #include "absl/strings/str_join.h"
    19  #include "absl/strings/str_split.h"
    20  #include "test/syscalls/linux/unix_domain_socket_test_util.h"
    21  #include "test/util/cleanup.h"
    22  #include "test/util/file_descriptor.h"
    23  #include "test/util/fs_util.h"
    24  #include "test/util/test_util.h"
    25  
    26  namespace gvisor {
    27  namespace testing {
    28  namespace {
    29  
    30  using absl::StrCat;
    31  using absl::StreamFormat;
    32  using absl::StrFormat;
    33  
    34  constexpr char kProcNetUnixHeader[] =
    35      "Num       RefCount Protocol Flags    Type St Inode Path";
    36  
    37  // Possible values of the "st" field in a /proc/net/unix entry. Source: Linux
    38  // kernel, include/uapi/linux/net.h.
    39  enum {
    40    SS_FREE = 0,      // Not allocated
    41    SS_UNCONNECTED,   // Unconnected to any socket
    42    SS_CONNECTING,    // In process of connecting
    43    SS_CONNECTED,     // Connected to socket
    44    SS_DISCONNECTING  // In process of disconnecting
    45  };
    46  
    47  // UnixEntry represents a single entry from /proc/net/unix.
    48  struct UnixEntry {
    49    uintptr_t addr;
    50    uint64_t refs;
    51    uint64_t protocol;
    52    uint64_t flags;
    53    uint64_t type;
    54    uint64_t state;
    55    uint64_t inode;
    56    std::string path;
    57  };
    58  
    59  // Abstract socket paths can have either trailing null bytes or '@'s as padding
    60  // at the end, depending on the linux version. This function strips any such
    61  // padding.
    62  void StripAbstractPathPadding(std::string* s) {
    63    const char pad_char = s->back();
    64    if (pad_char != '\0' && pad_char != '@') {
    65      return;
    66    }
    67  
    68    const auto last_pos = s->find_last_not_of(pad_char);
    69    if (last_pos != std::string::npos) {
    70      s->resize(last_pos + 1);
    71    }
    72  }
    73  
    74  // Precondition: addr must be a unix socket address (i.e. sockaddr_un) and
    75  // addr->sun_path must be null-terminated. This is always the case if addr comes
    76  // from Linux:
    77  //
    78  // Per man unix(7):
    79  //
    80  // "When the address of a pathname socket is returned (by [getsockname(2)]), its
    81  //  length is
    82  //
    83  //     offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1
    84  //
    85  //  and sun_path contains the null-terminated pathname."
    86  std::string ExtractPath(const struct sockaddr* addr) {
    87    const char* path =
    88        reinterpret_cast<const struct sockaddr_un*>(addr)->sun_path;
    89    // Note: sockaddr_un.sun_path is an embedded character array of length
    90    // UNIX_PATH_MAX, so we can always safely dereference the first 2 bytes below.
    91    //
    92    // We also rely on the path being null-terminated.
    93    if (path[0] == 0) {
    94      std::string abstract_path = StrCat("@", &path[1]);
    95      StripAbstractPathPadding(&abstract_path);
    96      return abstract_path;
    97    }
    98    return std::string(path);
    99  }
   100  
   101  // Returns a parsed representation of /proc/net/unix entries.
   102  PosixErrorOr<std::vector<UnixEntry>> ProcNetUnixEntries() {
   103    std::string content;
   104    RETURN_IF_ERRNO(GetContents("/proc/net/unix", &content));
   105  
   106    bool skipped_header = false;
   107    std::vector<UnixEntry> entries;
   108    std::vector<std::string> lines = absl::StrSplit(content, '\n');
   109    std::cerr << "<contents of /proc/net/unix>" << std::endl;
   110    for (const std::string& line : lines) {
   111      // Emit the proc entry to the test output to provide context for the test
   112      // results.
   113      std::cerr << line << std::endl;
   114  
   115      if (!skipped_header) {
   116        EXPECT_EQ(line, kProcNetUnixHeader);
   117        skipped_header = true;
   118        continue;
   119      }
   120      if (line.empty()) {
   121        continue;
   122      }
   123  
   124      // Parse a single entry from /proc/net/unix.
   125      //
   126      // Sample file:
   127      //
   128      // clang-format off
   129      //
   130      // Num       RefCount Protocol Flags    Type St Inode Path"
   131      // ffffa130e7041c00: 00000002 00000000 00010000 0001 01 1299413685 /tmp/control_server/13293772586877554487
   132      // ffffa14f547dc400: 00000002 00000000 00010000 0001 01  3793 @remote_coredump
   133      //
   134      // clang-format on
   135      //
   136      // Note that from the second entry, the inode number can be padded using
   137      // spaces, so we need to handle it separately during parsing. See
   138      // net/unix/af_unix.c:unix_seq_show() for how these entries are produced. In
   139      // particular, only the inode field is padded with spaces.
   140      UnixEntry entry;
   141  
   142      // Process the first 6 fields, up to but not including "Inode".
   143      std::vector<std::string> fields =
   144          absl::StrSplit(line, absl::MaxSplits(' ', 6));
   145  
   146      if (fields.size() < 7) {
   147        return PosixError(EINVAL, StrFormat("Invalid entry: '%s'\n", line));
   148      }
   149  
   150      // AtoiBase can't handle the ':' in the "Num" field, so strip it out.
   151      std::vector<std::string> addr = absl::StrSplit(fields[0], ':');
   152      ASSIGN_OR_RETURN_ERRNO(entry.addr, AtoiBase(addr[0], 16));
   153  
   154      ASSIGN_OR_RETURN_ERRNO(entry.refs, AtoiBase(fields[1], 16));
   155      ASSIGN_OR_RETURN_ERRNO(entry.protocol, AtoiBase(fields[2], 16));
   156      ASSIGN_OR_RETURN_ERRNO(entry.flags, AtoiBase(fields[3], 16));
   157      ASSIGN_OR_RETURN_ERRNO(entry.type, AtoiBase(fields[4], 16));
   158      ASSIGN_OR_RETURN_ERRNO(entry.state, AtoiBase(fields[5], 16));
   159  
   160      absl::string_view rest = absl::StripAsciiWhitespace(fields[6]);
   161      fields = absl::StrSplit(rest, absl::MaxSplits(' ', 1));
   162      if (fields.empty()) {
   163        return PosixError(
   164            EINVAL, StrFormat("Invalid entry, missing 'Inode': '%s'\n", line));
   165      }
   166      ASSIGN_OR_RETURN_ERRNO(entry.inode, AtoiBase(fields[0], 10));
   167  
   168      entry.path = "";
   169      if (fields.size() > 1) {
   170        entry.path = fields[1];
   171        StripAbstractPathPadding(&entry.path);
   172      }
   173  
   174      entries.push_back(entry);
   175    }
   176    std::cerr << "<end of /proc/net/unix>" << std::endl;
   177  
   178    return entries;
   179  }
   180  
   181  // Finds the first entry in 'entries' for which 'predicate' returns true.
   182  // Returns true on match, and sets 'match' to point to the matching entry.
   183  bool FindBy(std::vector<UnixEntry> entries, UnixEntry* match,
   184              std::function<bool(const UnixEntry&)> predicate) {
   185    for (size_t i = 0; i < entries.size(); ++i) {
   186      if (predicate(entries[i])) {
   187        *match = entries[i];
   188        return true;
   189      }
   190    }
   191    return false;
   192  }
   193  
   194  bool FindByPath(std::vector<UnixEntry> entries, UnixEntry* match,
   195                  const std::string& path) {
   196    return FindBy(entries, match,
   197                  [path](const UnixEntry& e) { return e.path == path; });
   198  }
   199  
   200  TEST(ProcNetUnix, Exists) {
   201    const std::string content =
   202        ASSERT_NO_ERRNO_AND_VALUE(GetContents("/proc/net/unix"));
   203    const std::string header_line = StrCat(kProcNetUnixHeader, "\n");
   204    // We could have abitrary sockets on the system, so just check the header.
   205    EXPECT_THAT(content, ::testing::StartsWith(header_line));
   206  }
   207  
   208  TEST(ProcNetUnix, FilesystemBindAcceptConnect) {
   209    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   210        FilesystemBoundUnixDomainSocketPair(SOCK_STREAM).Create());
   211  
   212    std::string path1 = ExtractPath(sockets->first_addr());
   213    std::string path2 = ExtractPath(sockets->second_addr());
   214    std::cerr << StreamFormat("Server socket address (path1): %s\n", path1);
   215    std::cerr << StreamFormat("Client socket address (path2): %s\n", path2);
   216  
   217    std::vector<UnixEntry> entries =
   218        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   219  
   220    // The server-side socket's path is listed in the socket entry...
   221    UnixEntry s1;
   222    EXPECT_TRUE(FindByPath(entries, &s1, path1));
   223  
   224    // ... but the client-side socket's path is not.
   225    UnixEntry s2;
   226    EXPECT_FALSE(FindByPath(entries, &s2, path2));
   227  }
   228  
   229  TEST(ProcNetUnix, AbstractBindAcceptConnect) {
   230    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   231        AbstractBoundUnixDomainSocketPair(SOCK_STREAM).Create());
   232  
   233    std::string path1 = ExtractPath(sockets->first_addr());
   234    std::string path2 = ExtractPath(sockets->second_addr());
   235    std::cerr << StreamFormat("Server socket address (path1): '%s'\n", path1);
   236    std::cerr << StreamFormat("Client socket address (path2): '%s'\n", path2);
   237  
   238    std::vector<UnixEntry> entries =
   239        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   240  
   241    // The server-side socket's path is listed in the socket entry...
   242    UnixEntry s1;
   243    EXPECT_TRUE(FindByPath(entries, &s1, path1));
   244  
   245    // ... but the client-side socket's path is not.
   246    UnixEntry s2;
   247    EXPECT_FALSE(FindByPath(entries, &s2, path2));
   248  }
   249  
   250  TEST(ProcNetUnix, SocketPair) {
   251    auto sockets =
   252        ASSERT_NO_ERRNO_AND_VALUE(UnixDomainSocketPair(SOCK_STREAM).Create());
   253  
   254    std::vector<UnixEntry> entries =
   255        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   256    EXPECT_GE(entries.size(), 2);
   257  }
   258  
   259  TEST(ProcNetUnix, StreamSocketStateUnconnectedOnBind) {
   260    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   261        AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
   262  
   263    ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
   264                     sockets->first_addr_size()),
   265                SyscallSucceeds());
   266  
   267    std::vector<UnixEntry> entries =
   268        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   269  
   270    const std::string address = ExtractPath(sockets->first_addr());
   271    UnixEntry bind_entry;
   272    ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
   273    EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
   274  }
   275  
   276  TEST(ProcNetUnix, StreamSocketStateStateUnconnectedOnListen) {
   277    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   278        AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
   279  
   280    ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
   281                     sockets->first_addr_size()),
   282                SyscallSucceeds());
   283  
   284    std::vector<UnixEntry> entries =
   285        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   286  
   287    const std::string address = ExtractPath(sockets->first_addr());
   288    UnixEntry bind_entry;
   289    ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
   290    EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
   291  
   292    ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
   293  
   294    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   295    UnixEntry listen_entry;
   296    ASSERT_TRUE(
   297        FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr())));
   298    EXPECT_EQ(listen_entry.state, SS_UNCONNECTED);
   299    // The bind and listen entries should refer to the same socket.
   300    EXPECT_EQ(listen_entry.inode, bind_entry.inode);
   301  }
   302  
   303  TEST(ProcNetUnix, StreamSocketStateStateConnectedOnAccept) {
   304    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   305        AbstractUnboundUnixDomainSocketPair(SOCK_STREAM).Create());
   306    const std::string address = ExtractPath(sockets->first_addr());
   307    ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
   308                     sockets->first_addr_size()),
   309                SyscallSucceeds());
   310    ASSERT_THAT(listen(sockets->first_fd(), 5), SyscallSucceeds());
   311    std::vector<UnixEntry> entries =
   312        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   313    UnixEntry listen_entry;
   314    ASSERT_TRUE(
   315        FindByPath(entries, &listen_entry, ExtractPath(sockets->first_addr())));
   316  
   317    ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
   318                        sockets->first_addr_size()),
   319                SyscallSucceeds());
   320  
   321    int clientfd;
   322    ASSERT_THAT(clientfd = accept(sockets->first_fd(), nullptr, nullptr),
   323                SyscallSucceeds());
   324    auto cleanup = Cleanup(
   325        [clientfd]() { ASSERT_THAT(close(clientfd), SyscallSucceeds()); });
   326  
   327    // Find the entry for the accepted socket. UDS proc entries don't have a
   328    // remote address, so we distinguish the accepted socket from the listen
   329    // socket by checking for a different inode.
   330    entries = ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   331    UnixEntry accept_entry;
   332    ASSERT_TRUE(FindBy(
   333        entries, &accept_entry, [address, listen_entry](const UnixEntry& e) {
   334          return e.path == address && e.inode != listen_entry.inode;
   335        }));
   336    EXPECT_EQ(accept_entry.state, SS_CONNECTED);
   337    // Listen entry should still be in SS_UNCONNECTED state.
   338    ASSERT_TRUE(FindBy(entries, &listen_entry,
   339                       [&sockets, listen_entry](const UnixEntry& e) {
   340                         return e.path == ExtractPath(sockets->first_addr()) &&
   341                                e.inode == listen_entry.inode;
   342                       }));
   343    EXPECT_EQ(listen_entry.state, SS_UNCONNECTED);
   344  }
   345  
   346  TEST(ProcNetUnix, DgramSocketStateDisconnectingOnBind) {
   347    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   348        AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create());
   349  
   350    ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
   351                     sockets->first_addr_size()),
   352                SyscallSucceeds());
   353  
   354    std::vector<UnixEntry> entries =
   355        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   356    const std::string address = ExtractPath(sockets->first_addr());
   357    UnixEntry bind_entry;
   358    ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
   359    EXPECT_EQ(bind_entry.state, SS_UNCONNECTED);
   360  }
   361  
   362  TEST(ProcNetUnix, DgramSocketStateConnectingOnConnect) {
   363    auto sockets = ASSERT_NO_ERRNO_AND_VALUE(
   364        AbstractUnboundUnixDomainSocketPair(SOCK_DGRAM).Create());
   365  
   366    ASSERT_THAT(bind(sockets->first_fd(), sockets->first_addr(),
   367                     sockets->first_addr_size()),
   368                SyscallSucceeds());
   369  
   370    std::vector<UnixEntry> entries =
   371        ASSERT_NO_ERRNO_AND_VALUE(ProcNetUnixEntries());
   372    const std::string address = ExtractPath(sockets->first_addr());
   373    UnixEntry bind_entry;
   374    ASSERT_TRUE(FindByPath(entries, &bind_entry, address));
   375  
   376    ASSERT_THAT(connect(sockets->second_fd(), sockets->first_addr(),
   377                        sockets->first_addr_size()),
   378                SyscallSucceeds());
   379  }
   380  
   381  }  // namespace
   382  }  // namespace testing
   383  }  // namespace gvisor