-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1016 from xchacha20-poly1305/dev-android-protect
feat: support Android protect path
- Loading branch information
Showing
9 changed files
with
346 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import socket | ||
import array | ||
import os | ||
import struct | ||
import sys | ||
|
||
|
||
def serve(path): | ||
try: | ||
os.unlink(path) | ||
except OSError: | ||
if os.path.exists(path): | ||
raise | ||
|
||
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) | ||
server.bind(path) | ||
server.listen() | ||
print(f"Listening on {path}") | ||
|
||
try: | ||
while True: | ||
connection, client_address = server.accept() | ||
print(f"Client connected") | ||
|
||
try: | ||
# Receiving fd from client | ||
fds = array.array("i") | ||
msg, ancdata, flags, addr = connection.recvmsg(1, socket.CMSG_LEN(struct.calcsize('i'))) | ||
for cmsg_level, cmsg_type, cmsg_data in ancdata: | ||
if cmsg_level == socket.SOL_SOCKET and cmsg_type == socket.SCM_RIGHTS: | ||
fds.frombytes(cmsg_data[:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)]) | ||
|
||
fd = fds[0] | ||
|
||
# We make a call to setsockopt(2) here, so client can verify we have received the fd | ||
# In the real scenario, the server would set things like SO_MARK, | ||
# we use SO_RCVBUF as it doesn't require any special capabilities. | ||
nbytes = struct.pack("i", 2500) | ||
fdsocket = fd_to_socket(fd) | ||
fdsocket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, nbytes) | ||
fdsocket.close() | ||
|
||
# The only protocol-like thing specified in the client implementation. | ||
connection.send(b'\x01') | ||
finally: | ||
connection.close() | ||
print("Connection closed") | ||
|
||
except KeyboardInterrupt: | ||
print("Exit") | ||
|
||
finally: | ||
server.close() | ||
os.unlink(path) | ||
|
||
|
||
def fd_to_socket(fd): | ||
return socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM) | ||
|
||
|
||
if __name__ == "__main__": | ||
if len(sys.argv) < 2: | ||
raise ValueError("unix socket path is required") | ||
|
||
serve(sys.argv[1]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package sockopts | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
) | ||
|
||
type SocketOptions struct { | ||
BindInterface *string | ||
FirewallMark *uint32 | ||
FdControlUnixSocket *string | ||
} | ||
|
||
// implemented in platform-specific files | ||
var ( | ||
bindInterfaceFunc func(c *net.UDPConn, device string) error | ||
firewallMarkFunc func(c *net.UDPConn, fwmark uint32) error | ||
fdControlUnixSocketFunc func(c *net.UDPConn, path string) error | ||
) | ||
|
||
func (o *SocketOptions) CheckSupported() (err error) { | ||
if o.BindInterface != nil && bindInterfaceFunc == nil { | ||
return &UnsupportedError{"bindInterface"} | ||
} | ||
if o.FirewallMark != nil && firewallMarkFunc == nil { | ||
return &UnsupportedError{"fwmark"} | ||
} | ||
if o.FdControlUnixSocket != nil && fdControlUnixSocketFunc == nil { | ||
return &UnsupportedError{"fdControlUnixSocket"} | ||
} | ||
return nil | ||
} | ||
|
||
type UnsupportedError struct { | ||
Field string | ||
} | ||
|
||
func (e *UnsupportedError) Error() string { | ||
return fmt.Sprintf("%s is not supported on this platform", e.Field) | ||
} | ||
|
||
func (o *SocketOptions) ListenUDP() (uconn net.PacketConn, err error) { | ||
uconn, err = net.ListenUDP("udp", nil) | ||
if err != nil { | ||
return | ||
} | ||
err = o.applyToUDPConn(uconn.(*net.UDPConn)) | ||
if err != nil { | ||
uconn.Close() | ||
uconn = nil | ||
return | ||
} | ||
return | ||
} | ||
|
||
func (o *SocketOptions) applyToUDPConn(c *net.UDPConn) error { | ||
if o.BindInterface != nil && bindInterfaceFunc != nil { | ||
err := bindInterfaceFunc(c, *o.BindInterface) | ||
if err != nil { | ||
return fmt.Errorf("failed to bind to interface: %w", err) | ||
} | ||
} | ||
if o.FirewallMark != nil && firewallMarkFunc != nil { | ||
err := firewallMarkFunc(c, *o.FirewallMark) | ||
if err != nil { | ||
return fmt.Errorf("failed to set fwmark: %w", err) | ||
} | ||
} | ||
if o.FdControlUnixSocket != nil && fdControlUnixSocketFunc != nil { | ||
err := fdControlUnixSocketFunc(c, *o.FdControlUnixSocket) | ||
if err != nil { | ||
return fmt.Errorf("failed to send fd to control unix socket: %w", err) | ||
} | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
//go:build linux | ||
|
||
package sockopts | ||
|
||
import ( | ||
"fmt" | ||
"net" | ||
"time" | ||
|
||
"golang.org/x/exp/constraints" | ||
"golang.org/x/sys/unix" | ||
) | ||
|
||
const ( | ||
fdControlUnixTimeout = 3 * time.Second | ||
) | ||
|
||
func init() { | ||
bindInterfaceFunc = bindInterfaceImpl | ||
firewallMarkFunc = firewallMarkImpl | ||
fdControlUnixSocketFunc = fdControlUnixSocketImpl | ||
} | ||
|
||
func controlUDPConn(c *net.UDPConn, cb func(fd int) error) (err error) { | ||
rconn, err := c.SyscallConn() | ||
if err != nil { | ||
return | ||
} | ||
cerr := rconn.Control(func(fd uintptr) { | ||
err = cb(int(fd)) | ||
}) | ||
if err != nil { | ||
return | ||
} | ||
if cerr != nil { | ||
err = fmt.Errorf("failed to control fd: %w", cerr) | ||
return | ||
} | ||
return | ||
} | ||
|
||
func bindInterfaceImpl(c *net.UDPConn, device string) error { | ||
return controlUDPConn(c, func(fd int) error { | ||
return unix.BindToDevice(fd, device) | ||
}) | ||
} | ||
|
||
func firewallMarkImpl(c *net.UDPConn, fwmark uint32) error { | ||
return controlUDPConn(c, func(fd int) error { | ||
return unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_MARK, int(fwmark)) | ||
}) | ||
} | ||
|
||
func fdControlUnixSocketImpl(c *net.UDPConn, path string) error { | ||
return controlUDPConn(c, func(fd int) error { | ||
socketFd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0) | ||
if err != nil { | ||
return fmt.Errorf("failed to create unix socket: %w", err) | ||
} | ||
defer unix.Close(socketFd) | ||
|
||
var timeout unix.Timeval | ||
timeUsec := fdControlUnixTimeout.Microseconds() | ||
castAssignInteger(timeUsec/1e6, &timeout.Sec) | ||
// Specifying the type explicitly is not necessary here, but it makes GoLand happy. | ||
castAssignInteger[int64](timeUsec%1e6, &timeout.Usec) | ||
|
||
_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &timeout) | ||
_ = unix.SetsockoptTimeval(socketFd, unix.SOL_SOCKET, unix.SO_SNDTIMEO, &timeout) | ||
|
||
err = unix.Connect(socketFd, &unix.SockaddrUnix{Name: path}) | ||
if err != nil { | ||
return fmt.Errorf("failed to connect: %w", err) | ||
} | ||
|
||
err = unix.Sendmsg(socketFd, nil, unix.UnixRights(fd), nil, 0) | ||
if err != nil { | ||
return fmt.Errorf("failed to send: %w", err) | ||
} | ||
|
||
dummy := []byte{1} | ||
n, err := unix.Read(socketFd, dummy) | ||
if err != nil { | ||
return fmt.Errorf("failed to receive: %w", err) | ||
} | ||
if n != 1 { | ||
return fmt.Errorf("socket closed unexpectedly") | ||
} | ||
|
||
return nil | ||
}) | ||
} | ||
|
||
func castAssignInteger[F, T constraints.Integer](from F, to *T) { | ||
*to = T(from) | ||
} |
Oops, something went wrong.