MidТеория6 min

Unicast, Broadcast, Multicast, Anycast

Четыре типа адресации: один-к-одному, один-ко-всем, один-к-группе, один-к-ближайшему. IGMP, mDNS, ARP, DHCP

Четыре способа доставки пакета

IP-адресация определяет, кому пакет доставляется. Исторически различают четыре модели:

Unicast      — один отправитель, один получатель           (1 → 1)
Broadcast    — один отправитель, все в сегменте LAN        (1 → all)
Multicast    — один отправитель, группа подписчиков         (1 → many)
Anycast      — один отправитель, ближайший из группы        (1 → nearest)
Unicast:                  Broadcast:
  A  ──────────►  B         A  ──┬──► B
                               ├──► C
                               ├──► D
                               └──► E (все в LAN)

Multicast:                Anycast:
  A  ──┬──► B(sub)          A  ───► B  (nearest)
       ├──► C(sub)              ╲╱
       ╳  → D(not sub)          ╱╲ (B, C, D share IP)
       └──► E(sub)

Unicast

Самый привычный тип -- пакет адресован конкретному IP. 99% трафика интернета -- unicast.

Ethernet header: dst MAC = aa:bb:cc:dd:ee:ff  (конкретный хост)
IP header:      dst IP = 192.168.1.5

Коммутаторы и маршрутизаторы доставляют пакет одному получателю. Если нужно передать одно и то же 1000 клиентам -- 1000 unicast-отправлений. Неэффективно, но просто.

Broadcast

Пакет адресован всем в LAN-сегменте. Существует только в IPv4, на уровне канала (Ethernet) и сети (IP).

Ethernet: dst MAC = ff:ff:ff:ff:ff:ff           ← broadcast MAC
IPv4:
  Limited broadcast:  255.255.255.255           ← никогда не роутится
  Directed broadcast: 192.168.1.255             ← broadcast для подсети

Коммутатор получив broadcast MAC, копирует фрейм на все порты. Роутеры по умолчанию дропают broadcast (иначе был бы storm).

Где используется broadcast

ARP (Address Resolution Protocol)

Хост хочет послать пакет на 192.168.1.5, но не знает его MAC:

ARP request (broadcast):
  "Who has 192.168.1.5? Tell 192.168.1.10"
  Sent to ff:ff:ff:ff:ff:ff

Все в LAN получают фрейм.
Хост с IP 192.168.1.5 отвечает:
  "192.168.1.5 is at aa:bb:cc:dd:ee:ff"
  ARP reply -- unicast обратно.
# Посмотреть ARP таблицу
ip neigh show
# 192.168.1.1 dev eth0 lladdr 00:11:22:33:44:55 REACHABLE
# 192.168.1.5 dev eth0 lladdr aa:bb:cc:dd:ee:ff STALE

# Вручную отправить ARP request
arping -I eth0 192.168.1.5

DHCP Discovery

Клиент только включился, не знает ни своего IP, ни адреса DHCP-сервера:

1. Client → broadcast:  DHCPDISCOVER (src=0.0.0.0, dst=255.255.255.255)
2. Server → broadcast:  DHCPOFFER    (предлагает IP, параметры)
3. Client → broadcast:  DHCPREQUEST  (принимаю этот offer)
4. Server → broadcast:  DHCPACK      (confirmed)

Всё через broadcast, потому что на этапе 1-3 у клиента ещё нет IP для unicast.

Wake-on-LAN

Magic packet на broadcast MAC пробуждает спящий хост.

Минусы broadcast

  • Шум в LAN -- каждый хост обязан обработать broadcast (хотя бы на уровне сетевой карты).
  • Ломает сегментацию -- broadcast domain = размер VLAN.
  • Storm потенциал -- петля в топологии заставляет broadcast крутиться бесконечно, съедая всю пропускную способность.

IPv6 полностью отказался от broadcast -- его заменил multicast с well-known адресами (например, ff02::1 -- все ноды в линке).

Multicast

Multicast -- между unicast и broadcast: пакет доставляется группе заинтересованных. Экономит bandwidth по сравнению с N unicast-копиями.

Адресация

  • IPv4: 224.0.0.0/4 (класс D, 224.0.0.0 - 239.255.255.255).
    • 224.0.0.0/24 -- link-local (не роутятся).
    • 239.0.0.0/8 -- administratively scoped (частный multicast).
  • IPv6: ff00::/8. С префиксами scope: ff02:: -- link, ff05:: -- site, ff0e:: -- global.

Известные адреса:

  • 224.0.0.1 -- все хосты в линке.
  • 224.0.0.2 -- все роутеры.
  • 224.0.0.251 -- mDNS (Bonjour).
  • 239.255.255.250 -- SSDP (UPnP discovery).
  • ff02::1 -- all nodes (IPv6 link-local).
  • ff02::fb -- mDNS (IPv6).

IGMP: подписка на группу

Host подписывается на multicast-группу через IGMP (Internet Group Management Protocol, IPv4) или MLD (Multicast Listener Discovery, IPv6):

Host → IGMP Membership Report → "я хочу получать 239.1.1.1"

Switch с IGMP snooping видит запрос, запоминает: "порт 5 = 239.1.1.1".
Теперь multicast-трафик на 239.1.1.1 копируется только на порт 5,
а не на все порты, как было бы без snooping.

Router запоминает: "через интерфейс eth0 есть подписчики 239.1.1.1".
Через PIM (Protocol Independent Multicast) строит дерево доставки
между роутерами.

IGMP v2: join, leave, query. IGMP v3: source-specific multicast (SSM) -- "только от этого отправителя".

Use cases multicast

Video streaming в LAN (IPTV)

Один сервер шлёт 4K поток на 239.0.1.1. 1000 set-top-box'ов подписаны, каждый получает тот же поток, сеть несёт его один раз через основные линки.

1 Gbps stream, 1000 subscribers:
  Unicast:    1000 × 1 Gbps = 1 Tbps (невозможно)
  Multicast:  1 × 1 Gbps     = 1 Gbps (идеально)

Stock ticker

NASDAQ/NYSE раздают market data по multicast. Trading firms подписываются, latency минимальна, нагрузка на source константная.

Service discovery (mDNS/Bonjour)

Когда iPhone печатает на принтере Brother без настройки DNS -- это mDNS. Запросы идут на 224.0.0.251:5353, все mDNS-клиенты в LAN слушают и отвечают.

# Посмотреть mDNS-устройства в сети
avahi-browse -a
# +   eth0 IPv4 HP LaserJet Pro M15w            _ipp._tcp            local
# +   eth0 IPv4 Apple TV                        _airplay._tcp        local
# +   eth0 IPv4 Home Assistant                  _home-assistant._tcp local

# Resolve .local имя
avahi-resolve -n my-printer.local
# my-printer.local  192.168.1.77

Cluster discovery

Kafka, Cassandra, Consul могут использовать multicast для bootstrap discovery внутри DC (хотя cloud-native предпочитают явные seed lists, потому что AWS/GCP режут multicast).

Router protocols

OSPF использует 224.0.0.5 (all OSPF), 224.0.0.6 (DR/BDR). RIPv2 -- 224.0.0.9. Внутренняя магия интернета работает на multicast.

Проблемы multicast в проде

  1. Не работает в публичном интернете -- провайдеры не распространяют multicast между AS. Только в пределах LAN или управляемых VPN.
  2. AWS/GCP VPC не поддерживают IP multicast напрямую (AWS есть Transit Gateway multicast, но ограниченно).
  3. IGMP snooping обязателен -- без него коммутатор флудит multicast на все порты (превращается в broadcast).
  4. Сложная отладка -- нужны специализированные инструменты (mtrace, smcroute).

Anycast

Тот же IP анонсирован из многих мест, ближайший (по BGP) обрабатывает запрос. Детально разбирали в главе про BGP.

Краткое напоминание:

  • Работает на IP-уровне, через routing protocol (BGP).
  • Не требует специальной поддержки клиента -- обычный IP.
  • Идеален для stateless протоколов (DNS over UDP, HTTP/QUIC).
  • Stateful TCP может мигрировать между POP -- проблема.

IPv6 отказался от broadcast

В IPv4 broadcast использовался для:

  1. ARP -- найти MAC по IP.
  2. DHCP discovery.
  3. Router discovery.

В IPv6 эти задачи решены через multicast с well-known адресами:

IPv4 broadcast task IPv6 multicast equivalent
ARP Neighbor Discovery (NDP) на ff02::1:ffxx:xxxx (solicited-node)
DHCP DHCPv6 на ff02::1:2 (all DHCP servers)
Router advertisement ff02::2 (all routers)
All hosts ff02::1 (all nodes)

Преимущество: пакет приходит только тем, кому нужно (не все хосты обязаны обрабатывать).

Сравнение всех четырёх

Свойство Unicast Broadcast Multicast Anycast
Получатели 1 Все в LAN Подписчики группы Ближайший из группы
Scope Global LAN only LAN или internet (PIM) Global via BGP
IP range (v4) Normal 255.255.255.255, x.x.x.255 224.0.0.0/4 Normal (anycast через routing)
IP range (v6) Normal нет ff00::/8 Normal
Требует подписки Нет Нет Да (IGMP) Нет
Bandwidth efficiency Низкая для N клиентов Высокая в LAN Высокая, распределена Высокая (клиент идёт в ближайший)
Router behavior Forward Drop (обычно) Build distribution tree (PIM) Normal unicast forwarding
Использование HTTP, SSH, DB ARP, DHCP, WoL IPTV, stock, mDNS DNS, CDN

Практика: UDP multicast в Go

Multicast на UDP: отправитель шлёт на группу, получатели слушают группу. Никакого handshake, никаких гарантий.

Receiver (подписчик)

package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

// MulticastReceiver joins a group and reads datagrams.
type MulticastReceiver struct {
	group  *net.UDPAddr
	conn   *net.UDPConn
	bufLen int
}

// NewMulticastReceiver creates a listener for the given group and port.
// The interface is chosen by OS unless explicitly set via ListenMulticastUDP.
func NewMulticastReceiver(groupAddr string) (*MulticastReceiver, error) {
	addr, err := net.ResolveUDPAddr("udp4", groupAddr)
	if err != nil {
		return nil, fmt.Errorf("resolve: %w", err)
	}

	// Bind on all interfaces for the group
	conn, err := net.ListenMulticastUDP("udp4", nil, addr)
	if err != nil {
		return nil, fmt.Errorf("listen: %w", err)
	}

	// Larger read buffer helps with bursts
	if err := conn.SetReadBuffer(1 << 20); err != nil {
		log.Printf("SetReadBuffer: %v", err)
	}

	return &MulticastReceiver{
		group:  addr,
		conn:   conn,
		bufLen: 2048,
	}, nil
}

// Receive loops and calls handler for each datagram until ctx is cancelled.
func (r *MulticastReceiver) Receive(handler func(src *net.UDPAddr, data []byte)) error {
	buf := make([]byte, r.bufLen)

	for {
		_ = r.conn.SetReadDeadline(time.Now().Add(5 * time.Second))

		n, src, err := r.conn.ReadFromUDP(buf)
		if err != nil {
			// Timeout is expected, continue
			if ne, ok := err.(net.Error); ok && ne.Timeout() {
				continue
			}
			return fmt.Errorf("read: %w", err)
		}

		// Copy since buf will be reused
		data := make([]byte, n)
		copy(data, buf[:n])
		handler(src, data)
	}
}

func (r *MulticastReceiver) Close() error {
	return r.conn.Close()
}

func main() {
	// 239.1.1.1 is administratively-scoped private multicast
	rec, err := NewMulticastReceiver("239.1.1.1:9999")
	if err != nil {
		log.Fatal(err)
	}
	defer rec.Close()

	log.Println("Listening on 239.1.1.1:9999")

	if err := rec.Receive(func(src *net.UDPAddr, data []byte) {
		log.Printf("from %s: %s", src, string(data))
	}); err != nil {
		log.Fatal(err)
	}
}

Sender (издатель)

package main

import (
	"fmt"
	"log"
	"net"
	"time"
)

// MulticastSender broadcasts datagrams to a group.
type MulticastSender struct {
	conn   *net.UDPConn
	target *net.UDPAddr
}

// NewMulticastSender opens a UDP connection to the group.
// TTL controls how many router hops the packet can cross.
func NewMulticastSender(groupAddr string, ttl int) (*MulticastSender, error) {
	addr, err := net.ResolveUDPAddr("udp4", groupAddr)
	if err != nil {
		return nil, fmt.Errorf("resolve: %w", err)
	}

	conn, err := net.DialUDP("udp4", nil, addr)
	if err != nil {
		return nil, fmt.Errorf("dial: %w", err)
	}

	// Default multicast TTL is 1 (link-local only).
	// For cross-router multicast set explicit TTL.
	// Requires golang.org/x/net/ipv4 for portable IP_MULTICAST_TTL.
	// Here we use raw syscall hint via connection -- see ipv4.NewPacketConn for full control.

	return &MulticastSender{
		conn:   conn,
		target: addr,
	}, nil
}

func (s *MulticastSender) Send(data []byte) error {
	_, err := s.conn.Write(data)
	return err
}

func (s *MulticastSender) Close() error {
	return s.conn.Close()
}

func main() {
	sender, err := NewMulticastSender("239.1.1.1:9999", 1)
	if err != nil {
		log.Fatal(err)
	}
	defer sender.Close()

	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	counter := 0
	for range ticker.C {
		msg := fmt.Sprintf("tick #%d @ %s", counter, time.Now().Format(time.RFC3339))
		if err := sender.Send([]byte(msg)); err != nil {
			log.Printf("send: %v", err)
			continue
		}
		log.Printf("sent: %s", msg)
		counter++
	}
}

Запуск и проверка

# Терминал 1 (receiver)
go run receiver.go
# 2026/04/18 10:00:01 Listening on 239.1.1.1:9999

# Терминал 2 (sender)
go run sender.go
# 2026/04/18 10:00:02 sent: tick #0 @ ...

# Терминал 3 (второй receiver -- оба получают тот же поток)
go run receiver.go

# Посмотреть multicast-трафик tcpdump'ом
sudo tcpdump -i any -nn 'host 239.1.1.1'
# 10:00:02 IP 192.168.1.10.51234 > 239.1.1.1.9999: UDP, length 42

Нюансы multicast в Linux

# Проверить, включён ли multicast forwarding
sysctl net.ipv4.conf.all.mc_forwarding
# net.ipv4.conf.all.mc_forwarding = 0

# Посмотреть multicast-группы, в которых состоит интерфейс
ip maddr show dev eth0
# 1: eth0
#   inet  224.0.0.1  (all hosts)
#   inet  224.0.0.251 (mDNS, если avahi бежит)

# Посмотреть IGMP-трафик
sudo tcpdump -i eth0 -nn 'igmp'

# Отладка multicast routing (требует smcrouted)
ip mroute show

Выводы

  • Четыре модели доставки: unicast (1→1), broadcast (1→all в LAN), multicast (1→группа подписчиков), anycast (1→ближайший).
  • Broadcast только в IPv4 и только в LAN (ARP, DHCP). IPv6 заменил его link-local multicast.
  • Multicast экономит bandwidth для 1-ко-многим: один поток IPTV на 1000 клиентов вместо 1000 unicast-копий.
  • IGMP (v4) / MLD (v6) -- протоколы подписки на группу; IGMP snooping на коммутаторах обязателен, иначе multicast = broadcast.
  • Private multicast: 239.0.0.0/8 для site-локальных задач, 224.0.0.0/24 для link-local control (ARP-аналоги, OSPF).
  • mDNS (224.0.0.251:5353) -- zero-configuration service discovery в LAN (.local имена).
  • В публичном интернете multicast не работает -- провайдеры не форвардят между AS.
  • Anycast -- routing-based альтернатива: один IP, много POP, BGP выбирает ближайший.
  • Go net.ListenMulticastUDP + net.DialUDP покрывают 95% multicast-сценариев без внешних библиотек.