Skip to content

Commit

Permalink
Initial SDK code (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
fortuna authored Mar 31, 2023
1 parent 1f2bd43 commit 8dfb0a4
Show file tree
Hide file tree
Showing 26 changed files with 2,633 additions and 2 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Build and Test

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions: # added using https://github.com/step-security/secure-workflows
contents: read

jobs:

build:
name: Build
runs-on: ubuntu-latest
steps:

- name: Set up Go 1.20
uses: actions/setup-go@v3
with:
go-version: ^1.20

- name: Check out code into the Go module directory
uses: actions/checkout@v3

- name: Build
run: go build -v ./...

- name: Test
run: go test -v -race -bench=. ./... -benchtime=100ms
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,30 @@
# outline-internal-sdk
SDK to build network tools based on Outline components.
# Outline SDK (Under Development, DO NOT USE)

![Build Status](https://github.com/Jigsaw-Code/outline-internal-sdk/actions/workflows/test.yml/badge.svg)
[![Go Report Card](https://goreportcard.com/badge/github.com/Jigsaw-Code/outline-internal-sdk)](https://goreportcard.com/report/github.com/Jigsaw-Code/outline-internal-sdk)
[![Go Reference](https://pkg.go.dev/badge/github.com/Jigsaw-Code/outline-internal-sdk.svg)](https://pkg.go.dev/github.com/Jigsaw-Code/outline-internal-sdk)

[![Mattermost](https://badgen.net/badge/Mattermost/Outline%20Community/blue)](https://community.internetfreedomfestival.org/community/channels/outline-community)
[![Reddit](https://badgen.net/badge/Reddit/r%2Foutlinevpn/orange)](https://www.reddit.com/r/outlinevpn/)

This is the repository to hold the future Outline SDK as we develop it. The goal is to clean up and move reusable code from [outline-ss-server](https://github.com/Jigsaw-Code/outline-ss-server) and [outline-go-tun2socks](https://github.com/Jigsaw-Code/outline-go-tun2socks).

**WARNING: This code is not ready to be used by the public. There's no guarantee of stability.**

Tentative roadmap:

- Transport libraries
- [x] Generic transport client primitives (`StreamDialer`, `PacketListener` and Endpoints)
- [x] TCP and UDP client implementations
- [x] Shadowsocks client implementations
- [ ] Generic transport server primitives (TBD)
- [ ] TCP and UDP server implementations
- [ ] Shadowsocks server implementations
- [ ] Utility implementations (`ReplaceablePacketListener`, `TruncateDNSPacketListener`)

- Network libraries
- [ ] Generic network primitives (TBD, something like a generic TUN device)
- [ ] Implementation based on go-tun2socks

- VPN API
- [ ] VPN API for desktop (Linux, Windows, macOS)
19 changes: 19 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module github.com/Jigsaw-Code/outline-internal-sdk

go 1.20

require (
github.com/shadowsocks/go-shadowsocks2 v0.1.5
github.com/stretchr/testify v1.8.2
golang.org/x/crypto v0.7.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
golang.org/x/sys v0.6.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
38 changes: 38 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28=
github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
88 changes: 88 additions & 0 deletions internal/slicepool/slicepool.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2020 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package slicepool

import (
"sync"
)

// Pool wraps a sync.Pool of *[]byte. To encourage correct usage,
// all public methods are on slicepool.LazySlice.
//
// All copies of a Pool refer to the same underlying pool.
//
// "*[]byte" is used to avoid a heap allocation when passing a
// []byte to sync.Pool.Put, which leaks its argument to the heap.
type Pool struct {
pool *sync.Pool
len int
}

// MakePool returns a Pool of slices with the specified length.
func MakePool(sliceLen int) Pool {
return Pool{
pool: &sync.Pool{
New: func() interface{} {
slice := make([]byte, sliceLen)
// Return a *[]byte instead of []byte ensures that
// the []byte is not copied, which would cause a heap
// allocation on every call to sync.pool.Put
return &slice
},
},
len: sliceLen,
}
}

func (p *Pool) get() *[]byte {
return p.pool.Get().(*[]byte)
}

func (p *Pool) put(b *[]byte) {
if len(*b) != p.len || cap(*b) != p.len {
panic("Buffer length mismatch")
}
p.pool.Put(b)
}

// LazySlice returns an empty LazySlice tied to this Pool.
func (p *Pool) LazySlice() LazySlice {
return LazySlice{pool: p}
}

// LazySlice holds 0 or 1 slices from a particular Pool.
type LazySlice struct {
slice *[]byte
pool *Pool
}

// Acquire this slice from the pool and return it.
// This slice must not already be acquired.
func (b *LazySlice) Acquire() []byte {
if b.slice != nil {
panic("buffer already acquired")
}
b.slice = b.pool.get()
return *b.slice

This comment has been minimized.

Copy link
@ignoramous

ignoramous Jun 27, 2024

I've seen some buffer pool impl (ex) extend len until cap,

arr := *b.slice
arr[:cap(arr)]

While other impls suggesting truncating the slice to 0 len ex:

*b.slice := (*b.slice)[:0]
}

// Release the buffer back to the pool, unless the box is empty.
// The caller must discard any references to the buffer.
func (b *LazySlice) Release() {
if b.slice != nil {
b.pool.put(b.slice)
b.slice = nil
}
}
38 changes: 38 additions & 0 deletions internal/slicepool/slicepool_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2020 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package slicepool

import (
"testing"
)

func TestPool(t *testing.T) {
pool := MakePool(10)
slice := pool.LazySlice()
buf := slice.Acquire()
if len(buf) != 10 {
t.Errorf("Wrong slice length: %d", len(buf))
}
slice.Release()
}

func BenchmarkPool(b *testing.B) {
pool := MakePool(10)
for i := 0; i < b.N; i++ {
slice := pool.LazySlice()
slice.Acquire()
slice.Release()
}
}
48 changes: 48 additions & 0 deletions transport/packet.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2019 Jigsaw Operations LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package transport

import (
"context"
"net"
)

// PacketEndpoint represents an endpoint that can be used to established packet connections (like UDP)
type PacketEndpoint interface {
// Connect creates a connection bound to an endpoint, returning the connection.
Connect(ctx context.Context) (net.Conn, error)
}

// PacketListener provides a way to create a local unbound packet connection to send packets to different destinations.
type PacketListener interface {
// ListenPacket creates a PacketConn that can be used to relay packets (such as UDP) through some proxy.
ListenPacket(ctx context.Context) (net.PacketConn, error)
}

// UDPEndpoint is a PacketEndpoint that connects to the given address via UDP
type UDPEndpoint struct {
// The Dialer used to create the net.Conn on Connect().
Dialer net.Dialer
// The remote address to pass to Dial.
RemoteAddr net.UDPAddr
}

func (e UDPEndpoint) Connect(ctx context.Context) (net.Conn, error) {
conn, err := e.Dialer.DialContext(ctx, "udp", e.RemoteAddr.String())
if err != nil {
return nil, err
}
return conn, nil
}
Loading

0 comments on commit 8dfb0a4

Please sign in to comment.