When dealing with multiple IP aliases on a single NIC (like eth0:0, eth0:1 etc.), Linux follows specific rules to determine the source IP for outbound connections:
# Display current routing table
route -n
# OR
ip route show table all
The kernel uses this decision process:
- The most specific route matching the destination
- The preferred source address associated with that route
- The primary IP address of the output interface
For your FTP scenario, you can force source IP selection:
# Method 1: Bind to specific IP using curl
curl --interface 192.168.1.100 ftp://client.server.com/file.txt
# Method 2: Permanent solution using routing policy
ip route add default via 192.168.1.1 src 192.168.1.100 table 100
ip rule add from 192.168.1.100 table 100
For non-interactive jobs, explicitly specify the source:
#!/bin/bash
lftp -u user,pass -e "set net:bind-to-single-address yes; \
set net:connection-limit 1; get file; quit" \
ftp://client.server.com
Create a dedicated routing table for FTP connections:
# /etc/iproute2/rt_tables
100 ftp_route
# /etc/network/if-up.d/ftp-route
ip route add default via 192.168.1.1 src 192.168.1.100 table ftp_route
ip rule add fwmark 1 table ftp_route
iptables -A OUTPUT -t mangle -p tcp --dport 21 -j MARK --set-mark 1
Verify source IP selection:
# Check which IP will be used
ip route get to 8.8.8.8
# Monitor outgoing connections
tcpdump -i eth0 -nn 'dst host client.ftp.server and port 21'
When dealing with multiple IP aliases on a single network interface (like eth0:0, eth0:1 etc.), Linux follows these key rules to determine the source IP for outbound connections:
1. Strong Host Model: The kernel prefers IP addresses assigned to the interface through which the packet will be sent
2. Routing Table Consultation: The src attribute in routing rules influences selection
3. Application Binding: Some applications can explicitly bind to specific IPs
Let's examine your FTP case where connections must originate from 192.168.1.100. The default behavior can be verified using:
# Check current source IP selection
ip route get 8.8.8.8
# Example output:
# 8.8.8.8 via 192.168.1.1 dev eth0 src 192.168.1.100 uid 0
The src
field in the output shows which IP will be used. We can force specific behavior through:
# Method 1: Add explicit route
ip route add 203.0.113.25/32 via 192.168.1.1 src 192.168.1.100
# Method 2: Policy routing
ip rule add from 192.168.1.100 lookup 100
ip route add default via 192.168.1.1 table 100
The SSH session's source IP doesn't automatically determine outbound IPs. For cron jobs, the selection depends entirely on:
- The routing table at execution time
- Any application-specific binding
- Possible policy routing rules
We can write a test script to verify behavior:
#!/bin/bash
# test_source_ip.sh
TARGET="example.com"
echo "Testing connection to $TARGET"
# Method 1: Using curl
echo -n "Curl default: "
curl --interface eth0 --connect-timeout 3 -s http://$TARGET/ip | grep origin
# Method 2: Using netcat
echo -n "Netcat test: "
echo -e "GET /ip HTTP/1.1\nHost: $TARGET\n\n" | nc -w 3 -s 192.168.1.100 $TARGET 80 | grep origin
For mission-critical applications like your FTP transfer, consider these robust solutions:
# Create dedicated routing table
echo "200 ftpclient" >> /etc/iproute2/rt_tables
ip route add default via 192.168.1.1 table ftpclient
ip rule add from 192.168.1.100 table ftpclient
# Application-level binding
lftp -u username,password -e "set net:source-address 192.168.1.100; get /path/to/file; quit" ftp://server
For permanent configuration, add these to /etc/network/interfaces:
post-up ip route add 203.0.113.0/24 dev eth0 src 192.168.1.100
post-up ip rule add from 192.168.1.100 lookup ftpclient
If you observe unexpected source IP selection:
# Check all potential influencing factors
ip addr show
ip route show
ip rule show
sysctl -a | grep rp_filter
# Test with different tools
tracepath -n example.com
traceroute --source=192.168.1.100 example.com
Remember that reverse path filtering (rp_filter) can affect this behavior. Adjust with:
sysctl -w net.ipv4.conf.all.rp_filter=2
sysctl -w net.ipv4.conf.eth0.rp_filter=2