celzero / firestack

Userspace wireguard and network monitor
https://rethinkdns.com/app
Mozilla Public License 2.0
88 stars 15 forks source link

TCP Desync #46

Closed ignoramous closed 2 months ago

ignoramous commented 6 months ago

A user writes,

hufrea's method works on older android devices: https://github.com/hufrea/byedpi/blob/82e5229df00859eb3500f0c771a8991d1fe8e607/desync.c#L69-L123

If you will implement TCB-Desynchronization (not just sending in reverse order) , hufrea's method is better.

golang.org/x/sys/unix has exported needed APIs:

package main
import (
    "net"
    "net/netip"
    "time"
    "golang.org/x/sys/unix"
    "flag"
)

func main(){
    var server6 string
    flag.StringVar(&server6,"server6", "[2600:9000:2001:7a00:13:5d53:5740:93a1]:443","must be IPv6")
    flag.Parse()

    dst := net.TCPAddrFromAddrPort(netip.MustParseAddrPort(server6))
    conn,err := net.DialTCP("tcp",nil,dst)
    if err!=nil{
        panic(err)
    }
    defer conn.Close()

    raw,err := conn.SyscallConn()
    if err!=nil{
        panic(err)
    }
    var soFD int
    raw.Control(func(fd uintptr){
        soFD=int(fd)
    })

    fFD,err := unix.MemfdCreate("haha",unix.O_RDWR)
    if err!=nil{
        panic(err)
    }
    defer unix.Close(fFD)
    err = unix.Ftruncate(fFD,4)
    if err!=nil{
        panic(err)
    }

    firstSegment,err := unix.Mmap(fFD,0,4,unix.PROT_WRITE,unix.MAP_SHARED)
    if err!=nil{
        panic(err)
    }
    defer unix.Munmap(firstSegment)
    for i := 0; i<4; i++{
        firstSegment[i]=0
    }

    unix.SetsockoptInt(soFD,unix.SOL_IPV6,unix.IPV6_UNICAST_HOPS,10)
    var t1 int64=0
    _,err = unix.Sendfile(soFD,fFD,&t1,4)
    if err!=nil{
        panic(err)
    }
    for i := 0; i<4; i++{
        firstSegment[i]=1
    }

    unix.SetsockoptInt(soFD,unix.SOL_IPV6,unix.IPV6_UNICAST_HOPS,64)
    secondSegment := [4]byte{5,6,7,8}
    _,err = conn.Write(secondSegment[:])
    if err!=nil{
        panic(err)
    }

    time.Sleep(5000 * time.Millisecond)
}

Currently, firestack has a retrier to deal with incompatible servers. I guess sending two TCP segments in reverse order can improve compatibility too, although the unprivileged implementation increases latency.

To implement it, we can use TTL to force the TCP stack to retransmit.

package main
import (
    "net"
    "net/netip"
    "syscall"
    "time"
)

const SO_ZEROCOPY int = 60
const MSG_ZEROCOPY int = 0x4000000
func main(){
    dst := net.TCPAddrFromAddrPort(netip.MustParseAddrPort("[2600:9000:2375:9a00:14:176d:6100:93a1]:443"))
    conn,err := net.DialTCP("tcp",nil,dst)
    if err!=nil{
        panic(err)
    }
    defer conn.Close()

    raw,err := conn.SyscallConn()
    if err!=nil{
        panic(err)
    }
    var soFD int
    raw.Control(func(fd uintptr){
        soFD=int(fd)
    })

    syscall.SetsockoptInt(soFD,syscall.SOL_SOCKET,SO_ZEROCOPY,1)
    syscall.SetsockoptInt(soFD,syscall.SOL_IPV6,syscall.IPV6_UNICAST_HOPS,10)
    firstSegment := [4]byte{0,0,0,0}
    t1 := syscall.SockaddrInet6{}
    syscall.Sendto(soFD,firstSegment[:],MSG_ZEROCOPY,&t1)
    firstSegment[0]=1
    firstSegment[3]=4
    syscall.SetsockoptInt(soFD,syscall.SOL_IPV6,syscall.IPV6_UNICAST_HOPS,64)

    secondSegment := [4]byte{5,6,7,8}
    _,err = conn.Write(secondSegment[:])
    if err!=nil{
        panic(err)
    }

    time.Sleep(5000 * time.Millisecond)
}

If it doesn't combine with ZEROCOPY, we can implement normal TCP Fragmentation; with ZEROCOPY, we can implement TCB-Desynchronization!


Also: #29

Lanius-collaris commented 2 months ago

10a20bce E split-desync: setsockopt failed: invalid argument 🤕 ~I guess it related to address family.~ s.ttl is set to 0.

Lanius-collaris commented 2 months ago

IPv4 traceroute and cache are broken. 🤕 I don't know how to deal with dual-stack socket.

Lanius-collaris commented 2 months ago

@ignoramous Don't use dual-stack socket, the type of from is always *syscall.SockaddrInet6, and in a received control message, Cmsghdr.Level is IPPROTO_IPV6, but sock_extended_err.ee_origin may be SO_EE_ORIGIN_ICMP. So IPv4 traceroute is broken.

package main

import (
    "fmt"
    "net"
    "net/netip"
    //"os"
    "reflect"
    "syscall"
    "time"
)

func p(e error) {
    if e != nil {
        panic(e)
    }
}
func main() {
    lAddr := netip.MustParseAddrPort("0.0.0.0:0")
    conn1, err := net.ListenUDP("udp", net.UDPAddrFromAddrPort(lAddr))
    p(err)
    defer conn1.Close()
    rawConn, _ := conn1.SyscallConn()
    var udpFd int
    rawConn.Control(func(fd uintptr) {
        udpFd = int(fd)
    })
    sockAddr, _ := syscall.Getsockname(udpFd)
    fmt.Printf("Getsockname: %v %v\n", reflect.TypeOf(sockAddr), sockAddr)

    /*err = syscall.SetsockoptInt(udpFd, syscall.IPPROTO_IPV6, syscall.IPV6_RECVERR, 1)
    p(err)*/

    err = syscall.SetsockoptInt(udpFd, syscall.IPPROTO_IP, syscall.IP_RECVERR, 1)
    p(err)

    /*err = syscall.SetsockoptInt(udpFd,syscall.IPPROTO_IPV6,syscall.IPV6_UNICAST_HOPS,1)
    p(err)*/

    err = syscall.SetsockoptInt(udpFd, syscall.IPPROTO_IP, syscall.IP_TTL, 1)
    p(err)

    dst := netip.MustParseAddrPort("140.82.121.4:443")
    conn1.WriteToUDPAddrPort([]byte("haha"), dst)

    time.Sleep(2*time.Second)

    var msgBuf [1024]byte
    var cmsgBuf [1024]byte
    n, cmsgN, _, from, err := syscall.Recvmsg(udpFd, msgBuf[:], cmsgBuf[:], syscall.MSG_ERRQUEUE)
    p(err)
    fmt.Printf("n: %v\ncmsgN: %v\nfrom: %v %v\n", n, cmsgN, reflect.TypeOf(from), from)
    fmt.Printf("msg: %v\ncmsg %v\n", msgBuf[:n], cmsgBuf[:cmsgN])
    //os.WriteFile("cmsg.bin", cmsgBuf[:cmsgN], 0o600)
}
ignoramous commented 2 months ago

Don't use dual-stack socket, the type of from is always *syscall.SockaddrInet6, and in a received control message, Cmsghdr.Level is IPPROTO_IPV6, but sock_extended_err.ee_origin may be SO_EE_ORIGIN_ICMP. So IPv4 traceroute is broken.

If you've got the time, please do send a PR, as I don't fully grasp these changes to make it happen for the upcoming release (imminent).

Lanius-collaris commented 2 months ago

In previous test, I applied a simple patch to call Intra.dialStrat().

diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt
index 5592dcd3..8ee119e9 100644
--- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt
+++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt
@@ -29,6 +29,7 @@ import com.celzero.bravedns.ui.activity.MiscSettingsActivity
 import com.celzero.bravedns.ui.activity.NetworkLogsActivity
 import com.celzero.bravedns.ui.activity.ProxySettingsActivity
 import com.celzero.bravedns.ui.activity.TunnelSettingsActivity
+import intra.Intra

 class ConfigureFragment : Fragment(R.layout.fragment_configure) {

@@ -87,6 +88,8 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) {
             // open logs configuration
             startActivity(ScreenType.LOGS)
         }
+        b.enableDesync.setOnClickListener{ Intra.dialStrat(1) }
+        b.enableRetrier.setOnClickListener{ Intra.dialStrat(0) }
     }

     private fun startActivity(type: ScreenType) {
diff --git a/app/src/main/res/layout/fragment_configure.xml b/app/src/main/res/layout/fragment_configure.xml
index 8f0d4775..ab86bc25 100644
--- a/app/src/main/res/layout/fragment_configure.xml
+++ b/app/src/main/res/layout/fragment_configure.xml
@@ -336,5 +336,42 @@
                     android:src="@drawable/ic_right_arrow_white" />
             </RelativeLayout>
         </com.google.android.material.card.MaterialCardView>
+        <com.google.android.material.card.MaterialCardView android:id="@+id/enable_desync"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="enable_desync"
+        android:textSize="16sp"
+        app:cardCornerRadius="8dp"
+            app:cardElevation="4dp"
+            app:cardUseCompatPadding="true">
+            <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="20dp">
+            <TextView
+            android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="15dp"
+                    android:text="enable_desync"
+                    android:textColor="?attr/primaryTextColor"
+                    android:textSize="16sp" />
+            </RelativeLayout>
+        </com.google.android.material.card.MaterialCardView>
+        <com.google.android.material.card.MaterialCardView android:id="@+id/enable_retrier"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="enable_retrier"
+        android:textSize="16sp"
+        android:layout_marginBottom="300dp"
+        app:cardCornerRadius="8dp"
+            app:cardElevation="4dp"
+            app:cardUseCompatPadding="true">
+            <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="20dp">
+            <TextView
+            android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="15dp"
+                    android:text="enable_retrier"
+                    android:textColor="?attr/primaryTextColor"
+                    android:textSize="16sp" />
+            </RelativeLayout>
+       </com.google.android.material.card.MaterialCardView>
     </LinearLayout>
 </androidx.core.widget.NestedScrollView>
\ No newline at end of file