📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

How do firewalls, security groups and network policies work? A .NET service cannot reach a dependency, or its connections drop after being idle. How do you troubleshoot it?

Stateful vs stateless firewalls, cloud NSGs/security groups, NACLs, WAFs, Kubernetes NetworkPolicy, idle timeouts, and a step-by-step connectivity troubleshooting method.

Mid-Level Debugging Intermediate cloud firewall network-policy networking security-groups troubleshooting

🎯 Key points

  • Stateful firewalls (Azure NSGs, AWS security groups, most host firewalls) track connections so replies are allowed automatically; stateless filters (AWS NACLs) need explicit rules for return traffic on ephemeral ports.
  • Network-level controls filter by IP, port and protocol; application-level controls (WAF, API gateway, Azure Firewall with FQDN or TLS inspection) also inspect HTTP content, hostnames or SNI.
  • Check each layer on the path: edge WAF or gateway, central firewall, route tables and NAT, NSG or security group, Kubernetes NetworkPolicy, host firewall, and the service's own firewall or private endpoint settings.
  • Classify the error first: timeout means dropped packets (firewall, routing, wrong IP), refused means no listener or a reject rule, reset after idle means a middlebox forgot the connection, TLS or HTTP errors mean the path works.
  • Test from the source workload (nc -vz, curl, Test-NetConnection, a netshoot debug pod), then confirm with NSG or VPC flow logs, Network Watcher IP flow verify, Reachability Analyzer or tcpdump.
  • A default-deny egress NetworkPolicy must allow UDP and TCP 53 to cluster DNS, and Private Link or service endpoints change which IP and route a dependency uses, so DNS and rules must match.
  • Middleboxes have idle timeouts: Azure Load Balancer and NAT Gateway default to 4 minutes (configurable up to 120), AWS NAT Gateway and NLB use 350 seconds; Linux TCP keep-alive starts only after 2 hours by default.
  • Keep client pool idle timeouts or keep-alives below the shortest middlebox timeout, fail fast with ConnectTimeout, retry idempotent calls, and manage rules as code with documented egress per service.

⏱ 30-second answer

A firewall allows or blocks traffic based on rules (source, destination, port, protocol, and for L7 firewalls/WAFs, HTTP content).

  • Stateful firewalls (Azure NSGs, AWS security groups, most host firewalls) track connections, so return traffic is automatically allowed. Stateless filters (AWS NACLs) need rules both ways.
  • Layers: host firewall (iptables/nftables, Windows Defender Firewall), cloud NSG/security group, network appliances, Kubernetes NetworkPolicy, and WAF at the edge.

Troubleshoot by symptom: timeout usually means dropped by a firewall or routing, refused means reached the host but nothing listening, reset after idle often means a firewall or load balancer idle timeout removed the connection state. Test from the source (nc -vz, Test-NetConnection), check rules and flow logs in both directions, and set TCP keep-alives or shorter pool idle timeouts below the firewall's idle timeout.

🗣 Full answer (senior)

Stateful vs stateless

Stateful (NSG, security group):
  outbound app → db:1433 allowed  ──►  connection tracked
  reply db:1433 → app:51544       ──►  allowed automatically (matches tracked flow)

Stateless (NACL):
  outbound rule app → db:1433
  inbound rule  db:1433 → app:1024-65535  ← must be added, or replies are dropped

Layers you may need to check

Layer Examples Scope
Edge / L7 WAF, Front Door, CloudFront, API gateway HTTP rules, rate limits
Network Azure Firewall, NVA, AWS Network Firewall Central egress/ingress
Subnet / NIC NSG, security group, NACL Per subnet or interface
Cluster Kubernetes NetworkPolicy, service mesh policy Pod-to-pod
Host iptables/nftables, Windows Firewall Per machine
Service Database firewall rules, private endpoint settings Per resource

Troubleshooting method

  1. Read the error precisely: timeout vs refused vs reset vs TLS/HTTP error.
  2. Test from the source (same pod, VM or subnet): DNS resolves to the expected IP? nc -vz host port or Test-NetConnection host -Port 1433.
  3. Follow the path: source egress rules, routes (UDR, NAT gateway), destination ingress rules, host firewall, service firewall.
  4. Use the platform's tools: NSG flow logs / VPC flow logs, Azure Network Watcher "IP flow verify" and "Connection troubleshoot", Reachability Analyzer.
  5. Capture if needed: tcpdump -i any host 10.0.2.4 and port 1433 to see SYNs without replies.

Production example

A .NET worker called a partner's SFTP and HTTPS endpoints. It worked during the day but the first call after a quiet night failed with IOException: Unable to read data from the transport connection: Connection reset by peer. The Azure NAT gateway's default idle timeout is 4 minutes; idle pooled connections had been silently dropped, and the next write on them was reset. We set SocketsHttpHandler.PooledConnectionIdleTimeout to 2 minutes, enabled TCP keep-alive for the SFTP client, and added a retry for idempotent calls. The resets stopped.

🔍 What the interviewer is checking

Checks practical network security knowledge and a structured troubleshooting approach.

Green flags

  • Knows stateful vs stateless and where each applies.
  • Interprets timeout/refused/reset correctly.
  • Tests from the source and uses flow logs/captures.
  • Knows idle timeouts and the DNS-in-NetworkPolicy trap.

Red flags

  • "Open all ports" or disable the firewall to test in production.
  • Tests only from a laptop.

📚 Learn in depth

1. What "timeout", "refused" and "reset" look like on the wire

Timeout:   SYN ────► (dropped)        retry SYN ────► (dropped) ...  → SocketException TimedOut
Refused:   SYN ────► host
           ◄──── RST (no listener)                                    → ConnectionRefused
Reset:     established ... idle ... firewall forgets flow
           data ────► middlebox / server: unknown connection
           ◄──── RST                                                   → ConnectionReset

Some firewalls send RST instead of dropping ("reject" vs "drop"), so refused can also come from a firewall. Check the source IP of the RST in a capture.

2. Idle timeouts

Component Typical default idle timeout
Azure Load Balancer / NAT Gateway 4 minutes (configurable)
AWS NAT Gateway 350 seconds
AWS NLB 350 seconds
Corporate firewalls often 30–60 minutes
Linux conntrack established 5 days (rarely the limit)

TCP keep-alive by default on Linux starts after 2 hours, far longer than these. Options: shorter pool idle timeouts in clients, application-level pings, or TCP keep-alive with shorter intervals (SocketsHttpHandler.KeepAlivePingDelay is for HTTP/2 pings; for raw sockets set SocketOptionName.TcpKeepAliveTime).

3. Kubernetes NetworkPolicy

  • By default all pods can talk to all pods. Once any policy selects a pod for ingress (or egress), only explicitly allowed traffic passes for that direction.
  • A common outage: adding a default-deny egress policy and forgetting DNS (UDP/TCP 53 to CoreDNS), so every name lookup times out.
  • Enforcement needs a CNI that supports it (Calico, Cilium, Azure NPM/Cilium).

4. Egress control

Many organisations force outbound traffic through a central firewall with FQDN allow-lists. New dependencies (a SaaS API, a package feed, a telemetry endpoint) then fail with timeouts until rules are added. Keep a documented list of required egress endpoints per service.

5. Tools

nc -vz db.internal 1433                 # Linux/macOS
curl -v telnet://db.internal:1433        # if nc is missing
Test-NetConnection db.internal -Port 1433  # Windows PowerShell
tcpdump -ni any host 10.0.2.4 and port 1433
kubectl run tmp --rm -it --image=nicolaka/netshoot -- bash   # debugging pod with tools

6. Try it yourself

  1. In a test cluster, apply a default-deny egress NetworkPolicy to a namespace and watch DNS lookups fail; then allow port 53 to kube-dns.
  2. Use iptables -A INPUT -p tcp --dport 8080 -j DROP vs -j REJECT on a test VM and compare the client errors.
  3. Hold an idle HttpClient connection through a NAT with a short idle timeout and observe the reset on the next request.

💻 Code

// Keep pooled connections shorter-lived than middlebox idle timeouts
builder.Services.AddHttpClient<PartnerClient>()
    .ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
    {
        PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),  // below a 4-minute NAT idle timeout
        PooledConnectionLifetime = TimeSpan.FromMinutes(10),
        ConnectTimeout = TimeSpan.FromSeconds(5)               // fail fast on dropped SYNs
    })
    .AddStandardResilienceHandler();                            // retries idempotent requests with backoff
// Raw socket: enable TCP keep-alive with short intervals (.NET Core 3.0+)
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 60);      // seconds idle
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 15); // between probes
socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 4);
await socket.ConnectAsync("sftp.partner.com", 22, ct);
# NetworkPolicy: default-deny egress but allow DNS and the database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: orders-egress, namespace: orders }
spec:
  podSelector: { matchLabels: { app: orders-api } }
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector: {}
          podSelector: { matchLabels: { k8s-app: kube-dns } }
      ports: [{ protocol: UDP, port: 53 }, { protocol: TCP, port: 53 }]
    - to:
        - ipBlock: { cidr: 10.20.4.0/24 }   # database subnet
      ports: [{ protocol: TCP, port: 1433 }]

⚠ Common mistakes

Mistake: Testing connectivity from your laptop instead of the failing workload. Why it is wrong: Your laptop has different routes, DNS answers and firewall rules, so a successful test proves nothing about the pod or VM. Instead: Run nc -vz, curl -v or Test-NetConnection from the same pod, VM or subnet, for example with kubectl debug or a netshoot pod.

Mistake: Treating every connection failure as "the firewall". Why it is wrong: Refused usually means nothing is listening, and TLS or HTTP errors mean the network path already works. Instead: Classify the error (timeout, refused, reset, TLS, HTTP) and look at the matching layer.

Mistake: Forgetting return traffic with stateless rules. Why it is wrong: NACLs drop replies to ephemeral ports unless an inbound rule allows them, so outbound calls time out. Instead: Know which controls are stateful, and add ephemeral-port return rules for stateless ones.

Mistake: Adding a default-deny egress policy without allowing DNS. Why it is wrong: Every name lookup times out and the whole service fails, which looks like a database or API outage. Instead: Always include UDP and TCP 53 to the cluster DNS, and test the policy in a lower environment first.

Mistake: Ignoring idle timeouts on NAT gateways and load balancers. Why it is wrong: Pooled connections die silently after 4 to 6 minutes idle and the next request gets a reset. Instead: Set PooledConnectionIdleTimeout or TCP keep-alive below the shortest middlebox timeout and retry idempotent calls.

🏛 Senior insight: trade-offs

Connectivity debugging is about narrowing the layer quickly and proving it with data rather than guesses.

Symptom Most likely layer Evidence to collect
Connect timeout Firewall, NSG, route, wrong IP from DNS Flow logs showing deny, tcpdump with SYNs and no SYN-ACK
Connection refused Destination has no listener, or a reject rule ss -ltnp on target, source IP of the RST
Reset after idle NAT or load balancer idle timeout Correlation with idle duration, middlebox settings
Works by IP, fails by name DNS or private DNS zone dig from the workload
TLS or HTTP error Path fine; certificate or app problem openssl s_client, app logs

I design for middleboxes: clients recycle idle connections, fail fast on connect and retry idempotent operations. I treat network rules as code (Terraform or Bicep, NetworkPolicies in the repo), keep each service's required egress documented, and monitor connection reset and timeout rates alongside flow-log denies, so a new dependency is a reviewed change rather than a 2 am discovery.

🔗 Follow-ups the interviewer may ask

  1. When: Candidate sees connection refused Why: Checks binding and listening sockets
    How to answer

    A port is a 16-bit number (0 to 65535) that lets one IP address host many services. A socket is the OS object for one endpoint: a listening socket bound to IP:port, and a connected socket for each accepted connection, identified by the 4-tuple (client IP, client port, server IP, server port).

    The Docker problem is almost always binding: the app listens on localhost (127.0.0.1) inside the container, which is unreachable from outside its network namespace, or it listens on a different port from the one you mapped. The official .NET 10 images set ASPNETCORE_HTTP_PORTS=8080, which binds all interfaces on 8080 (the default since .NET 8), so map it with -p 5000:8080 and do not override it with a localhost URL.

  2. When: Candidate forgets DNS in egress rules Why: Probes DNS in Kubernetes
    How to answer

    DNS maps names to records: A (IPv4), AAAA (IPv6), CNAME (alias), plus MX, TXT, SRV, NS and PTR. A client asks a recursive resolver, which walks root → TLD → authoritative servers and caches the answer for its TTL. .NET uses the OS resolver (getaddrinfo) and does not cache itself.

    Typical incidents:

    • Stale endpoint after failover: DNS changed, but HttpClient kept pooled connections to the old IP. Fix with PooledConnectionLifetime.
    • Slow or failing lookups in Kubernetes: ndots:5 makes each external name try several search domains first, and UDP conntrack races cause occasional 5-second delays.
    • Split-horizon / private endpoints: the name resolves to a public IP from one network and a private IP from another.

    Diagnose with dig/nslookup from the same pod, check TTLs, and look at the DNS phase of the HTTP timing.

  3. When: Candidate talks about layered network controls Why: Moves to designing the full path
    How to answer

    Layered from outside in:

    1. DNS with low TTL for the public name.
    2. Global edge / CDN + WAF (Front Door, CloudFront + AWS WAF, Cloudflare): anycast entry, TLS termination close to users, caching, DDoS and OWASP rules, rate limiting.
    3. Regional L7 gateway / ingress (App Gateway, ALB, ingress controller): routing, re-encrypted TLS, health probes; only accepts traffic from the edge.
    4. App tier in private subnets (AKS/ECS/App Service with VNet integration): no public IPs, NSGs/security groups and NetworkPolicies limit east-west traffic.
    5. Data tier via private endpoints (SQL, storage, Key Vault) with private DNS zones; public access disabled.
    6. Controlled egress through a NAT gateway or firewall with allow-lists.

    Plus observability at every hop (flow logs, edge logs, traces with traceparent), and automated certificates and network rules as code.

🛠 Real-world problems: how you handle them

Connection resets on the first call after quiet periods
Situation
[Partner integration worker] on .NET [10] in [Azure] calling a partner's HTTPS API and SFTP server through a [NAT gateway].
Signals
`Connection reset by peer` on the first call each morning and after lunch; later calls fine; partner saw nothing in their logs.
Investigation
Errors only after > [4] minutes idle. NAT gateway idle timeout was the default 4 minutes; `HttpClient` default pooled idle timeout was longer, so dead connections were reused.
Resolution
Set `PooledConnectionIdleTimeout` to 2 minutes, enabled TCP keep-alive (60 s) for SFTP, and added the standard resilience handler for idempotent calls.
Prevention
Documented middlebox timeouts per environment; client defaults in a shared library; alert on connection reset rate.

How to tell it (STAR)

**S:** Our [partner integration] failed on the first call every morning with connection resets. **T:** I was asked to make it reliable. **A:** The failures only happened after more than [four] minutes of idle time, which matched the NAT gateway's idle timeout. Our pooled connections outlived it, so the first request used a dead connection. I lowered the pool idle timeout, enabled keep-alives for SFTP and added retries for idempotent calls. **R:** Morning failures went to [zero], and the shared client defaults now prevent it in other services.

↩ Usually asked after

More in Networking Basics

Last updated 23 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me