vrchat_osc 2.0.0

vrchat_osc is a Rust crate designed to easily utilize VRChat's OSC (Open Sound Control) and OSCQuery protocols.
Documentation
# Known Issues in VRChat's OSC/mDNS Implementation and Workarounds in This Crate


This document describes known issues in VRChat's OSC/OSCQuery and mDNS implementation, and how the `vrchat_osc` crate works around them.

---

## Issue 1: Advertising 127.0.0.1 on All Interfaces


### Problem Details

In VRChat, OSC is bound to `0.0.0.0` (all interfaces), while OSCQuery is bound to `127.0.0.1` (loopback). However, the IP address advertised via mDNS is `127.0.0.1` for all interfaces, regardless of which interface the service is actually bound to.

This results in VRChat's services being discoverable via mDNS, even though they are unreachable from external networks.

### Workaround in This Crate


[`VRChatOSC::new()`](/src/lib.rs#L135-L192) accepts an `osc_ip` parameter (VRChat's IP address) and uses the [`find_local_ip_for_destination`](/src/lib.rs#L108-L123) function to determine the local interface IP that can reach that destination.

```rust
// Let the OS routing table determine the appropriate interface
fn find_local_ip_for_destination(dest_ip: IpAddr) -> Result<IpAddr, Error> {
    if dest_ip.is_loopback() {
        return Ok(dest_ip);
    }
    let socket = match dest_ip { ... };
    socket.connect((dest_ip, 0))?;
    Ok(socket.local_addr()?.ip())
}
```

This `advertised_ip` is passed to the [mDNS module](/src/mdns.rs#L150) to advertise the correct IP address in A/AAAA records.

---

## Issue 2: Duplicate Service Registration from First A Record


### Problem Details

VRChat registers the IP address from the first A record in an mDNS response as a service, regardless of the domain name. When mDNS responses are sent from multiple network interfaces, each interface's A record gets registered, causing duplicate service entries.

### Workaround in This Crate


The [`setup_multicast_socket`](/src/mdns/utils.rs#L58-L113) function joins the multicast group and sets the multicast transmission interface for **only** the single `advertised_ip` interface.

```rust
// For IPv4
socket.join_multicast_v4(&MDNS_IPV4_ADDR, &ipv4)?;
socket.set_multicast_if_v4(&ipv4)?;

// For IPv6
socket.join_multicast_v6(&MDNS_IPV6_ADDR, if_index)?;
socket.set_multicast_if_v6(if_index)?;
```

This ensures mDNS responses are sent from **only a single interface**, preventing duplicate service registration on the VRChat side.

---

## Issue 3: PTR Record Required in Answers Section


### Problem Details

VRChat does not recognize a service unless the mDNS response contains a PTR record in the **Answers section**. Standard mDNS implementations may place PTR records in the Additional section, but VRChat does not accept this.

### Workaround in This Crate


The [`create_mdns_response_message`](/src/mdns/utils.rs#L181-L234) function explicitly places the PTR record in the **Answers section**.

```rust
pub fn create_mdns_response_message(
    instance_name: &Name,
    interface_ip: IpAddr,
    port: u16,
) -> Message {
    let mut message = Message::new();
    // ...

    // Add PTR record to Answers section
    let service_type_name = instance_name.trim_to(3);
    message.add_answer(Record::from_rdata(
        service_type_name.clone(),
        RECORD_TTL,
        RData::PTR(PTR(instance_name.clone())),
    ));

    // SRV and A/AAAA records go in Additional section
    message.add_additional(Record::from_rdata(
        instance_name.clone(),
        RECORD_TTL,
        RData::SRV(SRV::new(0, 0, port, instance_name.clone())),
    ));
    // A/AAAA record...
    message
}
```

Similarly, the [`extract_service_info`](/src/mdns/utils.rs#L247-L346) function requires a PTR record when parsing responses to extract service information.