wip: unified ipv6 socket for ipv4 and ipv6 traffic
This commit is contained in:
parent
2c50a98e63
commit
c778de9bf9
267
Cargo.lock
generated
267
Cargo.lock
generated
|
@ -2,6 +2,17 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.2"
|
||||
|
@ -11,6 +22,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -47,6 +64,73 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset 0.9.0",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dns-parser"
|
||||
version = "0.8.0"
|
||||
|
@ -57,6 +141,19 @@ dependencies = [
|
|||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endorphin"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2dc87c62efee2c20e9287b6421800c02b56551b3cc5ee9af1ac9336e8173ebd"
|
||||
dependencies = [
|
||||
"crossbeam",
|
||||
"hashbrown 0.11.2",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"slotmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.0"
|
||||
|
@ -125,6 +222,26 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
|
@ -150,7 +267,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -176,6 +302,16 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.19"
|
||||
|
@ -189,16 +325,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "multicast-socket"
|
||||
version = "0.2.2"
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53eacba0998466687d051439bf144286203d7286623275a8007505d22fd21cfb"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
dependencies = [
|
||||
"get_if_addrs",
|
||||
"libc",
|
||||
"nix",
|
||||
"socket2",
|
||||
"winapi 0.3.9",
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -206,27 +347,68 @@ name = "multicaster"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dns-parser",
|
||||
"endorphin",
|
||||
"env_logger",
|
||||
"get_if_addrs",
|
||||
"libc",
|
||||
"log",
|
||||
"multicast-socket",
|
||||
"nix",
|
||||
"serde",
|
||||
"socket2",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.19.1"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ccba0cfe4fdf15982d1674c69b1fd80bad427d293849982668dfe454bd61f2"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset 0.7.1",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
||||
dependencies = [
|
||||
"instant",
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"instant",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.66"
|
||||
|
@ -251,6 +433,15 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.1"
|
||||
|
@ -293,6 +484,12 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.177"
|
||||
|
@ -323,16 +520,36 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
name = "slotmap"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
|
||||
checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.27"
|
||||
|
@ -393,6 +610,18 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
|
|
|
@ -5,10 +5,12 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
dns-parser = "0.8.0"
|
||||
endorphin = "0.1.9"
|
||||
env_logger = "0.10.0"
|
||||
get_if_addrs = "0.5.3"
|
||||
libc = "0.2.147"
|
||||
log = "0.4.18"
|
||||
multicast-socket = "0.2.2"
|
||||
nix = { version = "0.26.2", features = ["net"] }
|
||||
serde = { version = "1.0.163", features = ["derive"] }
|
||||
socket2 = { version = "0.5.3", features = ["all"] }
|
||||
toml = "0.7.4"
|
||||
|
|
13
README.md
13
README.md
|
@ -6,7 +6,7 @@ It'll allow you to be very specific about the exact traffic that is sent over.
|
|||
|
||||
# Working Notes
|
||||
|
||||
1. It needs to listen on the specified port to receive multicast traffic.
|
||||
* It needs to listen on the specified port to receive multicast traffic.
|
||||
This causes problems if there are other softwares that are also listening without using `SO_REUSE_ADDR`.
|
||||
|
||||
For now, Disable those softwares when running this. A list of such softwares,
|
||||
|
@ -14,14 +14,14 @@ For now, Disable those softwares when running this. A list of such softwares,
|
|||
a. avahi-daemon
|
||||
|
||||
|
||||
* Multicast DNS RFC https://datatracker.ietf.org/doc/html/rfc6762
|
||||
|
||||
|
||||
### How should this be designed??
|
||||
|
||||
|
||||
For now, I am restricting it to only consider 1 config.
|
||||
It won't listen on multiple ports for multicast traffic. This will be changed once I have the basic structure ready.
|
||||
|
||||
|
||||
For now, Only work on IPv4. IPv6 will be added once IPv4 is ready
|
||||
|
||||
|
||||
|
||||
|
@ -35,8 +35,3 @@ For now, Only work on IPv4. IPv6 will be added once IPv4 is ready
|
|||
|
||||
4. A DNS answer should not be forwarded from destination to source in any circumstances. This is not enforced right now.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ pub mod config;
|
|||
pub use config::*;
|
||||
pub mod mdns;
|
||||
pub use mdns::*;
|
||||
mod socket;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
|
51
src/mdns.rs
51
src/mdns.rs
|
@ -1,7 +1,11 @@
|
|||
use crate::socket::{
|
||||
Interface as MulticastInterface, MulticastGroup, MulticastOptions, MulticastSocket,
|
||||
};
|
||||
use crate::Config;
|
||||
use dns_parser::Packet;
|
||||
use log::{info, trace, warn};
|
||||
use multicast_socket::{Interface as MulticastInterface, MulticastOptions, MulticastSocket};
|
||||
use nix::errno::Errno;
|
||||
use std::net::{Ipv4Addr, SocketAddrV6};
|
||||
use std::{ffi::CString, net::SocketAddrV4};
|
||||
|
||||
pub struct Mdns {
|
||||
|
@ -12,15 +16,12 @@ pub struct Mdns {
|
|||
impl Mdns {
|
||||
pub fn new(config: Config) -> Self {
|
||||
// mdns
|
||||
let mdns_address = SocketAddrV4::new([224, 0, 0, 251].into(), 5353);
|
||||
let multicast_socket = MulticastSocket::with_options(
|
||||
mdns_address,
|
||||
// TODO(ishan): Listen on ALL Interfaces, including ipv6
|
||||
multicast_socket::all_ipv4_interfaces().expect("could not fetch all interfaces"),
|
||||
MulticastOptions {
|
||||
loopback: false,
|
||||
buffer_size: 4096,
|
||||
..Default::default()
|
||||
let multicast_socket = MulticastSocket::new(
|
||||
MulticastOptions::default(),
|
||||
MulticastSocket::all_interfaces().unwrap(),
|
||||
MulticastGroup {
|
||||
ipv4: Some(SocketAddrV4::new(Ipv4Addr::new(224, 0, 0, 251), 5353)),
|
||||
port: 5353,
|
||||
},
|
||||
)
|
||||
.expect("error in creating multicast socket");
|
||||
|
@ -37,7 +38,14 @@ impl Mdns {
|
|||
loop {
|
||||
match self.socket.receive() {
|
||||
Ok(msg) => self.process_packet(msg),
|
||||
Err(e) if e.to_string().contains("EAGAIN") => continue,
|
||||
Err(e)
|
||||
if e.get_ref().map_or(false, |e| {
|
||||
e.downcast_ref::<nix::Error>()
|
||||
.is_some_and(|c| *c == Errno::EAGAIN)
|
||||
}) =>
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("error in reading from socket {:?} ", e);
|
||||
}
|
||||
|
@ -45,9 +53,16 @@ impl Mdns {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn process_packet(&self, msg: multicast_socket::Message) {
|
||||
pub fn process_packet(&self, msg: crate::socket::Message) {
|
||||
// TODO: Generalize this to parse any type of supported packet
|
||||
let packet = Packet::parse(&msg.data).expect("failed to parse packet as a dns packet");
|
||||
let packet = Packet::parse(&msg.data).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"failed to parse packet as a dns packet: {:?} error = {:?}, loose_string = {}",
|
||||
msg,
|
||||
e,
|
||||
String::from_utf8_lossy(&msg.data)
|
||||
)
|
||||
});
|
||||
|
||||
let src_ifname = if let MulticastInterface::Index(idx) = msg.interface {
|
||||
ifidx_to_ifname(idx as u32)
|
||||
|
@ -56,12 +71,12 @@ impl Mdns {
|
|||
};
|
||||
|
||||
trace!(
|
||||
"EVENT src-if = {} if-index {:?} address = {}, packet: {:?} answers = {:?}",
|
||||
"EVENT src-if = {} if-index {:?} address = {:?}, packet: {:?} answers = {:?}",
|
||||
src_ifname,
|
||||
msg.interface,
|
||||
msg.origin_address,
|
||||
packet.questions.iter().map(|q| q.qname).collect::<Vec<_>>(),
|
||||
packet.answers.iter().map(|q| q.name).collect::<Vec<_>>()
|
||||
packet.questions.iter().collect::<Vec<_>>(),
|
||||
packet.answers.iter().collect::<Vec<_>>()
|
||||
);
|
||||
|
||||
let interfaces = get_if_addrs::get_if_addrs().unwrap();
|
||||
|
@ -97,8 +112,8 @@ impl Mdns {
|
|||
let dst_ifid = ifname_to_ifidx(dst_if.name.to_string());
|
||||
|
||||
info!(
|
||||
"forwarding packet questions {:?} answers = {:?} from {} to {}",
|
||||
packet.questions, packet.answers, src_ifname, dst_if.name
|
||||
"forwarding packet packet {:?} from {} to {}",
|
||||
packet, src_ifname, dst_if.name
|
||||
);
|
||||
// TODO(ishan): Take a note of transaction id
|
||||
// and avoid feedback loops
|
||||
|
|
265
src/socket.rs
Normal file
265
src/socket.rs
Normal file
|
@ -0,0 +1,265 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use log::trace;
|
||||
// This code has been adapted from multicast_socket crate
|
||||
use nix::sys::socket::{self as sock, AddressFamily, SockaddrIn, SockaddrLike, SockaddrStorage};
|
||||
use serde::de::value;
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::Hash,
|
||||
io::{self, IoSlice, IoSliceMut, Result as IoResult},
|
||||
mem,
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
|
||||
os::unix::io::AsRawFd,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
pub struct MulticastOptions {
|
||||
pub read_timeout: Duration,
|
||||
pub buffer_size: usize,
|
||||
}
|
||||
|
||||
impl Default for MulticastOptions {
|
||||
fn default() -> Self {
|
||||
MulticastOptions {
|
||||
read_timeout: Duration::from_secs(1),
|
||||
buffer_size: 512,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MulticastSocket {
|
||||
socket: socket2::Socket,
|
||||
interfaces: HashMap<String, Vec<IpAddr>>,
|
||||
multicast_group: MulticastGroup,
|
||||
buffer_size: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MulticastGroup {
|
||||
pub ipv4: SocketAddrV4,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl MulticastSocket {
|
||||
pub fn new(
|
||||
options: MulticastOptions,
|
||||
interfaces: HashMap<String, Vec<IpAddr>>,
|
||||
multicast_group: MulticastGroup,
|
||||
) -> Result<Self, std::io::Error> {
|
||||
// We want to accept traffic on IPv4 and IPv6.
|
||||
// and I don't want to deal with 2 sockets. 1 for ipv4 and 1 for ipv6
|
||||
// so we do this
|
||||
// Create a single Ipv6 socket and disable IPV6_ONLY option
|
||||
// This is already disabled on new OSes but just to be safe we do it any way
|
||||
// With this, We can accept IPv6 traffic _and_ we can accept IPv4 traffic
|
||||
// except the address for IPv4 will be presented within IPv6
|
||||
|
||||
let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
|
||||
socket.set_read_timeout(Some(options.read_timeout))?;
|
||||
socket.set_multicast_loop_v4(false)?;
|
||||
socket.set_reuse_address(true)?;
|
||||
socket.set_reuse_port(true)?;
|
||||
|
||||
// Ipv4PacketInfo translates to `IP_PKTINFO`. Checkout the [ip
|
||||
// manpage](https://man7.org/linux/man-pages/man7/ip.7.html) for more details. In summary
|
||||
// setting this option allows for determining on which interface a packet was received.
|
||||
sock::setsockopt(socket.as_raw_fd(), sock::sockopt::Ipv4PacketInfo, &true)
|
||||
.map_err(nix_to_io_error)?;
|
||||
sock::setsockopt(socket.as_raw_fd(), sock::sockopt::Ipv6RecvPacketInfo, &true)
|
||||
.map_err(nix_to_io_error)?;
|
||||
|
||||
// Receive IPv4 traffic on IPv6 socket
|
||||
sock::setsockopt(socket.as_raw_fd(), sock::sockopt::Ipv6V6Only, &false)
|
||||
.map_err(nix_to_io_error);
|
||||
|
||||
for (if_name, addresses) in interfaces.iter() {
|
||||
trace!(
|
||||
"joining groups if_name = {} addresses = {:?}",
|
||||
if_name,
|
||||
addresses
|
||||
);
|
||||
|
||||
if addresses.iter().any(|addr| addr.is_ipv6()) {
|
||||
trace!(
|
||||
"joined ipv6 multicast group {} if_name {}",
|
||||
multicast_group.ipv6.ip(),
|
||||
if_name
|
||||
);
|
||||
|
||||
socket.join_multicast_v6(
|
||||
multicast_group.ipv6.ip(),
|
||||
ifname_to_ifidx(if_name.to_string()),
|
||||
);
|
||||
}
|
||||
for address in addresses {
|
||||
if let IpAddr::V4(v4_addr) = address {
|
||||
if v4_addr.is_loopback() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(ipv4_mdns_group) = multicast_group.ipv4 {
|
||||
trace!(
|
||||
"joined ipv4 multicast group {} {}",
|
||||
ipv4_mdns_group.ip(),
|
||||
v4_addr
|
||||
);
|
||||
socket.join_multicast_v4(ipv4_mdns_group.ip(), v4_addr)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
socket.bind(
|
||||
&SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), multicast_group.port).into(),
|
||||
)?;
|
||||
|
||||
Ok(MulticastSocket {
|
||||
socket,
|
||||
interfaces,
|
||||
buffer_size: options.buffer_size,
|
||||
multicast_group,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn all_interfaces() -> IoResult<HashMap<String, Vec<IpAddr>>> {
|
||||
let interfaces = get_if_addrs::get_if_addrs()?.into_iter();
|
||||
// We have to filter the same interface if it has multiple ips
|
||||
// https://stackoverflow.com/questions/49819010/ip-add-membership-fails-when-set-both-on-interface-and-its-subinterface-is-that
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for interface in interfaces {
|
||||
map.entry(interface.name.clone())
|
||||
.or_insert_with(Vec::new)
|
||||
.push(interface.ip());
|
||||
}
|
||||
|
||||
// TODO: remove loopback?
|
||||
map.remove("lo");
|
||||
|
||||
Ok(map)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Message {
|
||||
pub data: Vec<u8>,
|
||||
pub origin_address: Option<SocketAddr>,
|
||||
pub interface: Interface,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Interface {
|
||||
Default,
|
||||
Index(i32),
|
||||
IpAddr(Ipv6Addr),
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn ifname_to_ifidx(name: String) -> u32 {
|
||||
let out = name.as_ptr() as *const _;
|
||||
unsafe { libc::if_nametoindex(out) }
|
||||
}
|
||||
|
||||
fn nix_to_io_error(e: nix::Error) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::Other, e)
|
||||
}
|
||||
|
||||
impl MulticastSocket {
|
||||
pub fn receive(&self) -> IoResult<Message> {
|
||||
let mut data_buffer = vec![0; self.buffer_size];
|
||||
let mut control_buffer = nix::cmsg_space!(libc::in6_pktinfo, libc::in_pktinfo);
|
||||
|
||||
let (origin_address, interface, bytes_read) = {
|
||||
let message = sock::recvmsg(
|
||||
self.socket.as_raw_fd(),
|
||||
&mut [IoSliceMut::new(&mut data_buffer)],
|
||||
Some(&mut control_buffer),
|
||||
sock::MsgFlags::empty(),
|
||||
)
|
||||
.map_err(nix_to_io_error)?;
|
||||
|
||||
let origin_address = match message.address {
|
||||
//v4 @ Some(SockaddrIn) => v4,
|
||||
Some(sock::SockAddr::Inet(inet)) => Some(inet.to_std()),
|
||||
_ => None,
|
||||
};
|
||||
// let origin_address = match origin_address {
|
||||
// Some(SocketAddr::V6(v6)) => v6,
|
||||
// _ => SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0),
|
||||
// };
|
||||
|
||||
println!("{:?}", origin_address);
|
||||
let mut interface = Interface::Default;
|
||||
|
||||
for cmsg in message.cmsgs() {
|
||||
if let sock::ControlMessageOwned::Ipv6PacketInfo(pktinfo) = cmsg {
|
||||
interface = Interface::Index(pktinfo.ipi6_ifindex as _);
|
||||
trace!("control packet ipv6: {:?}", pktinfo);
|
||||
}
|
||||
if let sock::ControlMessageOwned::Ipv4PacketInfo(pktinfo) = cmsg {
|
||||
interface = Interface::Index(pktinfo.ipi_ifindex as _);
|
||||
|
||||
trace!("control packet ipv4: {:?}", pktinfo);
|
||||
}
|
||||
}
|
||||
|
||||
(origin_address, interface, message.bytes)
|
||||
};
|
||||
|
||||
Ok(Message {
|
||||
data: data_buffer[0..bytes_read].to_vec(),
|
||||
origin_address,
|
||||
interface,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&self, buf: &[u8], interface: &Interface) -> io::Result<usize> {
|
||||
Ok(0)
|
||||
// match interface {
|
||||
// Interface::Default => todo!(),
|
||||
// Interface::Index(index) => {
|
||||
// // TODO: Send over ipv4 and ipv6
|
||||
// pkt_info.ipi_ifindex = *index as _;
|
||||
// }
|
||||
// Interface::Ip(IpAddr::V4(v4)) => {
|
||||
// let mut pkt_info: libc::in_pktinfo = unsafe { mem::zeroed() };
|
||||
// pkt_info.ipi_spec_dst = libc::in_addr {
|
||||
// s_addr: (*v4).into(),
|
||||
// };
|
||||
// }
|
||||
// Interface::Ip(IpAddr::V6(v6)) => {
|
||||
// let mut pkt_info: libc::in6_pktinfo = unsafe { mem::zeroed() };
|
||||
// }
|
||||
// }
|
||||
|
||||
// match interface {
|
||||
// Interface::Default => {}
|
||||
// Interface::Ipv6(address) => {}
|
||||
// Interface::Ipv4(address) => {
|
||||
// pkt_info.ipi_spec_dst = libc::in_addr {
|
||||
// s_addr: (*address).into(),
|
||||
// }
|
||||
// }
|
||||
|
||||
// Interface::Index(index) => pkt_info.ipi_ifindex = *index as _,
|
||||
// };
|
||||
|
||||
// let destination = SockaddrIn::from(self.multicast_group);
|
||||
|
||||
// sock::sendmsg(
|
||||
// self.socket.as_raw_fd(),
|
||||
// &[IoSlice::new(buf)],
|
||||
// &[sock::ControlMessage::Ipv4PacketInfo(&pkt_info)],
|
||||
// sock::MsgFlags::empty(),
|
||||
// Some(&destination),
|
||||
// )
|
||||
// .map_err(nix_to_io_error)
|
||||
}
|
||||
}
|
||||
|
||||
fn print_type_of<T>(_: &T) {
|
||||
println!("{}", std::any::type_name::<T>())
|
||||
}
|
Loading…
Reference in New Issue
Block a user