Browse Source

feat: add iSH iPhone support and optimize slider captcha solver

Optimized slider captcha solver to avoid CPU/RAM-heavy image rendering.
Added legacy TCP accept support specifically for iSH on linux/386 arch.
Fixed 'accept4: function not implemented' error in VLESS mode on iSH.
pull/162/head
Moroka8 2 months ago
parent
commit
2bcb9e3588
  1. 74
      client/ish_listener_linux_386.go
  2. 9
      client/ish_listener_other.go
  3. 11
      client/main.go
  4. 100
      client/slider_captcha.go

74
client/ish_listener_linux_386.go

@ -0,0 +1,74 @@
//go:build linux && 386
package main
import (
"net"
"os"
"syscall"
"time"
"unsafe"
)
type ishListener struct {
net.Listener
fd int
}
// wrapISHListener overrides the standard net.Listener with a legacy syscall listener
// designed specifically for the iSH simulator on iOS, which lacks modern `accept4`.
func wrapISHListener(ln net.Listener) (net.Listener, error) {
tl, ok := ln.(*net.TCPListener)
if !ok {
return ln, nil
}
f, err := tl.File()
if err != nil {
return nil, err
}
return &ishListener{Listener: ln, fd: int(f.Fd())}, nil
}
func (l *ishListener) Accept() (net.Conn, error) {
for {
addr := make([]byte, 128)
addrlen := uintptr(128)
// i386 network syscalls are multiplexed via socketcall (102).
// SYS_ACCEPT is subcall 5.
args := [3]uintptr{uintptr(l.fd), uintptr(unsafe.Pointer(&addr[0])), uintptr(unsafe.Pointer(&addrlen))}
r1, _, errno := syscall.Syscall(102, 5, uintptr(unsafe.Pointer(&args)), 0)
if errno != 0 {
if errno == syscall.EAGAIN || errno == syscall.EINTR || errno == syscall.EWOULDBLOCK {
time.Sleep(50 * time.Millisecond) // Just in case it's non-blocking somehow
continue
}
return nil, errno
}
nfd := int(r1)
// Wrap raw FD into os.File, then into a net.Conn.
// fileConn duplicates the fd again.
f := os.NewFile(uintptr(nfd), "ish-conn")
conn, err := net.FileConn(f)
f.Close()
if err != nil {
syscall.Close(nfd)
return nil, err
}
return conn, nil
}
}
func (l *ishListener) Close() error {
err1 := syscall.Close(l.fd)
err2 := l.Listener.Close()
if err1 != nil {
return err1
}
return err2
}

9
client/ish_listener_other.go

@ -0,0 +1,9 @@
//go:build !(linux && 386)
package main
import "net"
// wrapISHListener is a no-op for architectures that don't need the legacy socketcall accept bypass.
func wrapISHListener(ln net.Listener) (net.Listener, error) {
return ln, nil
}

11
client/main.go

@ -2036,12 +2036,19 @@ func runVLESSMode(ctx context.Context, tp *turnParams, peer *net.UDPAddr, listen
if err != nil {
log.Panicf("TCP listen: %s", err)
}
context.AfterFunc(ctx, func() { _ = listener.Close() })
wrappedListener, err := wrapISHListener(listener)
if err != nil {
log.Printf("Warning: failed to wrap listener: %v", err)
wrappedListener = listener
}
context.AfterFunc(ctx, func() { _ = wrappedListener.Close() })
log.Printf("VLESS mode: listening on %s (round-robin across %d sessions)", listenAddr, numSessions)
var wgConn sync.WaitGroup
for {
tcpConn, err := listener.Accept()
tcpConn, err := wrappedListener.Accept()
if err != nil {
select {
case <-ctx.Done():

100
client/slider_captcha.go

@ -715,12 +715,70 @@ func rankSliderCandidates(img image.Image, gridSize int, swaps []int) ([]sliderC
}
func scoreSliderCandidate(img image.Image, gridSize int, mapping []int) (int64, error) {
rendered, err := renderSliderCandidate(img, gridSize, mapping)
if err != nil {
return 0, err
bounds := img.Bounds()
var score int64
for row := 0; row < gridSize; row++ {
for col := 0; col < gridSize-1; col++ {
dstLeftIndex := row*gridSize + col
dstRightIndex := row*gridSize + col + 1
srcLeftIndex := mapping[dstLeftIndex]
srcRightIndex := mapping[dstRightIndex]
dstLeftRect := sliderTileRect(bounds, gridSize, dstLeftIndex)
dstRightRect := sliderTileRect(bounds, gridSize, dstRightIndex)
srcLeftRect := sliderTileRect(bounds, gridSize, srcLeftIndex)
srcRightRect := sliderTileRect(bounds, gridSize, srcRightIndex)
height := minInt(dstLeftRect.Dy(), dstRightRect.Dy())
leftSrcXRel := (dstLeftRect.Dx() - 1) * srcLeftRect.Dx() / dstLeftRect.Dx()
sxLeft := srcLeftRect.Min.X + leftSrcXRel
sxRight := srcRightRect.Min.X
for offset := 0; offset < height; offset++ {
syLeft := srcLeftRect.Min.Y + offset*srcLeftRect.Dy()/dstLeftRect.Dy()
syRight := srcRightRect.Min.Y + offset*srcRightRect.Dy()/dstRightRect.Dy()
score += pixelDiff(
img.At(sxLeft, syLeft),
img.At(sxRight, syRight),
)
}
}
}
for row := 0; row < gridSize-1; row++ {
for col := 0; col < gridSize; col++ {
dstTopIndex := row*gridSize + col
dstBottomIndex := (row+1)*gridSize + col
srcTopIndex := mapping[dstTopIndex]
srcBottomIndex := mapping[dstBottomIndex]
dstTopRect := sliderTileRect(bounds, gridSize, dstTopIndex)
dstBottomRect := sliderTileRect(bounds, gridSize, dstBottomIndex)
srcTopRect := sliderTileRect(bounds, gridSize, srcTopIndex)
srcBottomRect := sliderTileRect(bounds, gridSize, srcBottomIndex)
width := minInt(dstTopRect.Dx(), dstBottomRect.Dx())
topSrcYRel := (dstTopRect.Dy() - 1) * srcTopRect.Dy() / dstTopRect.Dy()
syTop := srcTopRect.Min.Y + topSrcYRel
syBottom := srcBottomRect.Min.Y
for offset := 0; offset < width; offset++ {
sxTop := srcTopRect.Min.X + offset*srcTopRect.Dx()/dstTopRect.Dx()
sxBottom := srcBottomRect.Min.X + offset*srcBottomRect.Dx()/dstBottomRect.Dx()
score += pixelDiff(
img.At(sxTop, syTop),
img.At(sxBottom, syBottom),
)
}
}
}
return scoreRenderedSliderImage(rendered, gridSize), nil
return score, nil
}
func renderSliderCandidate(img image.Image, gridSize int, mapping []int) (*image.RGBA, error) {
@ -744,40 +802,6 @@ func renderSliderCandidate(img image.Image, gridSize int, mapping []int) (*image
return rendered, nil
}
func scoreRenderedSliderImage(img image.Image, gridSize int) int64 {
bounds := img.Bounds()
var score int64
for row := 0; row < gridSize; row++ {
for col := 0; col < gridSize-1; col++ {
leftRect := sliderTileRect(bounds, gridSize, row*gridSize+col)
rightRect := sliderTileRect(bounds, gridSize, row*gridSize+col+1)
height := minInt(leftRect.Dy(), rightRect.Dy())
for offset := 0; offset < height; offset++ {
score += pixelDiff(
img.At(leftRect.Max.X-1, leftRect.Min.Y+offset),
img.At(rightRect.Min.X, rightRect.Min.Y+offset),
)
}
}
}
for row := 0; row < gridSize-1; row++ {
for col := 0; col < gridSize; col++ {
topRect := sliderTileRect(bounds, gridSize, row*gridSize+col)
bottomRect := sliderTileRect(bounds, gridSize, (row+1)*gridSize+col)
width := minInt(topRect.Dx(), bottomRect.Dx())
for offset := 0; offset < width; offset++ {
score += pixelDiff(
img.At(topRect.Min.X+offset, topRect.Max.Y-1),
img.At(bottomRect.Min.X+offset, bottomRect.Min.Y),
)
}
}
}
return score
}
func sliderTileRect(bounds image.Rectangle, gridSize int, index int) image.Rectangle {
row := index / gridSize

Loading…
Cancel
Save