Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • leap/obfsvpn
  • kali/obfsvpn
  • jkito/obfsvpn
  • kwadronaut/obfsvpn
4 results
Select Git revision
  • android_api
  • builds
  • feat/add-missing-env
  • feat/kcp
  • feat/quic
  • fixing-framing
  • go-version
  • kcp
  • log_error
  • main
  • maxb/bind-mount-start-scripts
  • maxb/gitlab-integration-test-stages
  • maxb/testing-golang-version-validation
  • nix_devshell
  • obfs4_env
  • obfsproxy
  • pcap
  • readme_minor
  • refactor-avoid-panic
  • refactor/server-env-parsing
  • xperimentl
  • v0.1.0
  • v1.0.0
  • v1.1.0
  • v1.2.0
  • v1.3.0
26 results
Show changes
Commits on Source (13)
...@@ -36,7 +36,7 @@ validate: ...@@ -36,7 +36,7 @@ validate:
go version go version
go env go env
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.57.2 go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.60.1
export PATH=$(go env GOPATH)/bin:$PATH export PATH=$(go env GOPATH)/bin:$PATH
......
# Changelog # Changelog
<a name="v1.2.0"></a>
## [v1.2.0](https://0xacab.org/leap/obfsvpn/compare/v1.1.0...v1.2.0) (2024-09-24)
### Feature
* Add ability to start obfsvpn server in TCP mode
### Fix
* Fix logging errrors for FFI clients
* Fix reconnect failures in obfsvpn clients
<a name="v1.1.0"></a> <a name="v1.1.0"></a>
## [v1.1.0](https://0xacab.org/leap/obfsvpn/compare/v1.0.0...v1.1.0) (2024-08-05) ## [v1.1.0](https://0xacab.org/leap/obfsvpn/compare/v1.0.0...v1.1.0) (2024-08-05)
......
...@@ -153,6 +153,7 @@ Before you can run a obfsvpn server container you need to make sure to set the f ...@@ -153,6 +153,7 @@ Before you can run a obfsvpn server container you need to make sure to set the f
| `OBFS4_DATA_DIR` | same as `OBFSVPN_STATE` | ./test_data | | `OBFS4_DATA_DIR` | same as `OBFSVPN_STATE` | ./test_data |
| `HOP_PT` | run server in hopping pt mode | 1 if true | | `HOP_PT` | run server in hopping pt mode | 1 if true |
| `KCP` | run server in KCP transport mode | 1 if true | | `KCP` | run server in KCP transport mode | 1 if true |
| `TCP` | run server in w/ tcp as the protocol the server accepts for proxying traffic | 1 if true |
### Integration testing ### Integration testing
......
...@@ -38,6 +38,7 @@ type EventLogger interface { ...@@ -38,6 +38,7 @@ type EventLogger interface {
const ( const (
dialGiveUpTime = 15 * time.Second dialGiveUpTime = 15 * time.Second
reconnectTime = 3 * time.Second
) )
type Obfs4Config struct { type Obfs4Config struct {
...@@ -45,6 +46,18 @@ type Obfs4Config struct { ...@@ -45,6 +46,18 @@ type Obfs4Config struct {
Cert string Cert string
} }
type Obfs4Conn struct {
net.Conn
config Obfs4Config
}
func NewObfs4Conn(conn net.Conn, config Obfs4Config) *Obfs4Conn {
return &Obfs4Conn{
Conn: conn,
config: config,
}
}
func (oc *Obfs4Config) String() string { func (oc *Obfs4Config) String() string {
return oc.Remote return oc.Remote
} }
...@@ -71,10 +84,9 @@ type HoppingConfig struct { ...@@ -71,10 +84,9 @@ type HoppingConfig struct {
type Client struct { type Client struct {
kcpConfig obfsvpn.KCPConfig kcpConfig obfsvpn.KCPConfig
ProxyAddr string ProxyAddr string
newObfs4Conn chan net.Conn newObfs4Conn chan Obfs4Conn
obfs4Conns []net.Conn obfs4Conns []Obfs4Conn
obfs4Endpoints []*Obfs4Config obfs4Endpoints []*Obfs4Config
obfs4Dialer *obfsvpn.Dialer
obfs4Failures map[string]int32 obfs4Failures map[string]int32
EventLogger EventLogger EventLogger EventLogger
state clientState state clientState
...@@ -100,7 +112,7 @@ func NewClient(ctx context.Context, stop context.CancelFunc, config Config) *Cli ...@@ -100,7 +112,7 @@ func NewClient(ctx context.Context, stop context.CancelFunc, config Config) *Cli
kcpConfig: config.KCPConfig, kcpConfig: config.KCPConfig,
obfs4Failures: map[string]int32{}, obfs4Failures: map[string]int32{},
minHopSeconds: config.HoppingConfig.MinHopSeconds, minHopSeconds: config.HoppingConfig.MinHopSeconds,
newObfs4Conn: make(chan net.Conn), newObfs4Conn: make(chan Obfs4Conn),
obfs4Endpoints: obfs4Endpoints, obfs4Endpoints: obfs4Endpoints,
stop: stop, stop: stop,
state: stopped, state: stopped,
...@@ -177,34 +189,18 @@ func (c *Client) Start() (bool, error) { ...@@ -177,34 +189,18 @@ func (c *Client) Start() (bool, error) {
c.updateState(starting) c.updateState(starting)
obfs4Endpoint := c.obfs4Endpoints[0] obfs4Endpoint := c.obfs4Endpoints[0]
obfs4Conn, err := c.createObfs4Connection(obfs4Endpoint)
c.obfs4Dialer, err = obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert)
if err != nil {
return false, fmt.Errorf("could not dial obfs4 remote: %w", err)
}
if c.kcpConfig.Enabled {
c.obfs4Dialer.DialFunc = obfsvpn.GetKCPDialer(c.kcpConfig, c.log)
}
obfs4Conn, err := c.obfs4Dialer.Dial("tcp", obfs4Endpoint.Remote)
if err != nil { if err != nil {
c.error("Could not dial obfs4 remote: %v", err) c.error("Could not dial obfs4 remote: %v", err)
return false, fmt.Errorf("could not dial remote: %w", err) return false, fmt.Errorf("could not dial remote: %w", err)
} }
c.obfs4Conns = []Obfs4Conn{*obfs4Conn}
c.obfs4Conns = []net.Conn{obfs4Conn}
c.updateState(running) c.updateState(running)
proxyAddr, err := net.ResolveUDPAddr("udp", c.ProxyAddr) c.openvpnConn, err = c.createOpenvpnConnection()
if err != nil {
return false, fmt.Errorf("cannot resolve UDP addr: %w", err)
}
c.openvpnConn, err = net.ListenUDP("udp", proxyAddr)
if err != nil { if err != nil {
return false, fmt.Errorf("error accepting udp connection: %w", err) return false, err
} }
if c.hopEnabled { if c.hopEnabled {
...@@ -222,6 +218,30 @@ func (c *Client) Start() (bool, error) { ...@@ -222,6 +218,30 @@ func (c *Client) Start() (bool, error) {
return true, nil return true, nil
} }
func (c *Client) createObfs4Connection(obfs4Endpoint *Obfs4Config) (*Obfs4Conn, error) {
var err error
obfs4Dialer, err := obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert)
if err != nil {
c.error("Could not dial obfs4 remote: %v", err)
return nil, err
}
if c.kcpConfig.Enabled {
obfs4Dialer.DialFunc = obfsvpn.GetKCPDialer(c.kcpConfig, c.log)
}
ctx, cancel := context.WithTimeout(context.Background(), dialGiveUpTime)
defer cancel()
c.log("Dialing remote: %v", obfs4Endpoint.Remote)
conn, err := obfs4Dialer.DialContext(ctx, "tcp", obfs4Endpoint.Remote)
if err != nil {
return nil, err
}
return NewObfs4Conn(conn, *obfs4Endpoint), nil
}
// updateState sets a new client state, logs it and sends an event to the clients // updateState sets a new client state, logs it and sends an event to the clients
// EventLogger in case it is available. Always set the state with this function in // EventLogger in case it is available. Always set the state with this function in
// order to ensure integrating clients receive an update state event via FFI. // order to ensure integrating clients receive an update state event via FFI.
...@@ -244,6 +264,40 @@ func (c *Client) pickRandomEndpoint() *Obfs4Config { ...@@ -244,6 +264,40 @@ func (c *Client) pickRandomEndpoint() *Obfs4Config {
return endpoint return endpoint
} }
func (c *Client) connectObfs4(obfs4Endpoint *Obfs4Config, sleepSeconds int) {
newObfs4Conn, err := c.createObfs4Connection(obfs4Endpoint)
if err != nil {
newRemote := obfs4Endpoint.Remote
_, ok := c.obfs4Failures[newRemote]
if ok {
c.obfs4Failures[newRemote] += 1
} else {
c.obfs4Failures[newRemote] = 1
}
c.error("Could not dial obfs4 remote: %v (failures: %d)", err, c.obfs4Failures[newRemote])
}
if newObfs4Conn == nil {
c.error("Did not get obfs4: %v ", err)
} else {
c.outLock.Lock()
c.obfs4Conns = append([]Obfs4Conn{*newObfs4Conn}, c.obfs4Conns...)
c.outLock.Unlock()
c.newObfs4Conn <- *newObfs4Conn
c.log("Dialed new remote")
// If we wait sleepSeconds here to clean up the previous connection, we can guarantee that the
// connection list will not grow unbounded
go func() {
time.Sleep(time.Duration(sleepSeconds) * time.Second)
c.cleanupOldConn()
}()
}
}
func (c *Client) hop() { func (c *Client) hop() {
for { for {
select { select {
...@@ -285,51 +339,8 @@ func (c *Client) hop() { ...@@ -285,51 +339,8 @@ func (c *Client) hop() {
} }
c.log("HOPPING to %+v", newRemote) c.log("HOPPING to %+v", newRemote)
c.connectObfs4(obfs4Endpoint, sleepSeconds)
obfs4Dialer, err := obfsvpn.NewDialerFromCert(obfs4Endpoint.Cert)
if err != nil {
c.error("Could not dial obfs4 remote: %v", err)
return
}
if c.kcpConfig.Enabled {
c.obfs4Dialer.DialFunc = obfsvpn.GetKCPDialer(c.kcpConfig, c.log)
}
ctx, cancel := context.WithTimeout(context.Background(), dialGiveUpTime)
defer cancel()
c.log("Dialing new remote: %v", newRemote)
newObfs4Conn, err := obfs4Dialer.DialContext(ctx, "tcp", newRemote)
if err != nil {
_, ok := c.obfs4Failures[newRemote]
if ok {
c.obfs4Failures[newRemote] += 1
} else {
c.obfs4Failures[newRemote] = 1
}
c.error("Could not dial obfs4 remote: %v (failures: %d)", err, c.obfs4Failures[newRemote])
}
if newObfs4Conn == nil {
c.error("Did not get obfs4: %v ", err)
} else {
c.outLock.Lock()
c.obfs4Conns = append([]net.Conn{newObfs4Conn}, c.obfs4Conns...)
c.outLock.Unlock()
c.newObfs4Conn <- newObfs4Conn
c.log("Dialed new remote")
// If we wait sleepSeconds here to clean up the previous connection, we can guarantee that the
// connection list will not grow unbounded
go func() {
time.Sleep(time.Duration(sleepSeconds) * time.Second)
c.cleanupOldConn()
}()
}
} }
} }
...@@ -340,13 +351,11 @@ func (c *Client) cleanupOldConn() { ...@@ -340,13 +351,11 @@ func (c *Client) cleanupOldConn() {
if len(c.obfs4Conns) > 1 { if len(c.obfs4Conns) > 1 {
c.log("Connections: %v", len(c.obfs4Conns)) c.log("Connections: %v", len(c.obfs4Conns))
connToClose := c.obfs4Conns[len(c.obfs4Conns)-1] connToClose := c.obfs4Conns[len(c.obfs4Conns)-1]
if connToClose != nil { c.log("Cleaning up old connection to %v", connToClose.RemoteAddr())
c.log("Cleaning up old connection to %v", connToClose.RemoteAddr())
err := connToClose.Close() err := connToClose.Close()
if err != nil { if err != nil {
c.log("Error closing obfs4 connection to %v: %v", connToClose.RemoteAddr(), err) c.log("Error closing obfs4 connection to %v: %v", connToClose.RemoteAddr(), err)
}
} }
// Remove the connection from our tracking list // Remove the connection from our tracking list
...@@ -375,21 +384,19 @@ func (c *Client) readUDPWriteTCP() { ...@@ -375,21 +384,19 @@ func (c *Client) readUDPWriteTCP() {
c.openvpnAddrLock.Unlock() c.openvpnAddrLock.Unlock()
} }
func() { // Always write to the first connection in our list because it will be most up to date
// Always write to the first connection in our list because it will be most up to date conn, err := c.getUsableConnection()
func() { if err != nil {
conn, err := c.getUsableConnection() c.error("Cannot get connection: %s", err)
if err != nil { continue
c.log("Cannot get connection: %s", err) }
return _, err = conn.Write(tcpBuffer)
} if err != nil {
_, err = conn.Write(tcpBuffer) c.error("readUDPWriteTCP: Write err from %v to %v: %v", conn.LocalAddr(), conn.RemoteAddr(), err)
if err != nil { time.Sleep(reconnectTime)
c.log("Write err from %v to %v: %v", conn.LocalAddr(), conn.RemoteAddr(), err) config := c.obfs4Conns[0].config
return c.connectObfs4(&config, 20)
} }
}()
}()
} }
} }
...@@ -440,18 +447,43 @@ func (c *Client) readTCPWriteUDP() { ...@@ -440,18 +447,43 @@ func (c *Client) readTCPWriteUDP() {
for { for {
tcpBytes := <-fromTCP tcpBytes := <-fromTCP
c.openvpnAddrLock.RLock() c.openvpnAddrLock.RLock()
_, err := c.openvpnConn.WriteToUDP(tcpBytes, c.openvpnAddr) _, err := c.openvpnConn.WriteToUDP(tcpBytes, c.openvpnAddr)
c.openvpnAddrLock.RUnlock() c.openvpnAddrLock.RUnlock()
if err != nil { if err != nil {
c.error("Write err from %v to %v: %v", c.openvpnConn.LocalAddr(), c.openvpnConn.RemoteAddr(), err) c.error("readTCPWriteUDP: Write err from %v to %v: %v", c.openvpnConn.LocalAddr(), c.openvpnConn.RemoteAddr(), err)
return c.openvpnAddrLock.Lock()
c.openvpnConn.Close()
c.openvpnConn, err = c.createOpenvpnConnection()
c.openvpnAddrLock.Unlock()
if err == nil {
c.openvpnAddrLock.RLock()
_, err := c.openvpnConn.WriteToUDP(tcpBytes, c.openvpnAddr)
c.openvpnAddrLock.RUnlock()
if err != nil {
c.error("Failed to resend. %v", err)
}
}
} }
} }
} }
} }
func (c *Client) createOpenvpnConnection() (*net.UDPConn, error) {
proxyAddr, err := net.ResolveUDPAddr("udp", c.ProxyAddr)
if err != nil {
c.error("cannot resolve UDP addr: %v", err)
return nil, err
}
udpConn, err := net.ListenUDP("udp", proxyAddr)
if err != nil {
c.error("error accepting udp connection: %v", err)
return nil, fmt.Errorf("error accepting udp connection: %w", err)
}
return udpConn, nil
}
func (c *Client) Stop() (bool, error) { func (c *Client) Stop() (bool, error) {
c.mux.Lock() c.mux.Lock()
defer c.mux.Unlock() defer c.mux.Unlock()
......
...@@ -85,6 +85,17 @@ elif [[ "$KCP" == "1" ]]; then ...@@ -85,6 +85,17 @@ elif [[ "$KCP" == "1" ]]; then
--config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \ --config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
--persist="${PERSIST_BRIDGE_STATE:-false}" \ --persist="${PERSIST_BRIDGE_STATE:-false}" \
-v -v
elif [[ "$TCP" == "1" ]]; then
# start the obfsvpn server in obfs4 mode w/ tcp as the protocol the server accepts for proxying traffic.
./obfsvpn-server \
--addr "${OBFS4_HOST}" \
--port "${OBFS4_PORT}" \
--remote "${OPENVPN_HOST}:${OPENVPN_PORT}" \
--state "$OBFS4_DATA_DIR" \
--config "${OBFS4_KEY_FILE:-$OBFS4_DATA_DIR/obfs4.json}" \
--persist="${PERSIST_BRIDGE_STATE:-false}" \
-v
else else
# start the obfsvpn server in regular obfs4 mode # start the obfsvpn server in regular obfs4 mode
./obfsvpn-server \ ./obfsvpn-server \
......
...@@ -46,23 +46,43 @@ docker compose -p "$mode" build --parallel --no-cache ...@@ -46,23 +46,43 @@ docker compose -p "$mode" build --parallel --no-cache
docker compose -p "$mode" --env-file $env_file up -d --build docker compose -p "$mode" --env-file $env_file up -d --build
# need to wait for openvpn to generate configs # need to wait for openvpn to generate configs
max_retry=40 max_initial_retry=40
counter=0 counter=0
# Testing bridged tunnel # Testing bridged tunnel
until docker compose -p "$mode" --env-file $env_file exec client ping -c 3 -I tun0 8.8.8.8 until docker compose -p "$mode" --env-file $env_file exec client ping -c 3 -I tun0 8.8.8.8
do do
((counter++)) ((counter++))
[[ counter -eq $max_retry ]] && echo "Failed!" | tee -a $logfile && exit 1 [[ counter -eq $max_initial_retry ]] && echo "Failed!" | tee -a $logfile && exit 1
echo "Pinging in client container with config $(cat ${env_file} | grep KCP) and $(cat ${env_file} | grep HOP_PT) failed. Trying again. Try #$counter" | tee -a $logfile echo "Pinging in client container with config $(cat ${env_file} | grep KCP) and $(cat ${env_file} | grep HOP_PT) failed. Trying again. Try #$counter" | tee -a $logfile
sleep 30 sleep 30
done done
counter=0
max_reconnect_retry=10
if [[ "$LIVE_TEST" == "0" ]]; then
echo "Testing reconnect"
# We can't just restart the obfsvpn servers because of some very odd way that key material is being generated so we go down and up instead 🤷
docker compose -p "$mode" --env-file $env_file down obfsvpn-1 obfsvpn-2
docker compose -p "$mode" --env-file $env_file up -d obfsvpn-1 obfsvpn-2
until docker compose -p "$mode" --env-file $env_file exec client ping -c 3 -I tun0 8.8.8.8
do
((counter++))
[[ counter -eq $max_reconnect_retry ]] && echo "Failed!" | tee -a $logfile && exit 1
echo "Pinging in client container with config $(cat ${env_file} | grep KCP) and $(cat ${env_file} | grep HOP_PT) after reconnect failed. Trying again. Try #$counter" | tee -a $logfile
sleep 10
done
fi
docker compose -p "$mode" --env-file $env_file exec client ndt7-client -quiet | tee -a $logfile docker compose -p "$mode" --env-file $env_file exec client ndt7-client -quiet | tee -a $logfile
# Testing bridge control panel # Testing bridge control panel
if [[ "$LIVE_TEST" == "0" ]]; then if [[ "$LIVE_TEST" == "0" ]]; then
if [[ `docker compose -p "$mode" logs | grep control-client | tail -n1 | cut -d "|" -f2 | xargs` == "parsing failure" ]] if [[ $(docker compose -p "$mode" logs | grep control-client | tail -n1 | cut -d "|" -f2 | xargs) == "parsing failure" ]]
then then
echo "failed to parse from control panel" | tee -a $logfile echo "failed to parse from control panel" | tee -a $logfile
exit 1 exit 1
......