en0网卡多次添加IPv6地址后出现「无法分配内存」错误
macOS单网卡IPv6地址数量限制问题
问题背景
在为en0网卡分配**Unique Local Address (ULA)**类型IPv6地址时,遇到以下问题:
- 初始可正常添加多个地址,但添加第18个时触发
Cannot allocate memory错误 - 手动执行命令:
返回错误:sudo ifconfig en0 inet6 fc00:45bd::81aa:15c6 prefixlen 64 aliasifconfig: ioctl (SIOCAIFADDR): Cannot allocate memory - 移除部分已分配地址后,可重新添加至第17个,第18个再次报错
- 怀疑Duplicate Address Detection (DAD)导致,将所有DAD相关sysctl参数设为0:
问题依旧,且报错时地址已出现在网卡配置中net.inet6.ip6.dad_count: 0 net.inet6.ip6.dad_enhanced: 0 net.inet6.ip6.nd6_dad_nonce_max_count: 0 net.inet6.icmp6.nd6_optimistic_dad: 0 - 使用Go程序和直接调用
ioctl的C程序测试,均复现相同错误
Go测试代码
package main import ( "crypto/sha256" "fmt" "math/big" "math/rand" "net/netip" "os/exec" ) func main() { i := 0 for { i++ command := exec.Command("sudo", "ifconfig", "en0", "inet6", "add", GenerateIPv6Address().String()) out, err := command.CombinedOutput() if err != nil { fmt.Println("\u001B[33m", i, command.String(), "\u001B[0m") fmt.Println(fmt.Errorf(err.Error())) fmt.Println("\u001B[31m" + string(out) + "\u001B[0m") break } else { fmt.Println("\u001B[32m", i, command.String()+"\u001B[0m") fmt.Println(string(out)) } } } func GenerateIPv6Address() netip.Addr { seedBytes := make([]byte, 8) _, err := rand.Read(seedBytes) if err != nil { return netip.Addr{} } hash := sha256.Sum256(seedBytes) seedValue := new(big.Int).SetBytes(hash[:8]) randInt := seedValue.Uint64() group2 := randInt % 65534 group3 := (randInt >> 16) % 65534 group4 := (randInt >> 32) % 65534 ipStr := fmt.Sprintf("fc00:%x::%x:%x", group2, group3, group4) if ip, err := netip.ParseAddr(ipStr); err == nil { return ip } return netip.Addr{} }
C测试代码
#include <stdio.h> #include <sys/socket.h> #include <stdlib.h> #include <string.h> #include <sys/ioctl.h> #include <arpa/inet.h> #include <unistd.h> #include <netinet/in.h> #include <netinet/in_var.h> #include <netinet6/nd6.h> #include <sys/errno.h> #include <stdint.h> char* generateIPv6() { unsigned short group[8]; char *buffer = malloc(40); // IPv6 max length is 39 characters + null terminator if (!buffer) { perror("Failed to allocate memory"); exit(EXIT_FAILURE); } group[0] = 0xfc00; // Unique local address prefix for (int i = 1; i < 8; i++) { group[i] = arc4random() % 65536; } // Format the IPv6 address into the buffer snprintf(buffer, 40, "%x:%x:%x:%x:%x:%x:%x:%x", group[0], group[1], group[2], group[3], group[4], group[5], group[6], group[7]); return buffer; // Return the dynamically allocated address } int main() { int sockfd; struct in6_aliasreq ifr6; const char *interface = "en0"; int prefix_len = 64; // Create socket sockfd = socket(AF_INET6, SOCK_DGRAM, 0); if (sockfd < 0) { perror("Socket creation failed"); return 1; } // Infinite loop to generate and add IPv6 addresses while (1) { char *ip_address = generateIPv6(); // Prepare interface request memset(&ifr6, 0, sizeof(ifr6)); strncpy(ifr6.ifra_name, interface, IFNAMSIZ); if (inet_pton(AF_INET6, ip_address, &ifr6.ifra_addr.sin6_addr) != 1) { perror("Invalid IP address"); free(ip_address); close(sockfd); return 1; } ifr6.ifra_addr.sin6_len = sizeof ifr6.ifra_addr; ifr6.ifra_addr.sin6_family = AF_INET6; ifr6.ifra_prefixmask.sin6_family = AF_INET6; ifr6.ifra_prefixmask.sin6_len = sizeof ifr6.ifra_prefixmask; memset(&ifr6.ifra_prefixmask.sin6_addr, 0xFF, prefix_len / 8); // Set infinite lifetime ifr6.ifra_lifetime.ia6t_vltime = ifr6.ifra_lifetime.ia6t_pltime = ND6_INFINITE_LIFETIME; // Perform ioctl to add the address if (ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6) < 0) { perror("Failed to add IPv6 address"); fprintf(stderr, "Error: %s\n", strerror(errno)); free(ip_address); break; // Exit loop on error } printf("IPv6 address added successfully: %s\n", ip_address); free(ip_address); } close(sockfd); return 0; }
C程序测试输出
$ sudo ktrace trace -Ss -f C4 -c ./output/bind_ipv6_using_syscall | grep bind_ipv6_using_sys ... IPv6 address added successfully: fc00:2452:fc7e:5604:3d5c:ee50:a275:1ee3 IPv6 address added successfully: fc00:a332:1532:86bc:4f09:c01:b845:1685 IPv6 address added successfully: fc00:5f4c:dcfe:100:4542:9626:430c:de7f IPv6 address added successfully: fc00:eb40:218a:6561:8f5a:ebab:7ed1:d3dc IPv6 address added successfully: fc00:3422:b629:5d3a:7b69:caf0:4524:a18d IPv6 address added successfully: fc00:cd47:a34b:f045:9620:2edd:9bc4:49f5 IPv6 address added successfully: fc00:305d:582c:94ff:d0fe:6d6f:b7df:31e1 IPv6 address added successfully: fc00:42e2:4920:93cc:6dec:8b3c:f34f:b7ef IPv6 address added successfully: fc00:e1c3:dd39:53e:64f2:f047:8135:ee6c IPv6 address added successfully: fc00:91ee:9b52:7cd0:862f:9101:84fd:9935 Failed to add IPv6 address: Cannot allocate memory Error: Cannot allocate memory
问题解答
1. 是否由内核/系统对单接口IPv6地址数量的限制导致?
是。macOS内核对单网络接口的IPv6地址数量存在硬编码上限,测试中固定在第18个地址触发错误,正是触发了这个内核限制。错误提示的“Cannot allocate memory”并非指系统内存不足,而是内核分配给该接口的IPv6地址结构内存配额耗尽。
2. macOS中该限制是否可调整?
默认情况下无法调整。这个上限是内核代码中的硬编码值,没有提供用户空间可修改的sysctl参数或配置项。现代macOS受系统完整性保护(SIP)限制,用户无法自行编译修改内核参数的自定义内核,因此官方渠道下无法调整该限制。
3. 排查工具与日志
- 内核日志排查:执行以下命令查看内核层面的详细日志,可找到触发限制时的内核报错信息:
log show --predicate 'process == "kernel"' --info --debug --last 1h - 系统调用跟踪:使用
dtruss跟踪程序的系统调用过程,查看ioctl失败时的详细上下文:sudo dtruss ./output/bind_ipv6_using_syscall - 内核参数检查:查看所有IPv6相关内核参数,确认是否存在隐藏的配置项(目前已知无此类可调整参数):
sysctl -a | grep net.inet6 - 实时地址数量验证:用以下命令查看当前接口已分配的IPv6地址数量,确认触发错误时的数量是否一致:
ifconfig en0 inet6 | grep net6 | wc -l
内容的提问来源于stack exchange,提问作者Mohit Kumar
相关产品推荐
相关产品推荐

