github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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