Skip to content

Commit a645716

Browse files
committed
Add IPv6 for ST utun
1 parent d680559 commit a645716

File tree

1 file changed

+202
-57
lines changed
  • talpid-core/src/split_tunnel/macos

1 file changed

+202
-57
lines changed

talpid-core/src/split_tunnel/macos/tun.rs

+202-57
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use pnet::{
1414
ethernet::{EtherTypes, MutableEthernetPacket},
1515
ip::IpNextHeaderProtocols,
1616
ipv4::MutableIpv4Packet,
17+
ipv6::MutableIpv6Packet,
1718
tcp::MutableTcpPacket,
1819
udp::MutableUdpPacket,
1920
MutablePacket, Packet,
@@ -436,7 +437,7 @@ async fn redirect_packets_for_pktap_stream(
436437
mut pktap_stream: PktapStream,
437438
default_interface: DefaultInterface,
438439
vpn_interface: Option<VpnInterface>,
439-
classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send + 'static>,
440+
mut classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send + 'static>,
440441
) -> Result<RedirectHandle, Error> {
441442
let default_dev = bpf::Bpf::open().map_err(Error::CreateDefaultBpf)?;
442443
let buffer_size = default_dev
@@ -478,6 +479,7 @@ async fn redirect_packets_for_pktap_stream(
478479
};
479480

480481
let vpn_v4 = vpn_interface.as_ref().and_then(|iface| iface.v4_address);
482+
let vpn_v6 = vpn_interface.as_ref().and_then(|iface| iface.v6_address);
481483

482484
let ingress_task: tokio::task::JoinHandle<(
483485
tokio::io::ReadHalf<tun::AsyncDevice>,
@@ -518,7 +520,7 @@ async fn redirect_packets_for_pktap_stream(
518520
let bpf_payload = &mut read_data[header.bh_hdrlen as usize
519521
..(header.bh_hdrlen as usize + header.bh_caplen as usize)];
520522

521-
handle_incoming_data(&mut tun_writer, bpf_payload, vpn_v4).await;
523+
handle_incoming_data(&mut tun_writer, bpf_payload, vpn_v4, vpn_v6).await;
522524

523525
if new_offset < read_data.len() {
524526
let read_len = read_data.len();
@@ -542,17 +544,9 @@ async fn redirect_packets_for_pktap_stream(
542544
});
543545

544546
let default_interface_clone = default_interface.clone();
547+
let vpn_interface_clone = vpn_interface.clone();
545548

546549
let classify_task = tokio::spawn(async move {
547-
let dest_mac = MacAddr::from(
548-
default_interface
549-
.v4_addrs
550-
.as_ref()
551-
.unwrap()
552-
.gateway_address
553-
.into_bytes(),
554-
);
555-
556550
loop {
557551
tokio::select! {
558552
packet = pktap_stream.next() => {
@@ -561,35 +555,13 @@ async fn redirect_packets_for_pktap_stream(
561555
Error::PktapStreamStopped
562556
})??;
563557

564-
match classify(&packet) {
565-
RoutingDecision::DefaultInterface => {
566-
packet.frame.set_destination(dest_mac);
567-
let mut ip = MutableIpv4Packet::new(packet.frame.payload_mut()).unwrap();
568-
569-
fix_ip_checksums(
570-
&mut ip,
571-
Some(default_interface.v4_addrs.as_ref().unwrap().source_ip),
572-
None,
573-
);
574-
if let Err(error) = default_write.write(packet.frame.packet()) {
575-
log::error!("Failed to forward to non-tun device: {error}");
576-
}
577-
}
578-
RoutingDecision::VpnTunnel => {
579-
let Some(mut ip) = MutableIpv4Packet::new(packet.frame.payload_mut()) else {
580-
continue;
581-
};
582-
if let Some(ref mut vpn_dev) = vpn_dev {
583-
fix_ip_checksums(&mut ip, vpn_v4, None);
584-
if let Err(error) = vpn_dev.write(packet.frame.payload()) {
585-
log::error!("Failed to forward to tun device: {error}");
586-
}
587-
}
588-
}
589-
RoutingDecision::Drop => {
590-
log::trace!("Dropped packet from pid {}", packet.header.pth_pid);
591-
}
592-
}
558+
let vpn_device = match (vpn_interface.as_ref(), vpn_dev.as_mut()) {
559+
(Some(interface), Some(device)) => Some((interface, device)),
560+
(None, None) => None,
561+
_ => unreachable!("missing tun interface or addresses"),
562+
};
563+
564+
classify = classify_and_send(classify, &mut packet, &default_interface, &mut default_write, vpn_device).await;
593565
}
594566
Ok(()) | Err(_) = egress_abort_rx.recv() => {
595567
log::debug!("stopping packet processing");
@@ -604,55 +576,187 @@ async fn redirect_packets_for_pktap_stream(
604576
ingress_task,
605577
classify_task,
606578
default_interface: default_interface_clone,
607-
vpn_interface,
579+
vpn_interface: vpn_interface_clone,
608580
})
609581
}
610582

583+
async fn classify_and_send(
584+
classify: Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send + 'static>,
585+
packet: &mut PktapPacket,
586+
default_interface: &DefaultInterface,
587+
default_write: &mut bpf::WriteHalf,
588+
vpn_interface: Option<(&VpnInterface, &mut bpf::Bpf)>,
589+
) -> Box<dyn Fn(&PktapPacket) -> RoutingDecision + Send + 'static> {
590+
match classify(&packet) {
591+
RoutingDecision::DefaultInterface => match packet.frame.get_ethertype() {
592+
EtherTypes::Ipv4 => {
593+
let Some(ref addrs) = default_interface.v4_addrs else {
594+
log::trace!("dropping IPv4 packet since there's no default route");
595+
return classify;
596+
};
597+
let gateway_address = MacAddr::from(addrs.gateway_address.into_bytes());
598+
packet.frame.set_destination(gateway_address);
599+
let Some(mut ip) = MutableIpv4Packet::new(packet.frame.payload_mut()) else {
600+
log::error!("dropping invalid IPv4 packet");
601+
return classify;
602+
};
603+
fix_ipv4_checksums(&mut ip, Some(addrs.source_ip), None);
604+
if let Err(error) = default_write.write(packet.frame.packet()) {
605+
log::error!("Failed to forward to default device: {error}");
606+
}
607+
}
608+
EtherTypes::Ipv6 => {
609+
let Some(ref addrs) = default_interface.v6_addrs else {
610+
log::trace!("dropping IPv6 packet since there's no default route");
611+
return classify;
612+
};
613+
let gateway_address = MacAddr::from(addrs.gateway_address.into_bytes());
614+
packet.frame.set_destination(gateway_address);
615+
let Some(mut ip) = MutableIpv6Packet::new(packet.frame.payload_mut()) else {
616+
log::error!("dropping invalid IPv6 packet");
617+
return classify;
618+
};
619+
fix_ipv6_checksums(&mut ip, Some(addrs.source_ip), None);
620+
if let Err(error) = default_write.write(packet.frame.packet()) {
621+
log::error!("Failed to forward to default device: {error}");
622+
}
623+
}
624+
other => log::error!("unknown ethertype: {other}"),
625+
},
626+
RoutingDecision::VpnTunnel => {
627+
let Some((vpn_interface, vpn_write)) = vpn_interface else {
628+
log::trace!("dropping IP packet since there's no tun route");
629+
return classify;
630+
};
631+
632+
match packet.frame.get_ethertype() {
633+
EtherTypes::Ipv4 => {
634+
let Some(addr) = vpn_interface.v4_address else {
635+
log::trace!("dropping IPv4 packet since there's no tun route");
636+
return classify;
637+
};
638+
let Some(mut ip) = MutableIpv4Packet::new(packet.frame.payload_mut()) else {
639+
log::error!("dropping invalid IPv4 packet");
640+
return classify;
641+
};
642+
fix_ipv4_checksums(&mut ip, Some(addr), None);
643+
if let Err(error) = vpn_write.write(packet.frame.payload()) {
644+
log::error!("Failed to forward to tun device: {error}");
645+
}
646+
}
647+
EtherTypes::Ipv6 => {
648+
let Some(addr) = vpn_interface.v6_address else {
649+
log::trace!("dropping IPv6 packet since there's no tun route");
650+
return classify;
651+
};
652+
let Some(mut ip) = MutableIpv6Packet::new(packet.frame.payload_mut()) else {
653+
log::error!("dropping invalid IPv6 packet");
654+
return classify;
655+
};
656+
fix_ipv6_checksums(&mut ip, Some(addr), None);
657+
if let Err(error) = vpn_write.write(packet.frame.payload()) {
658+
log::error!("Failed to forward to tun device: {error}");
659+
}
660+
}
661+
other => log::error!("unknown ethertype: {other}"),
662+
}
663+
}
664+
RoutingDecision::Drop => {
665+
log::trace!("Dropped packet from pid {}", packet.header.pth_pid);
666+
}
667+
}
668+
classify
669+
}
670+
611671
async fn handle_incoming_data(
612672
tun_writer: &mut tokio::io::WriteHalf<tun::AsyncDevice>,
613673
payload: &mut [u8],
614674
vpn_v4: Option<Ipv4Addr>,
675+
vpn_v6: Option<Ipv6Addr>,
615676
) {
616677
let Some(mut frame) = MutableEthernetPacket::new(payload) else {
617678
log::trace!("discarding non-Ethernet frame");
618679
return;
619680
};
620681

621-
if frame.get_ethertype() != EtherTypes::Ipv4 {
622-
log::trace!("discarding non-IPv4 frame");
623-
return;
682+
match frame.get_ethertype() {
683+
EtherTypes::Ipv4 => {
684+
let Some(vpn_addr) = vpn_v4 else {
685+
log::trace!("discarding incoming IPv4 packet: no tun V4 addr");
686+
return;
687+
};
688+
let Some(ip) = MutableIpv4Packet::new(frame.payload_mut()) else {
689+
log::trace!("discarding non-IPv4 packet");
690+
return;
691+
};
692+
handle_incoming_data_v4(tun_writer, ip, vpn_addr).await;
693+
}
694+
EtherTypes::Ipv6 => {
695+
let Some(vpn_addr) = vpn_v6 else {
696+
log::trace!("discarding incoming IPv6 packet: no tun V6 addr");
697+
return;
698+
};
699+
let Some(ip) = MutableIpv6Packet::new(frame.payload_mut()) else {
700+
log::trace!("discarding non-IPv6 packet");
701+
return;
702+
};
703+
handle_incoming_data_v6(tun_writer, ip, vpn_addr).await;
704+
}
705+
ethertype => {
706+
log::trace!("discarding non-IP frame: {ethertype}");
707+
}
624708
}
709+
}
625710

626-
let Some(mut ip) = MutableIpv4Packet::new(frame.payload_mut()) else {
627-
log::trace!("discarding non-IPv4 packet");
711+
async fn handle_incoming_data_v4(
712+
tun_writer: &mut tokio::io::WriteHalf<tun::AsyncDevice>,
713+
mut ip: MutableIpv4Packet<'_>,
714+
vpn_addr: Ipv4Addr,
715+
) {
716+
if ip.get_destination() == vpn_addr {
717+
// Drop attempt to send packets to tun IP on the real interface
628718
return;
629-
};
719+
}
720+
721+
fix_ipv4_checksums(&mut ip, None, Some(vpn_addr));
722+
723+
const BSD_LB_HEADER: &[u8] = &(AF_INET as u32).to_be_bytes();
724+
if let Err(error) = tun_writer
725+
.write_vectored(&[IoSlice::new(BSD_LB_HEADER), IoSlice::new(ip.packet())])
726+
.await
727+
{
728+
log::error!("Failed to redirect incoming IPv4 packet: {error}");
729+
}
730+
}
630731

631-
let dest = Some(ip.get_destination());
632-
if dest == vpn_v4 {
732+
async fn handle_incoming_data_v6(
733+
tun_writer: &mut tokio::io::WriteHalf<tun::AsyncDevice>,
734+
mut ip: MutableIpv6Packet<'_>,
735+
vpn_addr: Ipv6Addr,
736+
) {
737+
if ip.get_destination() == vpn_addr {
633738
// Drop attempt to send packets to tun IP on the real interface
634739
return;
635740
}
636741

637-
fix_ip_checksums(&mut ip, None, vpn_v4);
742+
fix_ipv6_checksums(&mut ip, None, Some(vpn_addr));
638743

639-
const BSD_LB_HEADER: &[u8] = &[0, 0, 0, AF_INET as u8];
744+
const BSD_LB_HEADER: &[u8] = &(AF_INET6 as u32).to_be_bytes();
640745
if let Err(error) = tun_writer
641746
.write_vectored(&[IoSlice::new(BSD_LB_HEADER), IoSlice::new(ip.packet())])
642747
.await
643748
{
644-
log::error!("Failed to redirect incoming packet: {error}");
749+
log::error!("Failed to redirect incoming IPv6 packet: {error}");
645750
}
646751
}
647752

648753
// Recalculate L3 and L4 checksums. Silently fail on error
649-
fn fix_ip_checksums(
754+
fn fix_ipv4_checksums(
650755
ip: &mut MutableIpv4Packet<'_>,
651756
new_source: Option<Ipv4Addr>,
652757
new_destination: Option<Ipv4Addr>,
653758
) {
654759
// Update source and update checksums
655-
656760
if let Some(source_ip) = new_source {
657761
ip.set_source(source_ip);
658762
}
@@ -690,6 +794,48 @@ fn fix_ip_checksums(
690794
ip.set_checksum(pnet::packet::ipv4::checksum(&ip.to_immutable()));
691795
}
692796

797+
// Recalculate L3 and L4 checksums. Silently fail on error
798+
fn fix_ipv6_checksums(
799+
ip: &mut MutableIpv6Packet<'_>,
800+
new_source: Option<Ipv6Addr>,
801+
new_destination: Option<Ipv6Addr>,
802+
) {
803+
// Update source and update checksums
804+
if let Some(source_ip) = new_source {
805+
ip.set_source(source_ip);
806+
}
807+
if let Some(dest_ip) = new_destination {
808+
ip.set_destination(dest_ip);
809+
}
810+
811+
let source_ip = ip.get_source();
812+
let destination_ip = ip.get_destination();
813+
814+
match ip.get_next_header() {
815+
IpNextHeaderProtocols::Tcp => {
816+
if let Some(mut tcp) = MutableTcpPacket::new(ip.payload_mut()) {
817+
use pnet::packet::tcp::ipv6_checksum;
818+
tcp.set_checksum(ipv6_checksum(
819+
&tcp.to_immutable(),
820+
&source_ip,
821+
&destination_ip,
822+
));
823+
}
824+
}
825+
IpNextHeaderProtocols::Udp => {
826+
if let Some(mut udp) = MutableUdpPacket::new(ip.payload_mut()) {
827+
use pnet::packet::udp::ipv6_checksum;
828+
udp.set_checksum(ipv6_checksum(
829+
&udp.to_immutable(),
830+
&source_ip,
831+
&destination_ip,
832+
));
833+
}
834+
}
835+
_ => (),
836+
}
837+
}
838+
693839
/// This returns a stream of outbound packets on a utun tunnel.
694840
///
695841
/// * `utun_iface`- name of a utun interface to capture packets on. Note that if this does not
@@ -759,9 +905,8 @@ impl PacketCodec for PktapCodec {
759905
return None;
760906
}
761907

762-
// TODO: Wasteful. We could use a ring buffer for concurrency handling or just
763-
// a share single buffer if handling one frame at a time (assuming no concurrency is needed)
764-
// Allocating the frame here is purely done for efficiency reasons.
908+
// TODO: Wasteful. Could share single buffer if handling one frame at a time (assuming no
909+
// concurrency is needed). Allocating the frame here is purely done for efficiency reasons.
765910
let mut frame = MutableEthernetPacket::owned(vec![0u8; 14 + data.len() - 4]).unwrap();
766911

767912
let raw_family = i32::from_ne_bytes(data[0..4].try_into().unwrap());

0 commit comments

Comments
 (0)