Until recently, it was possible to view the profiles of every user who posted in the forums. The profile page would then have links to posts/replies and other pages so that one could "follow" the recent comments by those users.
However, it appears that it no longer is possible to view a profile of individual "DTS Engineer". What I mean is I can no longer view their profile page and as a result the recent posts/replies that a specific "DTS Engineer" makes.
It's especially a loss because posts made by such engineers are very helpful and valuable and not being able to easily follow such posts on a single page makes the forum software less useful.
Is there a way the previous feature can be brought back?
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
I see that MacOS 12.2.1 ships in its dynamic libraries cache the zlib 1.2.11 version. The zlib library has the ability to log messages (at runtime) if it was configured during compilation with the --debug/-d option[1] (which internally sets the ZLIB_DEBUG compiler flag).
How does one go about finding the config options that were used to build this system library (or any system library for that matter that's shipped in MacOS)? Is the current one shipped enabled for this option?
[1] https://github.com/madler/zlib/blob/v1.2.11/configure#L139
[2] https://github.com/madler/zlib/blob/v1.2.11/configure#L202
I have a setup where /bin/ksh constantly crashes. The generated crash logs available at ~/Library/Logs/DiagnosticReports aren't too helpful because the content there contains only hex references to the ksh stack backtrace.
I then ran "dtruss" against the crashing /bin/ksh program and even that isn't helping narrow it down because that too uses hex digits in the stack backtrace.
What would be the right way to debug this crashing ksh program? Are there debug symbols available for the /bin/ksh binary shipped in macos M1 (13.0.1 version)?
I found some documentation about using XCode to "symbolicate" the crash logs, but following those instructions too wasn't helpful because even after loading the crash report in the XCode "Device Logs" window, it still only showed hex references.
I realize that recent versions of MacOS ship with a dynamic library cache. I am looking for inputs on how to list the dependencies of one such system library that's part of this cache.
Consider the case where I have a libfoo library which when inspected through "otool -L libfoo.dylib" shows this output:
otool -L libfoo.dylib
/home/me/lib/libjli.dylib:
@rpath/libjli.dylib (compatibility version 1.0.0, current version 1.0.0)
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa (compatibility version 1.0.0, current version 23.0.0)
/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 60157.60.19)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 56.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1311.0.0)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1856.105.0)
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1856.105.0)
/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
One of the dependencies in that library is the /usr/lib/libSystem.B.dylib. Previously, I could just do otool -L /usr/lib/libSystem.B.dylib to find the dependencies of libSystem.B.dylib, but now with the dynamic library cache in picture, if I do:
otool -L /usr/lib/libSystem.B.dylib
error: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool-classic: can't open file: /usr/lib/libSystem.B.dylib (No such file or directory)
So how do I get otool to show the dependencies of this cache library. Are there any other tools instead of otool which can do this in recent versions of MacOS?
I'm on 12.3.1 of macOS
Hello,
I have been looking at some TCP related configurations on Linux and then checking their counterparts on macos. On macos, I see these 2 (among many others) from "sysctl -a" output:
net.inet.tcp.ack_strategy: 1
...
net.inet.tcp.delayed_ack: 3
What does the ack_strategy = 1 imply and what does the value 3 for delayed_ack imply? Is there some additional documentation on what values are supported here and what each one implies? I did a basic search but couldn't find any details about these configurations.
Please consider this very trivial C code, which was run on 15.3.1 of macos:
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "sys/socket.h"
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
// prints out the sockaddr_in6
void print_addr(const char *msg_prefix, struct sockaddr_in6 sa6) {
char addr_text[INET6_ADDRSTRLEN] = {0};
printf("%s%s:%d, addr family=%u\n",
msg_prefix,
inet_ntop(AF_INET6, &sa6.sin6_addr, (char *) &addr_text, INET6_ADDRSTRLEN),
sa6.sin6_port,
sa6.sin6_family);
}
// creates a datagram socket
int create_dgram_socket() {
const int fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
perror("Socket creation failed");
return -1;
}
return fd;
}
int main() {
printf("current process id:%ld parent process id: %ld\n", (long) getpid(), (long) getppid());
//
// hardcode a link-local IPv6 address of a interface which is down
// ifconfig:
// ,,,
// awdl0: flags=8822<BROADCAST,SMART,SIMPLEX,MULTICAST> mtu 1500
// options=6460<TSO4,TSO6,CHANNEL_IO,PARTIAL_CSUM,ZEROINVERT_CSUM>
// ...
// inet6 fe80::34be:50ff:fe14:ecd7%awdl0 prefixlen 64 scopeid 0x10
// nd6 options=201<PERFORMNUD,DAD>
// media: autoselect (<unknown type>)
// status: inactive
//
const char *ip6_addr_str = "fe80::34be:50ff:fe14:ecd7"; // link-local ipv6 address from above ifconfig output
// parse the string literal to in6_addr
struct in6_addr ip6_addr;
int rv = inet_pton(AF_INET6, ip6_addr_str, &ip6_addr);
if (rv != 1) {
fprintf(stderr, "failed to parse ipv6 addr %s\n", ip6_addr_str);
exit(EXIT_FAILURE);
}
// create a AF_INET6 SOCK_DGRAM socket
const int sock_fd = create_dgram_socket();
if (sock_fd < 0) {
exit(EXIT_FAILURE);
}
printf("created a socket, descriptor=%d\n", sock_fd);
// create a destination sockaddr which points to the above
// ipv6 link-local address and an arbitrary port
const int dest_port = 12345;
struct sockaddr_in6 dest_sock_addr;
memset((char *) &dest_sock_addr, 0, sizeof(struct sockaddr_in6));
dest_sock_addr.sin6_addr = ip6_addr;
dest_sock_addr.sin6_port = htons(dest_port);
dest_sock_addr.sin6_family = AF_INET6;
dest_sock_addr.sin6_scope_id = 0x10; // scopeid from the above ifconfig output
// now sendto() to that address, whose network interface is down.
// we expect sendto() to return an error
print_addr("sendto() to ", dest_sock_addr);
const char *msg = "hello";
const size_t msg_len = strlen(msg) + 1;
rv = sendto(sock_fd, msg, msg_len, 0, (struct sockaddr *) &dest_sock_addr, sizeof(dest_sock_addr));
if (rv == -1) {
perror("sendto() expectedly failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
printf("sendto() unexpectedly succeeded\n"); // should not reach here, we expect sendto() to return an error
return 0;
}
It creates a SOCK_DGRAM socket and attempts to sendto() to a link-local IPv6 address of a local network interface which is not UP. The sendto() is expected to fail with a "network is down" (or at least fail with some error). Let's see how it behaves.
Copy that code to a file called netdown.c and compile it as follows:
clang netdown.c
Now run the program:
./a.out
That results in the following output:
current process id:29290 parent process id: 21614
created a socket, descriptor=3
sendto() to fe80::34be:50ff:fe14:ecd7:14640, addr family=30
sendto() unexpectedly succeeded
(To reproduce this locally, replace the IPv6 address in that code with a link-local IPv6 address of an interface that is not UP on your system)
Notice how the sendto() returned successfully without any error giving an impression to the application code that the message has been sent. In reality, the message isn't really sent. Here's the system logs from that run:
PID Type Date & Time Process Message
debug 2025-03-13 23:36:36.830147 +0530 kernel Process (a.out) allowed via dev tool environment (/System/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal)
debug 2025-03-13 23:36:36.833054 +0530 kernel [SPI][HIDSPI]
TX: 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
RX: 20 02 00 00 00 00 38 00 10 02 00 17 00 00 2E 00
26700 error 2025-03-13 23:36:36.838607 +0530 nehelper Failed to get the signing identifier for 29290: No such process
26700 error 2025-03-13 23:36:36.838608 +0530 nehelper Failed to get the code directory hash for 29290: No such process
default 2025-03-13 23:36:36.840070 +0530 kernel cfil_dispatch_attach_event:3507 CFIL: Failed to get effective audit token for <sockID 22289651233205710 <4f3051d7ec2dce>>
26700 error 2025-03-13 23:36:36.840678 +0530 nehelper Failed to get the signing identifier for 29290: No such process
26700 error 2025-03-13 23:36:36.840679 +0530 nehelper Failed to get the code directory hash for 29290: No such process
default 2025-03-13 23:36:36.841742 +0530 kernel cfil_hash_entry_log:6082 <CFIL: Error: sosend_reinject() failed>: [29290 ] <UDP(17) out so 891be95f39bd0385 22289651233205710 22289651233205710 age 0> lport 60244 fport 12345 laddr fe80::34be:50ff:fe14:ecd7 faddr fe80::34be:50ff:fe14:ecd7 hash D7EC2DCE
default 2025-03-13 23:36:36.841756 +0530 kernel cfil_service_inject_queue:4466 CFIL: sosend() failed 50
Notice the last line where it states the sosend() (and internal impl detail of macos) failed with error code 50, which corresponds to ENETDOWN ("Network is down"). However, like I noted, this error was never propagated back to the application from the sendto() system call.
The documentation of sendto() system call states:
man sendto
...
Locally detected errors are indicated by a return value of -1.
...
RETURN VALUES
Upon successful completion, the number of bytes which were sent is returned. Otherwise, -1 is returned and the global variable errno is set to indicate the error.
So I would expect sendto() to return -1, which it isn't.
The 15.3.1 source of xnu hasn't yet been published but there is the 15.3 version here https://github.com/apple-oss-distributions/xnu/tree/xnu-11215.81.4 and looking at the corresponding function cfil_service_inject_queue, line 4466 (the one which is reported in the logs) https://github.com/apple-oss-distributions/xnu/blob/xnu-11215.81.4/bsd/net/content_filter.c#L4466, the code there logs this error and the cfil_service_inject_queue function then returns back the error. However, looking at the call sites of the call to cfil_service_inject_queue(...), there are several places within that file which don't track the return value (representing an error value) and just ignore it. Is that intentional and does that explain this issue?
Does this deserve to be reported as a bug through feedback assistant?
Is there some official documentation about the SYSTEM_VERSION_COMPAT environment variable and how it affects the version reported by tools like sw_vers and whether the presence of that environment variable affects APIs like NSOperatingSystemVersion?
I ask this in context of recent macOS 26 Beta version where NSOperatingSystemVersion from older versions of XCode (for example XCode 15.4) report the macOS version as 16.0.
Topic:
Developer Tools & Services
SubTopic:
Xcode
There appears to be some unexplained change in behaviour in the recent version of macos 15.6.1 which is causing the BSD socket sendto() syscall to no longer send the data when the source socket is bound to a IPv4-mapped IPv6 address.
I have attached a trivial native code which reproduces the issue. What this reproducer does is explained as a comment on that code's main() function:
// Creates a AF_INET6 datagram socket, marks it as dual socket (i.e. IPV6_V6ONLY = 0),
// then binds the socket to a IPv4-mapped IPv6 address (chosen on the host where this test runs).
//
// The test then uses sendto() to send some bytes. For the sake of this test, it uses the same IPv4-mapped
// IPv6 address as the destination address to sendto(). The test then waits for (a maximum of) 15 seconds to
// receive that sent message by calling recvfrom().
//
// The test passes on macos (x64 and aarch64) hosts of versions 12.x, 13.x, 14.x and 15.x upto 15.5.
// Only on macos 15.6.1 and the recent macos 26, the test fails. Specifically, the first message that is
// sent using sendto() is never sent (and thus the recvfrom()) times out. sendto() however returns 0,
// incorrectly indicating a successful send. Interesting, if you repeat sendto() a second message from the
// same bound socket to the exact same destination address, the send message is indeed correctly sent and
// received immediately by the recvfrom(). It's only the first message which goes missing (the test uses
// unique content in each message to be sure which exact message was received and it has been observed that
// only the second message is received and the first one lost).
//
// Logs collected using "sudo log collect --last 2m" (after the test program returns) shows the following log
// message, which seem relevant:
// ...
// default kernel cfil_hash_entry_log:6088 <CFIL: Error: sosend_reinject() failed>:
// [86868 a.out] <UDP(17) out so 59faaa5dbbcef55d 127846646561221313 127846646561221313 age 0>
// lport 65051 fport 65051 laddr 192.168.1.2 faddr 192.168.1.2 hash 201AAC1
// default kernel cfil_service_inject_queue:4472 CFIL: sosend() failed 22
// ...
//
As noted, this test passes without issues on various macosx version (12 through 15.5), both x64 and aarch64 but always fails against 15.6.1. I have been told that it also fails on the recently released macos 26 but I don't have access to such host to verify it myself.
The release notes don't usually contain this level of detail, so it's hard to tell if something changed intentionally or if this is a bug. Should I report this through the feedback assistant?
Attached is the source of the reproducer, run it as:
clang dgramsend.c
./a.out
On macos 15.6.1, you will see that it will fail to send (and thus receive) the message on first attempt but the second one passes:
...
created and bound a datagram dual socket to ::ffff:192.168.1.2:65055
::ffff:192.168.1.2:65055 sendto() ::ffff:192.168.1.2:65055
---- Attempt 1 ----
sending greeting "hello 1"
sendto() succeeded, sent 8 bytes
calling recvfrom()
receive timed out
---------------------
---- Attempt 2 ----
sending greeting "hello 2"
sendto() succeeded, sent 8 bytes
calling recvfrom()
received 8 bytes: "hello 2"
---------------------
TEST FAILED
...
The output "log collect --last 2m" contains a related error (and this log message consistently shows up every time you run that reproducer):
...
default kernel cfil_hash_entry_log:6088 <CFIL: Error: sosend_reinject() failed>:
[86248 a.out] <UDP(17) out so 59faaa5dbbcef55d 127846646561221313 127846646561221313 age 0>
lport 65055 fport 65055 laddr 192.168.1.2 faddr 192.168.1.2 hash 201AAC1
default kernel cfil_service_inject_queue:4472 CFIL: sosend() failed 22
...
I don't know what it means though.
dgramsend.c
In some recent releases of macos (14.x and 15.x), we have noticed what seems to be a slower dlopen() implementation. I don't have any numbers to support this theory. I happened to notice this "slowness" when investigating something unrelated. In one part of the code we have a call of the form:
const char * fooBarLib = ....;
dlopen(fooBarLib, RTLD_NOW + RTLD_GLOBAL);
It so happened that due to some timing related issues, the process was crashing. A slow execution of code in this part of the code would trigger an issue in some other part of the code that would then lead to a process crash. The crash itself isn't a concern, because it's an internal issue that will addressed in the application code. What was interesting is that the slowness appears to be contributed by the call to dlopen(). Specifically, whenever a slowness was observed, the crash reports showed stack frames of the form:
Thread 1:
0 dyld 0x18f08b5b4 _kernelrpc_mach_vm_protect_trap + 8
1 dyld 0x18f08f540 vm_protect + 52
2 dyld 0x18f0b87e0 lsl::MemoryManager::writeProtect(bool) + 204
3 dyld 0x18f0a7fe4 invocation function for block in dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 932
4 dyld 0x18f0e629c invocation function for block in dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 172
5 dyld 0x18f0d9c38 invocation function for block in dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 496
6 dyld 0x18f08c2dc dyld3::MachOFile::forEachLoadCommand(Diagnostics&, void (load_command const*, bool&) block_pointer) const + 300
7 dyld 0x18f0d8bcc dyld3::MachOFile::forEachSection(void (dyld3::MachOFile::SectionInfo const&, bool, bool&) block_pointer) const + 192
8 dyld 0x18f0db5a0 dyld3::MachOFile::forEachInitializerPointerSection(Diagnostics&, void (unsigned int, unsigned int, bool&) block_pointer) const + 160
9 dyld 0x18f0e5f90 dyld3::MachOAnalyzer::forEachInitializer(Diagnostics&, dyld3::MachOAnalyzer::VMAddrConverter const&, void (unsigned int) block_pointer, void const*) const + 432
10 dyld 0x18f0a7bb4 dyld4::Loader::findAndRunAllInitializers(dyld4::RuntimeState&) const + 176
11 dyld 0x18f0af190 dyld4::JustInTimeLoader::runInitializers(dyld4::RuntimeState&) const + 36
12 dyld 0x18f0a8270 dyld4::Loader::runInitializersBottomUp(dyld4::RuntimeState&, dyld3::Array<dyld4::Loader const*>&, dyld3::Array<dyld4::Loader const*>&) const + 312
13 dyld 0x18f0ac560 dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const::$_0::operator()() const + 180
14 dyld 0x18f0a8460 dyld4::Loader::runInitializersBottomUpPlusUpwardLinks(dyld4::RuntimeState&) const + 412
15 dyld 0x18f0c089c dyld4::APIs::dlopen_from(char const*, int, void*) + 2432
16 libjli.dylib 0x1025515b4 DoFooBar + 56
17 libjli.dylib 0x10254d2c0 Hello_World_Launch + 1160
18 helloworld 0x10250bbb4 main + 404
19 libjli.dylib 0x102552148 apple_main + 88
20 libsystem_pthread.dylib 0x18f4132e4 _pthread_start + 136
21 libsystem_pthread.dylib 0x18f40e0fc thread_start + 8
So, out of curiosity, have there been any known changes in the implementation of dlopen() which might explain the slowness?
Like I noted, I don't have concrete numbers, but to quantify the slowness I don't think it's slower by a noticeable amount - maybe a few milli seconds. I guess what I am trying to understand is, whether there's anything that needs attention here.
In context of entitlements that are applicable on macos platform, I was discussing in another thread about the com.apple.security.cs.allow-unsigned-executable-memory and the com.apple.security.cs.allow-jit entitlements in a hardened runtime https://developer.apple.com/forums/thread/775520?answerId=827440022#827440022
In that thread it was noted that:
The hardened runtime enables a bunch of additional security checks. None of them are related to networking. Some of them are very important to a Java VM author, most notably the com.apple.security.cs.allow-jit -> com.apple.security.cs.allow-unsigned-executable-memory -> com.apple.security.cs.disable-executable-page-protection cascade. My advice on that front:
This sequence is a trade off between increasing programmer convenience and decreasing security. com.apple.security.cs.allow-jit is the most secure, but requires extra work in your code.
Only set one of these entitlements, because each is a superset of its predecessor.
com.apple.security.cs.disable-executable-page-protection is rarely useful. Indeed, on Apple silicon [1] it’s the same as com.apple.security.cs.allow-unsigned-executable-memory.
If you want to investigate moving from com.apple.security.cs.allow-unsigned-executable-memory to com.apple.security.cs.allow-jit, lemme know because there are a bunch of additional resources on that topic.
What that tells me is that com.apple.security.cs.allow-jit is the recommended entitlement that retains enough security and yet provides the necessary programmer convenience for applications.
In the OpenJDK project we use both com.apple.security.cs.allow-unsigned-executable-memory and com.apple.security.cs.allow-jit entitlements for the executables shipped in the JDK (for example java). I was told in that other thread that it might be possible to just use the com.apple.security.cs.allow-unsigned-executable-memory, but there are some additional details to consider. I'm starting this thread to understand what those details are.
I was reading through this documentation about instruments command line tool https://help.apple.com/instruments/mac/current/#/devb14ffaa5 and how it can be launched from the command line. However, unlike what the documentation states, there's no such instruments command anywhere on my macos M1 (OS version 15.6). That command gives:
$> instruments
zsh: command not found: instruments
I do have XCode installed which has the Instruments.App (GUI app) but not the command line utility:
$> ls Xcode.app/Contents/Applications/
... Instruments.app
Is that linked documentation up-to-date (it does say "latest" in the URL)? Is there some other way to install this command line utility?
We have some extensive tests which exercise UDP communication. Some of these tests fail fairly often due to the UDP packet being dropped by the kernel (or related reasons). These tests use loopback interface for communication. I have been looking to see if there's a way to pinpoint or narrow down exactly why a particular packet was dropped by the kernel. Looking at the kernel code, like here https://github.com/apple-opensource/xnu/blob/master/bsd/netinet/udp_usrreq.c#L1463 it appears that there are log message that get written out during some of this communication. However, looking at what KERNEL_DEBUG stands for, it appears that it's:
/*
* Traced only on debug kernels.
*/
#define KDBG_DEBUG(x, ...) KDBG_(_DEBUG, x, ## __VA_ARGS__, 4, 3, 2, 1, 0)
So I don't think these logs get generated in a regular release build of the OS.
Are there any other ways we can generate similar logs or any other tools that will give a clearer picture of why the packet might be drop?
We have been noticing some mysterious port binds on our macos setups, where the syslogd process binds to a ephemeral port on UDP. This socket isn't bound from the time syslogd process starts, but something/ some event triggers this bind.
So we investigated further. It appears that one of the macos specific modules in syslogd is the "bsd_out" module which reads the config rules from a file called "/etc/syslog.conf". The contents of that file on my setup are:
cat /etc/syslog.conf
# Note that flat file logs are now configured in /etc/asl.conf
install.* @127.0.0.1:32376
These contents are the default ones shipped in macos and nothing has been edited/changed.
So it appears that the bsd_out module has been configured with a rule to send logs/messages in the "install" facility to be forwarded to some process which has a socket listening on loopback's 32376 port.
Whenever some software gets installed/uninstalled from the machine, it looks like a log message gets generated which falls under this "install" facility and then the bsd_out module binds a socket for UDP and uses that socket to send the data to 127.0.0.1:32376. You will notice that before installing/uninstalling any software the command:
sudo lsof -p <syslogd-pid>
will not list any UDP port. As soon as you install/uninstall something that socket gets bound and is visible in the output of the above command. The (bound) socket stays around.
The curious part is there's still no one/nothing that listens on that 32376 port. So it appears that this module is sending some datagrams that are just lost and not delivered? Is there a reason why the /etc/syslog.conf has this rule if there's nothing that's receiving that data?
The "man syslogd" page does state that bsd_out module is only there for backward compatibility, so perhaps this config rule in /etc/syslog.conf is just a left over that is no longer relevant?
I'm on macos 13.2.1:
sw_vers
ProductName: macOS
ProductVersion: 13.2.1
BuildVersion: 22D68
but this has been noticed on older version (even 10.15.x) too.
To reproduce, here are the steps:
Find the pid of syslogd (ps -aef | grep syslogd)
Find the resources used by this process including ports (sudo lsof -p <syslog-pid>)
At this point, ideally, you shouldn't see any UDP ports being used by this process
Install/uninstall any software (for example: move to trash and delete any installed application)
Run the lsof command again (sudo lsof -p <syslog-pid>), you will now see that it uses a UDP port bound to INADDR_ANY address and an ephemeral port:
syslogd 12345 root 11u IPv4 0xf557ad678c99264b 0t0 UDP *:56972
netstat output too will show that port (for example: netstat -anv -p UDP)
Please consider this trivial C code which deals with BSD sockets. This will illustrate an issue with sendto() which seems to be impacted by the recent "Local Network" restrictions on 15.3.1 macos.
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "sys/socket.h"
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
// prints out the sockaddr_in6
void print_addr(const char *msg_prefix, struct sockaddr_in6 sa6) {
char addr_text[INET6_ADDRSTRLEN] = {0};
printf("%s%s:%d, addr family=%u\n",
msg_prefix,
inet_ntop(AF_INET6, &sa6.sin6_addr, (char *) &addr_text, INET6_ADDRSTRLEN),
sa6.sin6_port,
sa6.sin6_family);
}
// creates a datagram socket
int create_dgram_socket() {
const int fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
perror("Socket creation failed");
return -1;
}
return fd;
}
// returns a string representing the current local time
char *current_time() {
time_t seconds_since_epoch;
time(&seconds_since_epoch);
char *res = ctime(&seconds_since_epoch);
const size_t len = strlen(res);
// strip off the newline character that's at the end of the ctime() output
res[len - 1] = '\0';
return res;
}
// Creates a datagram socket and then sends a messages (through sendto()) to a valid
// multicast address. This it does two times, to the exact same destination address from
// the exact same socket.
//
// Between the first and the second attempt to sendto(), there is
// a sleep of 1 second.
//
// The first time, the sendto() succeeds and claims to have sent the expected number of bytes.
// However system logs (generated through "log collect") seem to indicate that the message isn't
// actually sent (there's a "cfil_service_inject_queue:4466 CFIL: sosend() failed 65" in the logs).
//
// The second time the sendto() returns a EHOSTUNREACH ("No route to host") error.
//
// If the sleep between these two sendto() attempts is removed then both the attempts "succeed".
// However, the system logs still suggest that the message isn't actually sent.
int main() {
printf("current process id:%ld parent process id: %ld\n", (long) getpid(), (long) getppid());
// valid multicast address as specified in
// https://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml
const char *ip6_addr_str = "ff01::1";
struct in6_addr ip6_addr;
int rv = inet_pton(AF_INET6, ip6_addr_str, &ip6_addr);
if (rv != 1) {
fprintf(stderr, "failed to parse ipv6 addr %s\n", ip6_addr_str);
exit(EXIT_FAILURE);
}
// create a AF_INET6 SOCK_DGRAM socket
const int sock_fd = create_dgram_socket();
if (sock_fd < 0) {
exit(EXIT_FAILURE);
}
printf("created a socket, descriptor=%d\n", sock_fd);
const int dest_port = 12345; // arbitrary port
struct sockaddr_in6 dest_sock_addr;
memset((char *) &dest_sock_addr, 0, sizeof(struct sockaddr_in6));
dest_sock_addr.sin6_addr = ip6_addr; // the target multicast address
dest_sock_addr.sin6_port = htons(dest_port);
dest_sock_addr.sin6_family = AF_INET6;
print_addr("test will attempt to sendto() to destination host:port -> ", dest_sock_addr);
const char *msg = "hello";
const size_t msg_len = strlen(msg) + 1;
for (int i = 1; i <= 2; i++) {
if (i != 1) {
// if not the first attempt, then sleep a while before attempting to sendto() again
int num_sleep_seconds = 1;
printf("sleeping for %d second(s) before calling sendto()\n", num_sleep_seconds);
sleep(num_sleep_seconds);
}
printf("%s attempt %d to sendto() %lu bytes\n", current_time(), i, msg_len);
const size_t num_sent = sendto(sock_fd, msg, msg_len, 0, (struct sockaddr *) &dest_sock_addr,
sizeof(dest_sock_addr));
if (num_sent == -1) {
fprintf(stderr, "%s ", current_time());
perror("sendto() failed");
close(sock_fd);
exit(EXIT_FAILURE);
}
printf("%s attempt %d of sendto() succeeded, sent %lu bytes\n", current_time(), i, num_sent);
}
return 0;
}
What this program does is, it uses the sendto() system call to send a message over a datagram socket to a (valid) multicast address. It does this twice, from the same socket to the same target address. There is a sleep() of 1 second between these two sendto() attempts.
Copy that code into noroutetohost.c and compile:
clang noroutetohost.c
Then run:
./a.out
This generates the following output:
current process id:58597 parent process id: 21614
created a socket, descriptor=3
test will attempt to sendto() to destination host:port ->ff01::1:14640, addr family=30
Fri Mar 14 20:34:09 2025 attempt 1 to sendto() 6 bytes
Fri Mar 14 20:34:09 2025 attempt 1 of sendto() succeeded, sent 6 bytes
sleeping for 1 second(s) before calling sendto()
Fri Mar 14 20:34:10 2025 attempt 2 to sendto() 6 bytes
Fri Mar 14 20:34:10 2025 sendto() failed: No route to host
Notice how the first call to sendto() "succeeds", even the return value (that represents the number of bytes sent) matches the number of bytes that were supposed to be sent. Then notice how the second attempt fails with a EHOSTUNREACH ("No route to host") error. Looking through the system logs, it appears that the first attempt itself has failed:
2025-03-14 20:34:09.474797 default kernel cfil_hash_entry_log:6082 <CFIL: Error: sosend_reinject() failed>: [58597 a.out] <UDP(17) out so 891be95f3a70c605 22558774573152560 22558774573152560 age 0> lport 0 fport 12345 laddr :: faddr ff01::1 hash 1003930
2025-03-14 20:34:09.474806 default kernel cfil_service_inject_queue:4466 CFIL: sosend() failed 65
(notice the time on that log messages, they match the first attempt from the program's output log)
So even though the first attempt failed, it never got reported back to the application. Then after sleeping for (an arbitrary amount of) 1 second, the second call fails with the EHOSTUNREACH. The system logs don't show any error (at least not the one similar to that previous one) for the second call.
If I remove that sleep() between those two attempts, then both the sendto() calls "succeed" (and return the expected value for the number of bytes sent). However, the system logs show that the first call (and very likely even the second) has failed with the exact same log message from the kernel like before.
If I'm not wrong then this appears to be some kind of a bug in the "local network" restrictions. Should this be reported? I can share the captured logs but I would prefer to do it privately for this one.
Another interesting thing in all this is that there's absolutely no notification to the end user (I ran this program from the Terminal) about any of the "Local Network" restrictions.
The man page of "connect" states (snippet below):
".... datagram sockets may use connect() multiple times to change their association. Datagram sockets may dissolve the association by calling disconnectx(2), or by connecting to an invalid address, such as a null address ..."
Up until 12.3.1 (and even 12.4 Beta1), if a connect() call was used on a datagram socket to disconnect an IPv4-mapped IPv6 address, the call used to complete and return back "EADDRNOTAVAIL". Internally it would indeed dissolve the current association of the connected datagram socket (i.e. subsequent calls to "send" would return back a "EDESTADDRREQ" errno).
However, starting 12.4 Beta2, if a connect() call is made on such a datagram socket which is connected to a IPv4-mapped IPv6 address, the call now returns a "EINVAL" errno. Do note that this change in behaviour is happening in this Beta release only with IPv4-mapped IPv6 addresses. Other addresses like plain IPv6 address, continue to return "EADDRNOTAVAIL" on such connect() calls which are used to dissolve the current association.
We have been able to reproduce this issue with a trivial C program that is attached to this discussion. The attached program, creates a datagram socket which then connects to a IPv4-mapped IPv6 loopback address. This connect is expected to succeed and does succeed. Once the connect is done, a next connect() is issued on the connected datagram socket, this time passing it a null address (as noted in the man page) and this call is expected to dissolve the connection association. The program expects a EADDRNOTAVAIL to be returned by this call (since that's what was being returned in all MacOS releases so far) and if some other errno gets returned then the program fails.
When the attached reproducer is run against 12.3.1 (or other previous MacOS versions or even 12.4 Beta1), the program gets the expected EADDRNOTAVAIL and when it is run against 12.4 Beta2, it gets the unexpected EINVAL errno.
Here's a sample output from 12.3.1 (the "successful" one):
$> sw_vers
ProductName: macOS
ProductVersion: 12.3.1
BuildVersion: 21E258
$> clang main.c
$> ./a.out
created a socket, descriptor=3
trying to bind to addr ::ffff:127.0.0.1:0, addr family=30
Bound socket to ::ffff:127.0.0.1:46787, addr family=30
created another socket, descriptor=4
connecting to ::ffff:127.0.0.1:46787, addr family=30
successfully connected
Disconnecting using addr :::0, addr family=30
got errno 49, which is considered OK for a connect to null address (a.k.a disconnect)
Successfully disconnected
Output from 12.4 Beta2 (the case where the behaviour has changed):
$> sw_vers
ProductName: macOS
ProductVersion: 12.4
BuildVersion: 21F5058e
$> clang main.c
$> ./a.out
created a socket, descriptor=3
trying to bind to addr ::ffff:127.0.0.1:0, addr family=30
Bound socket to ::ffff:127.0.0.1:23015, addr family=30
created another socket, descriptor=4
connecting to ::ffff:127.0.0.1:23015, addr family=30
successfully connected
Disconnecting using addr :::0, addr family=30
Got unexpected errno 22
disconnect failed: Invalid argument
Notice how the call to connect() to dissolve the connected association now returns errno 22 == Invalid Argument.
We had a look at the release notes here https://developer.apple.com/documentation/macos-release-notes/macos-12_4-release-notes but they appear to be very high level release notes and we couldn't find anything relevant related to this change. So:
Is this an intentional change? Are applications expected to account for this errno to be returned when calling connect() to dissolve a connected association of a datagram socket? If this is intentional, is there a reason why this is specific only for IPv4-mapped IPv6 address?
The man connect page also talks about using disconnectx as an alternative way to dissolve the association. This appears to be specific to MacOS systems (couldn't find it in BSD man pages for example). Is disconnectx the recommended way to do the dissociation?
P.S: I created a issue for this through the bug/feedback reporting tool (issue id: FB9996296), but there hasn't been any response to it since almost a week. So creating this thread in the forums hoping to get some inputs.
main.c