Sending UDP Broadcasts with Expo and Verifying with socat


I needed to test UDP broadcasting from a React Native (Expo) app running on a mobile device over LAN. Here’s a quick summary of how I did it, from preview setup to packet verification using socat.

Required Dependencies

These are the libraries I used:

  • expo-network – for getting the device’s local IP address. This is not required for udp broadcast
  • react-native-udp – to send UDP packets

Configure metro.config.js

Because react-native-udp is a Node.js core module polyfill, you need to update your Metro bundler config to support shimming:

metro.config.js
const { getDefaultConfig } = require("@expo/metro-config");

const config = getDefaultConfig(__dirname);

config.resolver.extraNodeModules = {
  ...config.resolver.extraNodeModules,
  dgram: require.resolve("react-native-udp"),
};

module.exports = config;
copied!

Create a Basic UDP Broadcast Sender

Here’s a minimal PocScreen.tsx that sends a UDP broadcast:

PocScreen.tsx
import React, { useEffect, useRef, useState } from "react";
import {
  Text,
  Button,
  ScrollView,
  StyleSheet,
  SafeAreaView,
} from "react-native";
import * as Network from "expo-network";
import dgram from "react-native-udp";

type UdpSocket = ReturnType<typeof dgram.createSocket>;

const PORT = 12345;

const getBroadcastAddress = (ip: string): string =>
  ip.split(".").slice(0, 3).join(".") + ".255";

const PocUdpScreen: React.FC = () => {
  const [ip, setIp] = useState<string | null>(null);
  const [broadcastIp, setBroadcastIp] = useState<string>("192.168.0.255");
  const [messages, setMessages] = useState<string[]>([]);
  const socketRef = useRef<UdpSocket | null>(null);
  const ipRef = useRef<string | null>(null);

  useEffect(() => {
    let isMounted = true;

    (async () => {
      const ipAddr = await Network.getIpAddressAsync();
      if (!isMounted) {
        return;
      }

      setIp(ipAddr);
      ipRef.current = ipAddr;
      setBroadcastIp(getBroadcastAddress(ipAddr));
    })();

    const socket = dgram.createSocket({ type: "udp4" });
    socketRef.current = socket;

    socket.bind(PORT);
    socket.once("listening", () => {
      socket.setBroadcast(true);
    });

    socket.on("message", (msg, rinfo) => {
      const text = new TextDecoder().decode(msg);
      const from =
        rinfo.address === ipRef.current
          ? `${rinfo.address} (you)`
          : rinfo.address;
      setMessages((prev) => [...prev, `${from}: ${text}`]);
    });

    return () => {
      isMounted = false;
      socket.close();
    };
  }, []);

  const handleSend = () => {
    if (!ip || !socketRef.current) {
      return;
    }

    const message = `📡 Hello from ${ip} at ${new Date().toISOString()}`;
    const buffer = new TextEncoder().encode(message);

    socketRef.current.send(
      buffer,
      0,
      buffer.length,
      PORT,
      broadcastIp,
      (err) => {
        if (err) {
          console.error("UDP send error:", err);
        } else {
          console.log(`✅ Sent to ${broadcastIp}:${PORT}`);
        }
      }
    );
  };

  return (
    <SafeAreaView style={styles.container}>
      <Text style={styles.title}>📡 UDP Broadcast PoC</Text>
      <Text>Your IP: {ip ?? "Loading..."}</Text>
      <Text>
        Broadcast: {broadcastIp}:{PORT}
      </Text>
      <Button title="Send UDP" onPress={handleSend} />

      <Text style={styles.subtitle}>Received Messages</Text>
      <ScrollView style={styles.scroll}>
        {messages.map((msg, i) => (
          <Text key={i} style={styles.message}>
            {msg}
          </Text>
        ))}
      </ScrollView>
    </SafeAreaView>
  );
};

export default PocUdpScreen;

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  title: { fontSize: 20, fontWeight: "bold", marginBottom: 8 },
  subtitle: { fontSize: 16, marginTop: 20, fontWeight: "600" },
  scroll: { marginTop: 10, maxHeight: 300 },
  message: { marginBottom: 6, fontSize: 14 },
});
copied!

To test this screen, I deployed it as a preview build on my phone using expo run:android. It worked well on a real device.

Install socat on macOS

To verify the packets on your Mac, install socat:

brew install socat
copied!

Start Listening for UDP Packets

In a terminal window:

socat -v -u UDP-RECV:12345 STDOUT
copied!

I successfully received outputs

📡 Hello from 192.168.0.16 at 2025-06-25T22:58:05.880Z
📡 Hello from 192.168.0.16 at 2025-06-25T22:58:08.460Z
copied!

Summary

  • UDP broadcasting from Expo works with react-native-udp
  • socat successfully receives and prints UDP messages in real time
buy me a coffee