When binding to ::
(IN6ADDR_ANY), Linux systems by default accept both native IPv6 connections and IPv4 connections through IPv4-mapped IPv6 addresses (e.g., ::ffff:192.0.2.1
). This behavior is controlled by the net.ipv6.bindv6only
sysctl parameter.
To disable IPv4-mapped addresses globally:
# Temporary setting (until reboot)
sudo sysctl -w net.ipv6.bindv6only=1
# Permanent setting (add to /etc/sysctl.conf or /etc/sysctl.d/)
echo "net.ipv6.bindv6only = 1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
For applications where you want to control this behavior at runtime:
C/C++ Socket Programming
int enable = 1;
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &enable, sizeof(enable));
Python Example
import socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
sock.bind(('::', 8080))
After making changes, verify the configuration:
# Check current sysctl value
sysctl net.ipv6.bindv6only
# Test connectivity
nc -6 -l :: 8080 # IPv6-only listener
nc -4 localhost 8080 # Should fail
Nginx Configuration
server {
listen [::]:80 ipv6only=on;
# ... other config
}
Apache Configuration
# ... config
Disabling IPv4-mapped IPv6 can improve security by:
- Reducing attack surface by eliminating IPv4 processing paths
- Preventing potential IPv4-related vulnerabilities in IPv6 services
- Simplifying firewall rules by clearly separating IPv4/IPv6 traffic
However, ensure your infrastructure fully supports IPv6 before implementing this change.
When you bind a service to ::
(IPv6 wildcard address) on Linux, the kernel automatically handles IPv4 connections through IPv4-mapped IPv6 addresses (like ::ffff:198.51.100.37
). This behavior is defined in RFC 4291 section 2.5.5.2.
There are several valid reasons for wanting pure IPv6 services:
- Security hardening by reducing attack surface
- Explicit control over protocol availability
- Preventing accidental IPv4 traffic when testing IPv6-only environments
- Compliance with network policies
The most effective way to disable IPv4-mapped IPv6 addresses system-wide is through sysctl:
# Disable IPv4-mapped IPv6 addresses
echo "net.ipv6.bindv6only = 1" >> /etc/sysctl.conf
sysctl -p
This setting makes IPv6 sockets truly IPv6-only. After applying this change, services binding to ::
will:
- Accept only IPv6 connections
- Return ECONNREFUSED for IPv4 attempts
- Require explicit binding to 0.0.0.0 for IPv4 service
For individual applications, you can use IPV6_V6ONLY socket option:
C Example
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd = socket(AF_INET6, SOCK_STREAM, 0);
int opt = 1;
// Enable IPv6-only mode
setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(8080),
.sin6_addr = in6addr_any
};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
listen(sockfd, 5);
// ... rest of server code
}
Python Example
import socket
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
sock.bind(('::', 8080))
sock.listen(5)
# ... rest of server code
After implementation, verify the behavior:
# IPv6 connection should work
nc -6 ::1 8080
# IPv4 connection should fail
nc -4 127.0.0.1 8080
Nginx
server {
listen [::]:80 ipv6only=on;
# IPv4 listeners must be explicitly declared
# listen 0.0.0.0:80;
}
SSH
# In /etc/ssh/sshd_config
AddressFamily inet6 # IPv6 only
# Or for dual stack:
# AddressFamily any
# ListenAddress ::
# ListenAddress 0.0.0.0
Problem: Legacy applications might not handle ECONNREFUSED properly.
Solution: Consider using a firewall rule to drop IPv4 packets instead of refusing them:
iptables -I INPUT -p tcp --dport 8080 -j DROP