-
Notifications
You must be signed in to change notification settings - Fork 19
Add WeirdSplitter #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9335746
3038f2b
0b4cba7
6d4c891
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package dialers | ||
|
||
import ( | ||
"io" | ||
"net" | ||
"strings" | ||
"syscall" | ||
"github.com/celzero/firestack/intra/protect" | ||
) | ||
|
||
type WeirdSplitter struct { | ||
*net.TCPConn | ||
used bool | ||
Size int | ||
randomOffset bool | ||
} | ||
|
||
// similar to DialWithSplit(), but the second segment will be received first and it allow users to specify the size of 1st segment | ||
func DialWithWeirdSplit(d *protect.RDial, addr *net.TCPAddr, size int) (DuplexConn, error) { | ||
tcpConn, err := d.DialTCP(addr.Network(), nil, addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if tcpConn == nil { | ||
return nil, errNoConn | ||
} | ||
split1 := &WeirdSplitter{ | ||
TCPConn: tcpConn, | ||
Size: size, | ||
} | ||
return split1, nil | ||
} | ||
|
||
// similar to DialWithSplit(), but the second segment will be received first. | ||
func DialWithWeirdSplitRandomOffset(d *protect.RDial, addr *net.TCPAddr) (DuplexConn, error) { | ||
tcpConn, err := d.DialTCP(addr.Network(), nil, addr) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if tcpConn == nil { | ||
return nil, errNoConn | ||
} | ||
split1 := &WeirdSplitter{ | ||
TCPConn: tcpConn, | ||
randomOffset: true, | ||
} | ||
return split1, nil | ||
} | ||
|
||
func (s *WeirdSplitter) Write(b []byte) (int, error) { | ||
conn := s.TCPConn | ||
if s.used { | ||
// After the first write, there is no special write behavior. | ||
return conn.Write(b) | ||
} | ||
|
||
// Setting `used` to true ensures that this code only runs once per socket. | ||
s.used = true | ||
// One-byte segment is unable to be split. | ||
if len(b) < 2 { | ||
return conn.Write(b) | ||
} | ||
var b1, b2 []byte | ||
if s.randomOffset { | ||
b1, b2 = splitHello(b) | ||
} else { | ||
size := s.Size | ||
if len(b) <= s.Size { | ||
size = 1 | ||
} | ||
b1 = b[:size] | ||
b2 = b[size:] | ||
} | ||
rawConn, err := conn.SyscallConn() | ||
if err != nil { | ||
return 0, err | ||
} | ||
isIPv6 := strings.Contains(conn.RemoteAddr().String(), "[") | ||
|
||
rawErr := rawConn.Control(func(fd uintptr) { | ||
if isIPv6 { | ||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_UNICAST_HOPS, 1) | ||
} else { | ||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, 1) | ||
} | ||
}) | ||
Comment on lines
+80
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you add a succinct code comment on what this approach does to bypass censorship or link to existing projects using this technique? Is it similar to what's discussed here? #46 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GoodbyeDPI commit e28cb526
But we don't have privileges, so we should implement this with setting TTL. The TCP stack transmits the segment with TTL=1 first, then we restore TTL option before the TCP stack retransmits, this idea is mentioned in #46 too. |
||
if err != nil { | ||
return 0, err | ||
} | ||
if rawErr != nil { | ||
return 0, rawErr | ||
} | ||
n1, err := conn.Write(b1) | ||
if err != nil { | ||
return n1, err | ||
} | ||
|
||
rawErr = rawConn.Control(func(fd uintptr) { | ||
if isIPv6 { | ||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IPV6, syscall.IPV6_UNICAST_HOPS, 64) | ||
} else { | ||
err = syscall.SetsockoptInt(int(fd), syscall.IPPROTO_IP, syscall.IP_TTL, 64) | ||
} | ||
}) | ||
if err != nil { | ||
return n1, err | ||
} | ||
if rawErr != nil { | ||
return n1, rawErr | ||
} | ||
n2, err := conn.Write(b2) | ||
return n1 + n2, err | ||
} | ||
|
||
func (s *WeirdSplitter) ReadFrom(reader io.Reader) (bytes int64, err error) { | ||
if !s.used { | ||
// This is the first write on this socket. | ||
// Use copyOnce(), which calls Write(), to get Write's splitting behavior for | ||
// the first segment. | ||
if bytes, err = copyOnce(s, reader); err != nil { | ||
return | ||
} | ||
} | ||
|
||
b, err := s.TCPConn.ReadFrom(reader) | ||
bytes += b | ||
return | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you think we should add a code comment on why
len(b) < 2
won't work?(to my untrained eye, it isn't obvious)