Skip to content

Commit

Permalink
Merge pull request #26 from mysteriumnetwork/feature/original-destina…
Browse files Browse the repository at this point in the history
…tion

Recover original destination address of redirected requests
  • Loading branch information
Waldz authored Dec 6, 2022
2 parents efbc85f + 2e60f94 commit 302d7f7
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 15 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ Let's assume:

1. Run forwarder as a Docker container:
```bash
docker run -d --restart=on-failure --name forwarder -p 127.0.0.1:8443:8443 --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
docker run -d --restart=always --name forwarder --network host --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
--proxy.bind=127.0.0.1:8443 \
--proxy.upstream-url="https://superproxy.com:443" \
--filter.hostnames="ipinfo.io"
```

2. Redirect HTTP ports to forwarder:
```bash
iptables -t nat -A PREROUTING -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 172.18.0.4:8443
iptables -t nat -A PREROUTING -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 127.0.0.1:8443
```

3. Forwarder redirects HTTP traffic to upstream HTTPS proxy (this case just hostname 'ipinfo.io'):
Expand All @@ -36,7 +37,7 @@ Let's assume:

1. Run forwarder as a Docker container:
```bash
docker run -d --restart=on-failure --name forwarder --net openvpn_network --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
docker run -d --restart=always --name forwarder --network openvpn_network --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
--proxy.upstream-url="https://superproxy.com:443" \
--filter.hostnames="ipinfo.io,whatismyipaddress.com"
```
Expand Down Expand Up @@ -97,7 +98,7 @@ docker exec -it openvpn iptables -t nat -A POSTROUTING ! -d forwarder -j MASQUER

And the `forwarder` container should be started with the following environment variables:
```
docker run -d --restart=on-failure --name forwarder --net openvpn_network -e "EXTRA_ROUTES=192.168.255.0/24:openvpn" \
docker run -d --restart=always --name forwarder --network openvpn_network -e "EXTRA_ROUTES=192.168.255.0/24:openvpn" \
--cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
--proxy.upstream-url="https://superproxy.com:443" \
--filter.hostnames="ipinfo.io,whatismyipaddress.com"
Expand Down Expand Up @@ -148,15 +149,16 @@ If you need to forward non standard port too, the following steps required:

1. Start OpenVPN forwarder with the `--proxy.port-map` flag:
```bash
docker run -d --restart=on-failure --name forwarder -p 127.0.0.1:8443:8443 --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
docker run -d --restart=always --name forwarder --network host --cap-add NET_ADMIN mysteriumnetwork/openvpn-forwarder \
--proxy.bind=127.0.0.1:8443 \
--proxy.upstream-url="https://superproxy.com:443" \
--proxy.port-map=18443:8443,1234:1234
```

2. Apply additional iptables rule to forward required traffic:
```bash
docker exec -it openvpn iptables -t nat -A PREROUTING -p tcp -m tcp --dport 8443 -j DNAT --to-destination $FORWARDER_IP:18443
docker exec -it openvpn iptables -t nat -A PREROUTING -p tcp -m tcp --dport 1234 -j DNAT --to-destination $FORWARDER_IP:1234
docker exec -it openvpn iptables -t nat -A PREROUTING -p tcp -m tcp --dport 8443 -j DNAT --to-destination 127.0.0.1:18443
docker exec -it openvpn iptables -t nat -A PREROUTING -p tcp -m tcp --dport 1234 -j DNAT --to-destination 127.0.0.1:1234
```

This will allow keeping the original non-standard port.
Expand Down
5 changes: 3 additions & 2 deletions proxy/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (

// Context is the Proxy context, contains useful information about every request.
type Context struct {
scheme string
conn net.Conn
scheme string
conn net.Conn
connOriginalDst *net.TCPAddr

destinationHost string
destinationAddress string
Expand Down
93 changes: 87 additions & 6 deletions proxy/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ package proxy
import (
"bufio"
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"
"syscall"
"unsafe"

log "github.com/cihub/seelog"
"github.com/inconshreveable/go-vhost"
Expand Down Expand Up @@ -81,15 +85,31 @@ func (s *proxyServer) ListenAndServe(addr string) error {

func (s *proxyServer) handler(l net.Listener, f func(c *Context)) {
for {
conn, err := l.Accept()
var c Context
var err error

c.conn, err = l.Accept()
connMux, ok := c.conn.(*cmux.MuxConn)
if !ok {
err = fmt.Errorf("unsupported connection: %T", c.conn)
}
connTCP, ok := connMux.Conn.(*net.TCPConn)
if !ok {
err = fmt.Errorf("non-TCP connection: %T", connMux.Conn)
}
if err != nil {
_ = log.Errorf("Error accepting new connection - %v", err)
_ = log.Errorf("Error accepting new connection. %v", err)
continue
}

c.connOriginalDst, err = getOriginalDst(connTCP)
if err != nil {
_ = log.Errorf("Error recovering original destination address. %v", err)
}

go func() {
f(&Context{conn: conn})
conn.Close()
f(&c)
c.conn.Close()
}()
}
}
Expand Down Expand Up @@ -150,7 +170,7 @@ func (s *proxyServer) serveTLS(c *Context) {

tlsConn, err := vhost.TLS(c.conn)
if err != nil {
_ = log.Errorf("Error accepting new connection - %v", err)
_ = log.Errorf("Error accepting new TLS connection - %v", err)
return
}
defer tlsConn.Close()
Expand Down Expand Up @@ -211,12 +231,73 @@ func (s *proxyServer) connectTo(c net.Conn, remoteHost string) (conn io.ReadWrit
return conn, nil
}

const SO_ORIGINAL_DST = 0x50

// getOriginalDst retrieves the original destination address from
// NATed connection. Currently, only Linux iptables using DNAT/REDIRECT
// is supported. For other operating systems, this will just return
// conn.LocalAddr().
//
// Note that this function only works when nf_conntrack_ipv4 and/or
// nf_conntrack_ipv6 is loaded in the kernel.
func getOriginalDst(conn *net.TCPConn) (*net.TCPAddr, error) {
f, err := conn.File()
if err != nil {
return nil, err
}
defer f.Close()

fd := int(f.Fd())
// revert to non-blocking mode.
// see http://stackoverflow.com/a/28968431/1493661
if err = syscall.SetNonblock(fd, true); err != nil {
return nil, os.NewSyscallError("setnonblock", err)
}

// IPv4
var addr syscall.RawSockaddrInet4
var len uint32
len = uint32(unsafe.Sizeof(addr))
err = getSockOpt(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST, unsafe.Pointer(&addr), &len)
if err != nil {
return nil, os.NewSyscallError("getSockOpt", err)
}

ip := make([]byte, 4)
for i, b := range addr.Addr {
ip[i] = b
}
pb := *(*[2]byte)(unsafe.Pointer(&addr.Port))

return &net.TCPAddr{
IP: ip,
Port: int(pb[0])*256 + int(pb[1]),
}, nil
}

func getSockOpt(s int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) {
_, _, e := syscall.Syscall6(
syscall.SYS_GETSOCKOPT,
uintptr(s),
uintptr(level),
uintptr(optname),
uintptr(optval),
uintptr(unsafe.Pointer(optlen)),
0,
)
if e != 0 {
return e
}
return
}

func (s *proxyServer) accessLog(message string, c *Context) {
log.Tracef(
"%s [client_addr=%s, dest_addr=%s, destination_host=%s, destination_addr=%s]",
"%s [client_addr=%s, dest_addr=%s, original_dest_addr=%s destination_host=%s, destination_addr=%s]",
message,
c.conn.RemoteAddr().String(),
c.conn.LocalAddr().String(),
c.connOriginalDst,
c.destinationHost,
c.destinationAddress,
)
Expand Down

0 comments on commit 302d7f7

Please sign in to comment.