Last active
September 23, 2025 14:39
-
-
Save craighewetson/42d311cd41075aec193a8ecb939039b1 to your computer and use it in GitHub Desktop.
UDP Multicast example in odin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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