How Cellular Network NAT and IP Assignment Works: A Deep Dive for Developers


10 views

When your mobile device connects to the internet through a cellular network, it traverses a complex infrastructure where multiple layers of network address translation (NAT) occur. Cell towers don't directly assign public IPs to individual devices - instead, carrier-grade NAT (CGNAT) systems manage this process at the network core.

Device (Private IP) → Tower → SGSN/GGSN (NAT Gateway) → Public Internet

Each device gets a private IP (like 10.x.x.x) within the carrier's network. The Serving GPRS Support Node (SGSN) or Gateway GPRS Support Node (GGSN) performs the NAT translation to public IPs.

Multiple devices may share a single public IP through port address translation (PAT). The system maintains a translation table mapping:

{
  "public_ip:port": "private_ip:port",
  "203.0.113.25:54321": "10.1.42.7:42891",
  "203.0.113.25:54322": "10.1.42.9:49152"
}

Incoming packets are routed based on this port mapping. TCP/UDP ports create the necessary differentiation between devices sharing an IP.

While NAT handles basic routing, websites use additional headers to identify devices:

  • X-Forwarded-For: May contain the original private IP (if configured)
  • User-Agent: Helps differentiate between device types
  • HTTP Cookies: Session tracking at application layer

Carriers typically use:

  1. Dynamic pools of public IPs assigned per geographic region
  2. IP allocation based on current network load and available resources
  3. Sticky assignments where devices keep the same IP for a session

Here's a Python example to detect your mobile IP assignment:

import requests

def get_mobile_ip_details():
    response = requests.get('https://api.ipify.org?format=json')
    ip_data = response.json()
    
    headers = {
        'User-Agent': 'Mozilla/5.0 (Linux; Android 10) MobileIPCheck/1.0'
    }
    response = requests.get(f'http://ip-api.com/json/{ip_data["ip"]}', 
                          headers=headers)
    return response.json()

print(get_mobile_ip_details())

This will show whether you're behind carrier NAT and your public-facing IP characteristics.

Developers must account for:

  • IP-based rate limiting won't work well (many users share IPs)
  • Geolocation services may show gateway locations rather than actual device locations
  • WebRTC implementations need special handling for NAT traversal

When your mobile device connects through a cellular network, it operates behind a Carrier-Grade NAT (CGNAT) system. This multi-layered architecture handles IP assignment differently than traditional ISPs:

// Simplified NAT mapping structure example
struct NATMapping {
    string publicIP;
    int publicPort;
    string privateIP;
    int privatePort;
    string IMSI;  // International Mobile Subscriber Identity
    time_t expiry;
};

Multiple devices on the same cell tower typically share a pool of public IPs, but not necessarily the same IP simultaneously. The system maintains:

  • Dynamic IP Allocation: IPs are assigned per session or flow
  • Port-Based Differentiation: Each connection gets unique port mappings
  • IMSI Binding: Links temporary IP assignments to device identity

The GPRS gateway maintains stateful translation tables to route responses correctly:

// Example packet flow (simplified)
1. Device (10.0.0.15:1234) → NAT (203.0.113.5:54321) → Website
2. Website responds to 203.0.113.5:54321
3. NAT checks mapping table:
   - 203.0.113.5:54321 → 10.0.0.15:1234
4. Packet routed to correct device

Mobile networks often inject headers for identification:

X-Forwarded-For: 10.0.0.15
X-Network-Info: MCC=310,MNC=410  // Mobile Country/Network Code
X-MSISDN: 15551234567  // Phone number (when available)

Different carriers implement varying approaches:

Strategy Description Example Carriers
Per-Tower Pool Dedicated IP range per cell site Verizon (US)
Regional Pool IPs shared across geographic area T-Mobile (EU)
Session-Based New IP per data session China Mobile

When coding for mobile users:

// Proper way to handle mobile IPs in your backend
function getClientIp(request) {
    return request.headers['x-forwarded-for'] || 
           request.connection.remoteAddress;
}

// Session handling recommendation
app.use((req, res, next) => {
    const deviceId = req.headers['x-device-id'] || 
                    generateFingerprint(req);
    // Use deviceId rather than IP for session tracking
});