Skip to content
This repository has been archived by the owner on May 6, 2024. It is now read-only.

feat: ✨ add new OutlineDevice API that uses Outline SDK #118

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

jyyi1
Copy link
Contributor

@jyyi1 jyyi1 commented Apr 14, 2023

In this PR, I added a new OutlineDevice API that can be used by Outline Client to replace the old shadowsocks.CheckConnectivity, shadowsocks.newClient and OutlineTunnel. I integrated Outline SDK into the implementation as well.

I also refactored a bunch of things in this PR:

  • Export outline package in gomobile
  • Mark all outline/** sub packages as deprecated

Related PR: #122 , #124

@jyyi1 jyyi1 requested a review from a team as a code owner April 14, 2023 20:48
@jyyi1 jyyi1 requested a review from fortuna April 14, 2023 20:48
@jyyi1 jyyi1 requested a review from fortuna April 19, 2023 22:32
outline/shadowsocks/legacy_api.go Outdated Show resolved Hide resolved
outline/internal/shadowsocks/shadowsocks.go Outdated Show resolved Hide resolved
outline/client.go Outdated Show resolved Hide resolved
outline/client.go Outdated Show resolved Hide resolved
outline/connectivity.go Outdated Show resolved Hide resolved
outline/client.go Show resolved Hide resolved
@jyyi1 jyyi1 requested a review from fortuna April 21, 2023 22:50
Copy link
Contributor

@fortuna fortuna left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you in the office tomorrow? perhaps we can brainstorm a bit on the api

outline/client.go Outdated Show resolved Hide resolved
outline/electron/main.go Outdated Show resolved Hide resolved
outline/client.go Outdated Show resolved Hide resolved
outline/internal/shadowsocks/shadowsocks.go Outdated Show resolved Hide resolved
outline/tun2socks/tunnel_android.go Outdated Show resolved Hide resolved
outline/tun2socks/tunnel_darwin.go Outdated Show resolved Hide resolved
outline/client.go Outdated Show resolved Hide resolved
@jyyi1
Copy link
Contributor Author

jyyi1 commented Apr 24, 2023

@fortuna I will be in office on Wednesday, let me schedule a meeting.

@jyyi1 jyyi1 requested a review from fortuna May 2, 2023 20:31
@jyyi1
Copy link
Contributor Author

jyyi1 commented May 2, 2023

As discussed offline, @fortuna please take a look at the newly proposed tun2socks.ConnectTunnel, while still keeping all old function signature unchanged.


func startLegacyShadowsocksClient() {
// legacy raw flags
config := shadowsocks.Config{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you define a legacyConfigFromFlags(), and pass the config as a parameter?

Making the config explicit in the function call will make the call more understandable.

Let's use "start tunnel" to be analogous to the new code.
I'm imagining startLegacyShadowsocksTunnel(legacyConfigFromClient)

return nil, nil, fmt.Errorf("failed to resolve proxy address: %w", err)
}

proxyTCPEndpoint := transport.TCPEndpoint{RemoteAddr: net.TCPAddr{IP: proxyIP.IP, Port: port}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: #120

Let me know of you want to merge that or not. It shouldn't conflict much.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, please merge that. BTW, I will work on the SDK change (to introduce IPDevice) before updating this PR as well.

config.Prefix = p
}
}
client, err := shadowsocks.NewClient(&config)
if err != nil {
log.Errorf("Failed to create Shadowsocks client: %v", err)
os.Exit(neterrors.IllegalConfiguration.Number())
}

if *args.checkConnectivity {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this back out. The connectivity test and the tunnel flows are separate. We need to clearly run one or the other like we had before.

Also, don't we need a check connectivity with json config, for the udp update?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need any connectivity test for the new json config tunnel. I'd rather expose a public OutlineTunnel.IsUDPSupported() method so the caller can retrieve that info.

if tunDev == nil {
return nil, errors.New("tun device is required")
}
tn, err := newTunnelFromJSON(configJSON, tunDev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a clearer name. Perhaps this?

Suggested change
tn, err := newTunnelFromJSON(configJSON, tunDev)
proxyTun, err := newTunnelFromJSON(configJSON, tunDev)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Tunnel is a bad name, it's similar to tun device, which is very confusing.

@@ -40,7 +44,7 @@ type Tunnel interface {
}

// Deprecated: use Tunnel directly.
type OutlineTunnel = Tunnel
type OutlineTunnel Tunnel

type outlinetunnel struct {
tunnel.Tunnel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove this. I don't really think it's needed, and it's extremely confusing. Let's see what breaks and fix it. I believe the comments here will address most issues.

Suggested change
tunnel.Tunnel

//
// This function does *not* take ownership of the TUN file descriptor `fd`; the
// caller is responsible for closing after Tunnel disconnects.
func ConnectTunnel(configJSON string, fd int) (Tunnel, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ConnectTunnel function is doing different things in different platforms. That is confusing. We need to fix that.

Can you make this take a ReadWriteCloser for the tun device and move this to the shared code?

if err != nil {
return nil, err
}
tn, err := newTunnelFromJSON(configJSON, tunDev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove tunDev, and give a better name for the proxy tunnel:

Suggested change
tn, err := newTunnelFromJSON(configJSON, tunDev)
proxyTun, err := newTunnelFromJSON(configJSON)

The initialization that needs tunDev should happen below.

return nil, fmt.Errorf("unable to create tunnel from config: %w", err)
}

go tunnel.ProcessInputPackets(tn, tunDev)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use CopyBuffer. See comment on Electron code, which should be shared.

return nil, fmt.Errorf("unable to create tunnel from config: %w", err)
}

go io.CopyBuffer(tn, tunDev, make([]byte, mtu))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to see the copy both ways in the same place. This code only shows one way, which is confusing. Where's the flow from the tun device to the proxy device? It takes a lot of digging to figure that out.

I think we could implement the bridge like this:

Suggested change
go io.CopyBuffer(tn, tunDev, make([]byte, mtu))
go func() {
defer proxyTun.Close()
io.CopyBuffer(proxyDevice, tunDev, make([]byte, mtu))
}
go proxyTun.WriteTo(tunDev)

The reason for WriteTo is to avoid a layer of buffers when possible.

proxyTun.WriteTo will effectively call core.RegisterOutputFn to write to the tun device, Then wait for proxyTun.Close(). ProxyTun.Close() should call lwipStack.Close(). This way you don't need tunnel.Tunnel anymore.

@jyyi1 jyyi1 changed the title fix: 💊 make sure gomobile can export CheckConnectivity and NewClient feat: ✨ add new OutlineDevice API that uses Outline SDK Jul 24, 2023
jyyi1 added a commit that referenced this pull request Jul 25, 2023
Outline Client is referencing the following APIs (Android JNI interface as an example) exported by `gomobile`.

<img width="642" alt="image" src="https://user-images.githubusercontent.com/93548144/232151618-de7c91eb-2ee6-444e-bbe0-023262af2149.png">

But due to the recent refactoring, we introduced types that are not recognized by `gomobile`. This is the only function exported in `master` branch now:

<img width="646" alt="image" src="https://user-images.githubusercontent.com/93548144/232152396-6cf278ab-f45b-448a-adb9-c03b13772eb7.png">

In this PR, I re-exported these functions (together with the new `NewClientFromJSON`) by using the built-in types. Here is the exported Android JNI interface:

<img width="635" alt="image" src="https://user-images.githubusercontent.com/93548144/232151977-ad6bd330-4bc5-4383-b6ef-49dce4dc1853.png">

/cc @sbruens , although the required APIs are re-exported, some of the data types still need to be updated:

* [`OutlineTunnel`](https://github.com/Jigsaw-Code/outline-client/blob/master/src/cordova/android/OutlineAndroidLib/outline/src/main/java/org/outline/vpn/VpnTunnel.java#L49) -> `Tunnel`
* [`Tun2socksOutlineTunnel`](https://github.com/Jigsaw-Code/outline-client/blob/master/src/cordova/apple/OutlineAppleLib/Sources/PacketTunnelProviderSources/PacketTunnelProvider.m#L39) -> `Tun2socksTunnel`

Related PR: #118
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants