Skip to content

Instantly share code, notes, and snippets.

@craighewetson
Last active September 23, 2025 14:39
Show Gist options
  • Select an option

  • Save craighewetson/42d311cd41075aec193a8ecb939039b1 to your computer and use it in GitHub Desktop.

Select an option

Save craighewetson/42d311cd41075aec193a8ecb939039b1 to your computer and use it in GitHub Desktop.
UDP Multicast example in odin
package main
import "core:fmt"
import "core:net"
import os "core:os/os2"
import "core:reflect"
import "core:time"
import "core:c"
import "core:sys/linux"
foreign import libc "system:c"
IPPROTO_IP :: 0 // IP protocol level
IP_ADD_MEMBERSHIP :: 35 // Option to join multicast group (value may vary by system)
IP_DROP_MEMBERSHIP :: 36
// Define the ip_mreq structure equivalent to C's struct ip_mreq
ip_mreq :: struct {
imr_multiaddr: in_addr, // Multicast group address
imr_interface: in_addr, // Local interface address
}
// Define in_addr structure for IPv4 addresses
in_addr :: struct {
s_addr: c.uint32_t, // 32-bit address in network byte order
}
foreign libc {
setsockopt :: proc(socket: c.int, level: c.int, optname: c.int, optval: rawptr, optlen: c.uint) -> c.int ---
}
main :: proc() {
// Multicast settings
mt4, mt6, multicast_err := net.resolve("239.255.0.1")
assert(multicast_err == nil)
fmt.assertf(mt4.address != nil, "no ip4 address")
multicast_ep := mt4
PORT :: 54321
if multicast_ep.port == 0 {
multicast_ep.port = PORT
}
if len(os.args) < 2 {
panic("not given either -receiver or -sender")
}
option_name := os.args[1]
if option_name[0] != '-' {
panic("first argument not -receiver or -sender")
}
option_name = option_name[1:]
choice, choice_ok := reflect.enum_from_name(enum {
receiver,
sender,
}, option_name)
if !choice_ok {
fmt.panicf("unrecognised choice '%q'", option_name)
}
switch choice {
case .sender:
sender(multicast_ep)
case .receiver:
receiver(multicast_ep)
}
}
sender :: proc(multicast_ep: net.Endpoint) {
// Create UDP socket
sock, err := net.make_unbound_udp_socket(.IP4)
if err != nil {
fmt.eprintln("Failed to create socket:", err)
return
}
defer net.close(sock)
message := "Hello World from Odin!"
for {
bytes_sent, _ := net.send_udp(sock, transmute([]u8)message, multicast_ep)
fmt.printf("Sent %d bytes\n", bytes_sent)
time.sleep(time.Second)
}
}
receiver :: proc(multicast_ep: net.Endpoint) {
multicast_group := Multicast_Group {
group_endpoint = multicast_ep,
interface_address = net.IP4_Any
}
socket, _ := net.make_bound_udp_socket(net.IP4_Any, multicast_ep.port)
defer net.close(socket)
join_multicast_group(socket, multicast_group)
defer leave_multicast_group(socket, multicast_group)
buf: [32]byte
fmt.println("Listening on port", multicast_ep.port)
for {
bytes_received, sender_endpoint, _ := net.recv_udp(socket, buf[:])
fmt.printf("Received %d bytes from %v: %s\n", bytes_received, sender_endpoint, string(buf[:bytes_received]))
}
}
@(private)
_to_address_bytes :: proc(address: net.Address) -> (s_addr: c.uint32_t, ok: bool) {
switch value in address {
case net.IP4_Address:
s_addr := c.uint32_t(
(u32(value[3]) << 24) | // value[3] = 1 (0x01) → 0x01000000
(u32(value[2]) << 16) | // value[2] = 0 (0x00) → 0x00000000
(u32(value[1]) << 8) | // value[1] = 255 (0xFF) → 0x0000FF00
(u32(value[0])) // value[0] = 239 (0xEF) → 0x000000EF
)
return s_addr, true
case net.IP6_Address:
panic("TODO implement ip6 address")
}
return 0, false
}
@(private)
_to_ip_mreq :: proc(group: Multicast_Group) -> ip_mreq {
mreq: ip_mreq
if addr, ok := _to_address_bytes(group.group_endpoint.address); ok {
mreq.imr_multiaddr.s_addr = addr
} else {
panic("Failed: not an IPv4 address")
}
if addr, ok := _to_address_bytes(group.interface_address); ok {
mreq.imr_interface.s_addr = addr
} else {
panic("Error: Failed to convert interface address")
}
return mreq
}
join_multicast_group :: proc(socket: net.UDP_Socket, group: Multicast_Group) {
mreq := _to_ip_mreq(group)
if res := setsockopt((cast(c.int)unwrap_os_socket(socket)), IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, size_of(ip_mreq)); res < 0 {
panic("Error: Failed to join multicast group")
}
}
leave_multicast_group :: proc(socket: net.UDP_Socket, group: Multicast_Group) {
mreq := _to_ip_mreq(group)
if res := setsockopt((cast(c.int)unwrap_os_socket(socket)), IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, size_of(ip_mreq)); res < 0 {
panic("Error: Failed to leave multicast group")
}
}
Multicast_Group :: struct {
// The multicast address of this group on the network.
group_endpoint: net.Endpoint,
// The local IP address of the network interface that the group is associated with.
// NOTE: The 'Any' address -can- be used here.
interface_address: net.Address,
}
unwrap_os_socket :: proc "contextless" (sock: net.Any_Socket) -> linux.Fd {
return linux.Fd(net.any_socket_to_socket(sock))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment