When the Kladde is “not reachable”, that can mean a lot: the name isn’t resolved, the address can’t be reached, nobody is listening on the port, a firewall drops the packet, or the app is listening, but in the wrong place. In this lesson you get to know the lower layers: addresses, ports and connections. At the end you design the address plan for the network the Kladde moves into in K4, and have a program check that it is correct.
Addresses: IPv4
Every device in a network has an IP address. An IPv4 address consists of 32 bits, written as four numbers from 0 to 255:
192.168.0.20, 10.20.3.4. Two addresses are special:
127.0.0.1is called localhost and always means your own machine. Packets to this address never leave it.0.0.0.0is not an address you call. A service that binds to0.0.0.0listens on all addresses of the machine.
Not every address can be reached on the internet. Three ranges are reserved for internal networks, and they are never routed on the internet:
| Range | typical use |
|---|---|
10.0.0.0/8 |
company networks, virtual networks in Azure |
172.16.0.0/12 |
internal as well; Docker takes 172.17.0.0/16 from it for its default network |
192.168.0.0/16 |
home networks behind a router |
On top of that comes 169.254.0.0/16 for addresses a device gives itself when it doesn’t get one. In Azure, a service answers at 169.254.169.254
that a VM uses, among other things, to fetch the sign-in tokens of its managed identity. That is why you never use this range for a
network of your own.
CIDR: networks as a prefix
A network is a block of contiguous addresses. It is written in CIDR notation: the first address, a slash and
the number of bits that are the same for all addresses in the block, the prefix. The remaining bits are free. 10.20.0.0/16 means: the first 16
bits (10.20) are fixed, the last 16 are free. That is 2^16 = 65,536 addresses, from 10.20.0.0 to 10.20.255.255.
You calculate the size as 2 to the power of (32 minus the prefix):
| Prefix | Addresses | Example | runs from … to |
|---|---|---|---|
/16 |
65,536 | 10.20.0.0/16 |
10.20.0.0 to 10.20.255.255 |
/22 |
1,024 | 10.20.4.0/22 |
10.20.4.0 to 10.20.7.255 |
/23 |
512 | 10.20.0.0/23 |
10.20.0.0 to 10.20.1.255 |
/24 |
256 | 10.20.2.0/24 |
10.20.2.0 to 10.20.2.255 |
/26 |
64 | 10.20.3.0/26 |
10.20.3.0 to 10.20.3.63 |
/32 |
1 | 10.20.3.4/32 |
exactly one address |
Two rules that are often forgotten:
- A block starts at a round spot. A
/23spans two/24blocks and must start at an even third number:10.20.0.0/23and10.20.2.0/23exist,10.20.1.0/23doesn’t. - Two blocks lie either inside each other or side by side. They can’t overlap partially. Whether two blocks overlap, you can therefore see by whether the first address of one lies in the other.
You may know the old notation with a netmask from the network settings of Windows: 255.255.255.0 is the same as /24,
255.255.0.0 the same as /16.
Azure adds a peculiarity: five addresses are reserved in every subnet, the first four and the last one. A /24 there has
251 usable addresses, so, and the smallest subnet is a /29 with three usable ones.
Exercise 1: Calculating
Calculate without a program, on paper or in your head:
- How many
/24subnets fit into10.20.0.0/16? - Which addresses does
10.20.8.0/21span? - Which of these networks does
10.20.5.17belong to:10.20.0.0/23,10.20.4.0/22or10.20.6.0/24? - How many addresses are usable in an Azure subnet
10.20.3.0/26? - Is
10.20.6.0/23a valid notation? And10.20.7.0/23?
How to check it: Compare with the solution. If you’re unsure about 2 and 5, write the third number out as eight bits.
Solution 1
- 256: a
/16has 8 more free bits than a/24, and 2^8 = 256. 10.20.8.0to10.20.15.255. A/21has 11 free bits, that is 2,048 addresses or eight/24blocks:10.20.8to10.20.15.- To
10.20.4.0/22, which runs from10.20.4.0to10.20.7.255.10.20.0.0/23ends at10.20.1.255,10.20.6.0/24starts at10.20.6.0. - 59: 64 addresses minus the five that Azure reserves.
10.20.6.0/23yes: 6 is even, the block runs to10.20.7.255.10.20.7.0/23no: 7 is odd,10.20.7.0lies in the middle of the block10.20.6.0/23. As bits: 6 =00000110, 7 =00000111. For the/23, the last bit of the third number is free and has to be 0 at the start of the block.
Networks with IPNetwork
.NET has the type System.Net.IPNetwork for this. Parse reads CIDR notation, Contains checks whether an address lies in the network.
This program works through an address plan and checks it. Create it in your practice folder as netplan.cs:
// netplan.cs
using System.Buffers.Binary;
using System.Net;
const string VnetCidr = "10.20.0.0/16";
(string Name, string Cidr)[] plan =
[
("snet-apps", "10.20.0.0/23"),
("snet-endpoints", "10.20.2.0/24"),
("AzureBastionSubnet", "10.20.3.0/26"),
];
var vnet = IPNetwork.Parse(VnetCidr);
var subnets = plan.Select(s => (s.Name, s.Cidr, Range: IPNetwork.Parse(s.Cidr))).ToArray();
Console.WriteLine($"{"Network",-20} {"Range",-15} {"From",-13} {"To",-13} {"Size",8} {"Usable",8}");
Console.WriteLine($"{"vnet-kladde-dev",-20} {vnet,-15} {First(vnet),-13} {Last(vnet),-13} {Size(vnet),8}");
foreach (var (name, _, range) in subnets)
{
Console.WriteLine($"{name,-20} {range,-15} {First(range),-13} {Last(range),-13} {Size(range),8} {Size(range) - 5,8}");
}
var problems = 0;
foreach (var (name, cidr, range) in subnets)
{
// Parse quietly clears the bits after the prefix: 10.20.1.0/23 becomes 10.20.0.0/23. A plan says what it means.
if (range.ToString() != cidr)
{
Console.WriteLine($"ERROR: {name}: {cidr} doesn't start at the beginning of a block, it reads as {range}.");
problems++;
}
if (!vnet.Contains(range.BaseAddress) || range.PrefixLength < vnet.PrefixLength)
{
Console.WriteLine($"ERROR: {name} isn't entirely inside the network {vnet}.");
problems++;
}
}
for (var i = 0; i < subnets.Length; i++)
{
for (var j = i + 1; j < subnets.Length; j++)
{
if (Overlaps(subnets[i].Range, subnets[j].Range))
{
Console.WriteLine($"ERROR: {subnets[i].Name} and {subnets[j].Name} overlap.");
problems++;
}
}
}
Console.WriteLine(problems == 0 ? "Plan is fine." : $"{problems} errors in the plan.");
return problems == 0 ? 0 : 1;
// Two CIDR blocks either lie one inside the other or apart, so one base address inside the other block is enough.
static bool Overlaps(IPNetwork a, IPNetwork b) => a.Contains(b.BaseAddress) || b.Contains(a.BaseAddress);
static long Size(IPNetwork n) => 1L << (32 - n.PrefixLength);
static IPAddress First(IPNetwork n) => n.BaseAddress;
static IPAddress Last(IPNetwork n)
{
var number = BinaryPrimitives.ReadUInt32BigEndian(n.BaseAddress.GetAddressBytes()) + (uint)(Size(n) - 1);
var bytes = new byte[4];
BinaryPrimitives.WriteUInt32BigEndian(bytes, number);
return new IPAddress(bytes);
}
dotnet run netplan.cs
Network Range From To Size Usable
vnet-kladde-dev 10.20.0.0/16 10.20.0.0 10.20.255.255 65536
snet-apps 10.20.0.0/23 10.20.0.0 10.20.1.255 512 507
snet-endpoints 10.20.2.0/24 10.20.2.0 10.20.2.255 256 251
AzureBastionSubnet 10.20.3.0/26 10.20.3.0 10.20.3.63 64 59
Plan is fine.
Three places deserve a second look:
IPNetwork.Parseis forgiving: from10.20.1.0/23it makes10.20.0.0/23without an error message. A plan that says10.20.1.0/23means something other than what it gets, though. That is why the program compares the block it read with the text that was written.Lastcalculates the last address as a number: the four bytes of the first address as a 32-bit number in network order (BigEndian), plus size minus one, and back.- The program ends with exit code 1 if the plan has errors. That way a script, or later a pipeline, can use it as a check.
Addresses: IPv6
IPv4 has only a little over four billion addresses, and they were handed out long ago. IPv6 has 128 bits, written in eight groups in hexadecimal,
such as 2001:db8:12:3::4. Two colons stand for any number of groups of zeros. The counterparts of the special addresses above:
| IPv4 | IPv6 | means |
|---|---|---|
127.0.0.1 |
::1 |
your own machine |
0.0.0.0 |
:: |
all addresses |
10.0.0.0/8 and others |
fd00::/8 |
internal, not routed on the internet |
169.254.0.0/16 |
fe80::/10 |
self-assigned address in the local network |
In an address with a port, IPv6 goes in square brackets: http://[::1]:8080/. With ASPNETCORE_HTTP_PORTS=8080, the setting of the .NET
images, Kestrel binds to [::]:8080 and accepts IPv4 connections on the same socket too; the log then says
Now listening on: http://[::]:8080. Many networks in Azure are still pure IPv4 today. The Kladde’s address plan is too.
Packets, ports and connections
Data travels in packets. Each carries a sender and a destination address. So that a machine knows which program gets a packet, there are ports, numbers from 0 to 65535:
- The ports up to 1023 are assigned to well-known services: 22 SSH, 25 SMTP, 53 DNS, 80 HTTP, 443 HTTPS. On Linux a program needs special rights to listen on them. That is why ASP.NET Core in a container listens on 8080 (lesson 3.0.1).
- A program that opens a connection gets a free ephemeral port from the operating system as the sender. On Linux it usually lies between 32768 and 60999, on Windows between 49152 and 65535.
A connection is uniquely identified by five values: protocol, sender address, sender port, destination address, destination port. A socket is the end of such a connection in a program. A service has one socket that listens, and one more for every connection it accepts.
There are two protocols you meet all the time:
| TCP | UDP | |
|---|---|---|
| Connection | yes, with setup and teardown | no, every packet on its own |
| Order and delivery | guaranteed, lost packets are repeated | no guarantee |
| Use | HTTP/1.1 and HTTP/2, SQL Server (1433), SMTP, SSH | DNS (53), HTTP/3 over QUIC, time synchronization |
A TCP connection begins with the three-way handshake:
Client Server (listens on 443)
| ---- SYN: "I want to connect" -------> |
| <--- SYN-ACK: "Agreed" ---------------- |
| ---- ACK: "Let's go" ----------------> |
| Connection is up (ESTABLISHED) |
Only then does data flow, for HTTPS first the TLS handshake (lesson 3.0.4), then the request. Three answers you should learn to tell apart:
- Connected: Somebody is listening on the port.
- Refused (
connection refused): The machine can be reached, but nobody is listening on this port. It answers at once with a reset. - No answer (timeout): The packet doesn’t arrive or the answer doesn’t come back. Usually a firewall silently drops it (lesson 3.0.6), or the address is wrong.
From this difference alone you can often tell whether an error lies with the app or in the network.
127.0.0.1 or 0.0.0.0
A service decides at startup which address it listens on. The following program starts a service on 127.0.0.1 and then
one on 0.0.0.0 and tries each time to reach it in two ways: through 127.0.0.1 and through the address the machine has in its
network.
// bind.cs
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
// The machine's own address in its network (not 127.0.0.1): the first IPv4 address of an interface that is up.
var own = NetworkInterface.GetAllNetworkInterfaces()
.Where(i => i.OperationalStatus == OperationalStatus.Up && i.NetworkInterfaceType != NetworkInterfaceType.Loopback)
.SelectMany(i => i.GetIPProperties().UnicastAddresses)
.Select(a => a.Address)
.First(a => a.AddressFamily == AddressFamily.InterNetwork);
foreach (var bindTo in new[] { IPAddress.Loopback, IPAddress.Any })
{
using var listener = new TcpListener(bindTo, 5090);
listener.Start();
Console.WriteLine($"Service on {listener.LocalEndpoint}");
foreach (var target in new[] { IPAddress.Loopback, own })
{
Console.WriteLine($" via {target,-15}: {await TryConnectAsync(target, 5090)}");
}
listener.Stop();
}
static async Task<string> TryConnectAsync(IPAddress address, int port)
{
using var client = new TcpClient();
try
{
await client.ConnectAsync(address, port).WaitAsync(TimeSpan.FromSeconds(2));
return "connected";
}
catch (SocketException e)
{
return $"refused ({e.SocketErrorCode})";
}
catch (TimeoutException)
{
return "no answer";
}
}
In the Codespace:
dotnet run bind.cs
Service on 127.0.0.1:5090
via 127.0.0.1 : connected
via 172.19.80.9 : refused (ConnectionRefused)
Service on 0.0.0.0:5090
via 127.0.0.1 : connected
via 172.19.80.9 : connected
The second address will be a different one on your machine. The service on 127.0.0.1 can be reached only through 127.0.0.1. Anyone coming through the address in the network
is refused, even from the same machine. On a Windows machine, when the second service starts, Windows Firewall may
ask whether dotnet may listen on the network. That is the right question at the right time: allow it for private networks at most, or
try the program in the Codespace.
For containers this is the most common network error of all. “From outside” means, for a container: through its network interface eth0, even
when the request comes from your own laptop via docker run -p 8080:8080. If the app in the container binds to 127.0.0.1, it listens only to
requests from inside the container itself. docker ps still shows the port as published, and from outside only an error comes. Kestrel even warns
about it, you just have to read the warning:
warn: Microsoft.AspNetCore.Hosting.Diagnostics[15]
Overriding HTTP_PORTS '8080' and HTTPS_PORTS ''. Binding to values defined by URLS instead 'http://127.0.0.1:8080'.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://127.0.0.1:8080
Who listens where? ss, Test-NetConnection and curl
On Linux, ss shows the sockets. -t means TCP, -l only listening ones, -n numbers instead of names, -p with program:
ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 512 127.0.0.1:8080 0.0.0.0:* users:(("dotnet",pid=1,fd=179))
The column Local Address:Port is the answer: 127.0.0.1:8080 means from inside only, 0.0.0.0:8080 from everywhere over IPv4, *:8080 from
everywhere over IPv4 and IPv6. There is no ss in the Kladde’s container; you see it with a debug container that shares the Kladde’s network
(lesson 3.0.1), after apt-get install -y iproute2. The older netstat -tlnp shows the same, where it is still installed.
On Windows, PowerShell asks the same like this. So that somebody is listening on port 5091, first start shutdown.cs from
lesson 3.0.1 in a second terminal with dotnet run shutdown.cs -- --urls http://127.0.0.1:5091 and end it afterwards with Ctrl+C:
Get-NetTCPConnection -State Listen -LocalPort 5091 | Select-Object LocalAddress, LocalPort, State, OwningProcess
LocalAddress LocalPort State OwningProcess
------------ --------- ----- -------------
127.0.0.1 5091 Listen 108316
Whether you can reach a port from your machine is checked by Test-NetConnection:
Test-NetConnection -ComputerName 127.0.0.1 -Port 5091
Test-NetConnection -ComputerName 127.0.0.1 -Port 5092
ComputerName : 127.0.0.1
RemoteAddress : 127.0.0.1
RemotePort : 5091
InterfaceAlias : Loopback Pseudo-Interface 1
SourceAddress : 127.0.0.1
TcpTestSucceeded : True
WARNING: TCP connect to (127.0.0.1 : 5092) failed
ComputerName : 127.0.0.1
RemoteAddress : 127.0.0.1
RemotePort : 5092
InterfaceAlias : Loopback Pseudo-Interface 1
SourceAddress : 127.0.0.1
PingSucceeded : True
PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded : False
The number for the process will be different on your machine. The second case is instructive: the machine answers ping (PingSucceeded), but nobody is listening on
port 5092. Reachable and ready are two different things. In bash, for the same question you use curl (lesson 3.0.1,
exit code 7 for “no connection”) or, for a port without HTTP, nc -vz host port.
ping and traceroute
ping sends ICMP packets, a protocol of its own without ports, and measures how long the answer takes. A missing echo proves little:
many machines and almost all services in Azure don’t answer ping at all. A working web app that stays silent on ping is the
normal case. For the question “Does my request arrive?”, a test on the port is the better way.
traceroute (tracert on Windows) shows the routers on the way to a destination. Each line is a hop with three measured times:
tracert -d -h 6 learn.microsoft.com
Tracing route to learn.microsoft.com [203.0.113.80]
over a maximum of 6 hops:
1 1 ms <1 ms <1 ms 192.168.178.1
2 8 ms 7 ms 8 ms 100.71.12.1
3 9 ms 9 ms 9 ms 198.51.100.17
4 * * * Request timed out.
5 11 ms 10 ms 11 ms 198.51.100.45
6 10 ms 10 ms 10 ms 203.0.113.80
This is an example with made-up addresses from the ranges reserved for documentation. Your hops will be different, and the
first is almost always your router. -d leaves out the names of the routers, which is faster. Stars at a hop only mean that this
router doesn’t answer; if it goes on after that, everything is fine. In the Codespace you get traceroute after sudo apt-get install -y traceroute; in Container Apps it is part of the debug console.
A packet capture with tcpdump or Wireshark shows every single packet, the handshake from above included. That is the sharpest
tool when nothing else explains anything. In K3 you hardly need it, because the telemetry from module 3.4 answers most questions faster.
NAT: many addresses behind one
Your home network has addresses from 192.168.0.0/16, which aren’t routed on the internet. Still, you reach learn.microsoft.com. That is done by
NAT (Network Address Translation): on the way out, the router replaces your private sender address and the port with its public
address and a port of its own, remembers the mapping and translates the answer back. From outside you see only the router.
You meet NAT again in three places:
- Docker:
docker run -p 8080:8080forwards port 8080 of your machine to port 8080 of the container. That is NAT in the other direction: from outside to inside. - Azure Container Apps: Outgoing connections of the Kladde, for example to the TED interface, reach the recipient from an address of the platform, not from one of your own. If you need a fixed outgoing address, say because a partner allows only that one, you need a network of your own with a NAT gateway (K4).
- Visitor addresses: Behind NAT, proxies and Front Door, the Kladde doesn’t see the visitor’s address. How to find out anyway, and why you are easily deceived doing so, comes in lesson 3.0.5 and in 3.3.4.
Exercise 2: The Kladde’s address plan
In K4 the Kladde gets a virtual network of its own in Azure: the Container Apps environment lies in it, the database, Key Vault and storage can be reached only through private addresses, and administrative access goes through Azure Bastion. Design the plan for it now.
- Choose an address range
/16for the networkvnet-kladde-dev. It must not overlap with172.17.0.0/16(Docker), with192.168.0.0/16(typical home networks that might later be connected through a VPN) and with the ranges that Azure Container Apps reserves for itself (169.254.0.0/16,172.30.0.0/16,172.31.0.0/16,192.0.2.0/24and, in an environment with workload profiles,100.100.0.0/17with three/19blocks behind it, together100.100.0.0to100.100.223.255). - Carve out three subnets:
snet-appsfor the Container Apps environment. It requires at least a/27; take room for growth.snet-endpointsfor the private endpoints of the database, Key Vault, storage and Service Bus.AzureBastionSubnetfor Azure Bastion. The name is prescribed by Azure, the size is at least/26.
- Enter your plan into
netplan.csand run the program. - Build in two errors on purpose, an overlap and a block that doesn’t start at a round spot, and see that the program finds them and ends with exit code 1. Take the errors out again.
- In the Kladde, create the file
docs/network.md: the network, the three subnets with purpose, size and usable addresses, and why you chose the range.
How to check it: dotnet run netplan.cs prints “Plan is fine.”, $? (or $LASTEXITCODE in PowerShell) is 0, with the
built-in errors it is 1, and docs/network.md names all three subnets with reasons.
Solution 2
The plan from the example above meets all the conditions. With the two deliberate errors, snet-endpoints at 10.20.1.0/24 and
AzureBastionSubnet at 10.20.3.32/26:
Network Range From To Size Usable
vnet-kladde-dev 10.20.0.0/16 10.20.0.0 10.20.255.255 65536
snet-apps 10.20.0.0/23 10.20.0.0 10.20.1.255 512 507
snet-endpoints 10.20.1.0/24 10.20.1.0 10.20.1.255 256 251
AzureBastionSubnet 10.20.3.0/26 10.20.3.0 10.20.3.63 64 59
ERROR: AzureBastionSubnet: 10.20.3.32/26 doesn't start at the beginning of a block, it reads as 10.20.3.0/26.
ERROR: snet-apps and snet-endpoints overlap.
2 errors in the plan.
One possible content for docs/network.md:
# The Kladde's network
Basis for the virtual network in K4 (4.7.2). Checked with netplan.cs (exit code 0).
## Address plan dev
| Network | Range | Addresses | usable | Purpose |
| ------------------ | ------------- | --------: | -----: | ------------------------------------------------------- |
| vnet-kladde-dev | 10.20.0.0/16 | 65,536 | | the whole network of the dev environment |
| snet-apps | 10.20.0.0/23 | 512 | 507 | Container Apps environment, delegated to Microsoft.App/environments |
| snet-endpoints | 10.20.2.0/24 | 256 | 251 | private endpoints: SQL, Key Vault, Storage, Service Bus |
| AzureBastionSubnet | 10.20.3.0/26 | 64 | 59 | Azure Bastion, name and minimum size prescribed by Azure |
Free for later: 10.20.3.64 to 10.20.255.255.
## Why 10.20.0.0/16
- does not lie in 172.17.0.0/16 (Docker), nor in 192.168.0.0/16 (home networks),
nor in the ranges that Container Apps reserves;
- test and prod get ranges of their own without overlap (10.21.0.0/16, 10.22.0.0/16),
so that the networks can be connected later.
Why /23 for the apps, although /27 would do? The size of a subnet can no longer be changed once the environment is in it, and
when rolling out a new revision it briefly needs twice as many addresses. There is plenty of room in the /16. For Bastion, on the other hand, /26 is
the requirement, and more gains nothing. Whether you take 10.20 or 10.40 makes no difference; what matters is that dev, test and prod never overlap.
Networks with the same addresses can’t be connected to each other later, and rebuilding the addresses means creating everything anew.
Exercise 3: Bound to the wrong address
- Run
bind.csin the Codespace and explain each of the four lines. - Start the Kladde’s image from K2 once the way it is meant to be, and once with a binding to
127.0.0.1. The commands are below this list. Put the name of your image in place ofkladde-web. If the Kladde needs its database for/health, start withdocker composeand set the variable there. - After the second start, use a debug container to see where the Kladde listens, and ask it from the inside.
- Clean up:
docker stop kladde-test.
docker run --rm -d --name kladde-test -p 8080:8080 kladde-web
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8080/health
docker stop kladde-test
docker run --rm -d --name kladde-test -p 8080:8080 -e ASPNETCORE_URLS=http://127.0.0.1:8080 kladde-web
curl -sS -o /dev/null -w '%{http_code}\n' http://localhost:8080/health
How to check it: On the first start, curl answers with 200, on the second with an error. In the debug container, ss -tln shows
127.0.0.1:8080, and curl to http://127.0.0.1:8080/health answers 200 there.
Solution 3
On 1: A service on 127.0.0.1 accepts only connections that go to 127.0.0.1. The address in the network belongs to a different
interface, and nobody is listening there, so a reset comes at once (ConnectionRefused). A service on 0.0.0.0 listens on all
interfaces and can be reached both ways.
On 2: Depending on the Docker version, the second curl reports Empty reply from server (exit code 52) or Connection reset by peer (56), and
-w prints 000. Docker’s forwarding does accept the connection on your machine, but in the container it finds nobody who listens on
the interface eth0. docker logs kladde-test shows the warning “Overriding HTTP_PORTS” from above.
On 3:
docker run --rm -it --network container:kladde-test ubuntu:24.04 bash
apt-get update && apt-get install -y --no-install-recommends iproute2 curl
ss -tln
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/health
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0 512 127.0.0.1:8080 0.0.0.0:*
200
From the inside the Kladde is healthy, from the outside it can’t be reached. This is exactly what the error looks like in Azure Container Apps: the platform reports that
the app doesn’t answer on its target port, and its health probe fails, although there is not a single error message in the Kladde’s log.
The ingress of Container Apps comes, like Docker, from the outside through eth0.
The Kladde after this lesson
docs/network.mdcontains the address plan forvnet-kladde-devwith three subnets and the reasons for the range.- The plan is checked with
netplan.cs. It is the basis for the Kladde’s network in K4 and for the rules of the network security group in lesson 3.0.6.
Commit docs/network.md as a pull request. netplan.cs stays in your practice folder; if you want to put it next to the plan, use a
folder outside the Kladde’s projects.
What you can do now
- You read and calculate CIDR blocks and know why a block starts at a round spot and how many addresses Azure keeps per subnet.
- You know the private ranges of IPv4 and the counterparts in IPv6.
- You tell TCP and UDP apart, know the handshake and tell from “connected”, “refused” and “no answer” where an error lies.
- You know why a service in a container has to be bound to
0.0.0.0(or[::]), and you find out withss,Get-NetTCPConnectionandTest-NetConnectionwho listens where. - You know what NAT does and where you meet it in Docker and Azure.
- The Kladde has a checked address plan.
Further reading
- Introduction to Azure Virtual Networks: a module from Microsoft Learn on networks, subnets and address spaces in Azure.
- IP address planning for Azure virtual networks: the reserved addresses and how to build a plan with room to spare.
- Virtual network configuration in Azure Container Apps: size and reserved ranges of an environment’s subnet.
- IPNetwork Struct: the type for networks in .NET.
- Test-NetConnection: parameters and outputs.