Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
osm authored and mvd-ows committed Apr 18, 2024
0 parents commit 7348dd2
Show file tree
Hide file tree
Showing 155 changed files with 127,188 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Continuous Integration

on: push

jobs:
build:
runs-on: ubuntu-latest
steps:
- run: env
- name: Checkout code
uses: actions/checkout@v2
- name: Setup go
uses: actions/setup-go@v4
with:
go-version-file: 'go.mod'
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/staticcheck@HEAD
- name: Print staticcheck version
run: staticcheck -version
- name: Run go build
run: make all
- name: Run go test
run: make test
- name: Run go vet
run: make vet
- name: Run go fmt
run: if [ "$(go fmt | wc -l)" -ne 0 ]; then exit 1; fi
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/apisocks5*
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Changelog
All notable changes are recorded here.

### Format

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).

Entries should have the imperative form, just like commit messages. Start each entry with words like
add, fix, increase, force etc.. Not added, fixed, increased, forced etc.

Line wrap the file at 100 chars. That is over here -> |

### Categories each change fall into

* **Added**: for new features.
* **Changed**: for changes in existing functionality.
* **Deprecated**: for soon-to-be removed features.
* **Removed**: for now removed features.
* **Fixed**: for any bug fixes.
* **Security**: in case of vulnerabilities.

## [1.0.0] - 2024-04-18
### Added
- Core functionality with some configurability.
14 changes: 14 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM ubuntu:22.04

ENV PATH="$PATH:/usr/local/go/bin"

# The SHA256 checksum used to verify the go archive can be found at https://go.dev/dl/

ENV GO_FILENAME=go1.22.2.linux-amd64.tar.gz
ENV GO_FILEHASH=5901c52b7a78002aeff14a21f93e0f064f74ce1360fce51c6ee68cd471216a17

RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl make git zip \
&& curl -L https://go.dev/dl/${GO_FILENAME} >/tmp/${GO_FILENAME} \
&& echo ${GO_FILEHASH} /tmp/${GO_FILENAME} | sha256sum --check \
&& tar -C /usr/local -xzf /tmp/${GO_FILENAME}
59 changes: 59 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export VERSION = ${shell git describe --tags 2>/dev/null}

BIN = apisocks5
GO_LDFLAGS = -buildid= -s -w -X main.VERSION=${VERSION}

.PHONY: all
all: ${BIN}

.PHONY: ${BIN}
${BIN}:
go build -a -trimpath -buildvcs=false -ldflags "${GO_LDFLAGS}" -o ${BIN} .

.PHONY: install
install:
go install

.PHONY: clean
clean:
rm -f ${BIN} ${BIN}*.zip ${BIN}*.asc

.PHONY: fmt
fmt:
go fmt ./...

.PHONY: vet
vet:
go vet ./...
staticcheck ./...

.PHONY: test
test:
go test -v -race ./...

.PHONY: build-container
build-container:
podman build -t ${BIN} .

.PHONY: build
build: build-container
podman run --rm -v .:/build:Z -w /build \
-e GOOS=${GOOS} -e GOARCH=${GOARCH} \
-it ${BIN} \
sh -c 'make BIN=${BIN}${EXT} && zip ${BIN}_${VERSION}_${GOOS}_${GOARCH}.zip ${BIN}${EXT}'

.PHONY: release-darwin-amd64
release-darwin-amd64:
$(MAKE) GOOS=darwin GOARCH=amd64 build

.PHONY: release-darwin-arm64
release-darwin-arm64:
$(MAKE) GOOS=darwin GOARCH=arm64 build

.PHONY: release-linux-amd64
release-linux-amd64:
$(MAKE) GOOS=linux GOARCH=amd64 build

.PHONY: release-windows-amd64
release-windows-amd64:
$(MAKE) GOOS=windows GOARCH=amd64 EXT=.exe build
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# apisocks5

This is a small SOCKS5 proxy designed to be used in conjunction with the
Mullvad VPN app for accessing the Mullvad API from restricted locations.
4 changes: 4 additions & 0 deletions artifacts.list
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apisocks5_v?.?.?_darwin_amd64.zip
apisocks5_v?.?.?_darwin_arm64.zip
apisocks5_v?.?.?_linux_amd64.zip
apisocks5_v?.?.?_windows_amd64.zip
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/mullvad/apisocks5

go 1.22.2

replace github.com/mullvad/ipv6md => ./ipv6md

replace github.com/mullvad/proxy => ./proxy

require (
github.com/mullvad/ipv6md v0.0.0-00010101000000-000000000000 // indirect
github.com/mullvad/proxy v0.0.0-00010101000000-000000000000
)

require (
github.com/likexian/doh-go v0.6.4 // indirect
github.com/likexian/gokit v0.25.13 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/text v0.14.0 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
github.com/likexian/doh-go v0.6.4 h1:UnTrIVAOwkBvKU6qOt2W3C5yC9/YO02UVPPcN26iZDY=
github.com/likexian/doh-go v0.6.4/go.mod h1:9jHpL/WPYmOM8+93RwXDf5TpZZwQjHrmIglXmjHpLlA=
github.com/likexian/gokit v0.21.11/go.mod h1:0WlTw7IPdiMtrwu0t5zrLM7XXik27Ey6MhUJHio2fVo=
github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM=
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
58 changes: 58 additions & 0 deletions ipv6md/addrport/addrport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package addrport

import (
"encoding/binary"
"errors"
"net"
"net/netip"

"github.com/mullvad/ipv6md"
"github.com/mullvad/ipv6md/utils"
)

var (
ErrAddrPortInvalidIP = errors.New("invalid ip")
ErrAddrPortInvalidIPLen = errors.New("invalid ip length")
)

// Encode encodes an ipv4:port into an IPv6 address.
func Encode(addrPort string) (net.IP, error) {
ap, err := netip.ParseAddrPort(addrPort)
if err != nil {
return nil, err
}

data := [16]byte{ipv6md.IPv6Prefix[0], ipv6md.IPv6Prefix[1]}

binary.LittleEndian.PutUint16(data[2:4], ipv6md.AddrPort.ToUint16())

addr := ap.Addr()
addrBytes := addr.AsSlice()
copy(data[4:8], addrBytes)

port := ap.Port()
binary.LittleEndian.PutUint16(data[8:10], port)

return net.IP(data[:]), nil
}

// Decode assumes an IPv4 address and port has been encoded within the
// IPv6 address and returns a netip.AddrPort with the information.
func Decode(ip net.IP) (netip.AddrPort, error) {
var addrPort netip.AddrPort

if ip == nil {
return addrPort, ErrAddrPortInvalidIP
}

data := []byte(ip.To16())
if len(data) != 16 {
return addrPort, ErrAddrPortInvalidIPLen
}

addr := utils.ToNetIPAddr(data[4:8])
port := binary.LittleEndian.Uint16(data[8:10])
addrPort = netip.AddrPortFrom(addr, port)

return addrPort, nil
}
41 changes: 41 additions & 0 deletions ipv6md/addrport/addrport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package addrport

import (
"testing"
)

type addrPortTest struct {
input string
encoded string
}

var addrPortTests = []addrPortTest{
{
input: "127.0.0.1:1337",
encoded: "2001:100:7f00:1:3905::",
},
{
input: "192.168.1.1:443",
encoded: "2001:100:c0a8:101:bb01::",
},
}

func TestAddrPort(t *testing.T) {
for _, test := range addrPortTests {
t.Run(test.input, func(t *testing.T) {
encoded, _ := Encode(test.input)
if test.encoded != encoded.String() {
t.Errorf("unexpected encoding result")
t.Logf("actual: %#v", encoded.String())
t.Logf("expected: %#v", test.encoded)
}

decoded, _ := Decode(encoded)
if test.input != decoded.String() {
t.Errorf("unexpected decoding result")
t.Logf("actual: %#v", decoded.String())
t.Logf("expected: %#v", test.input)
}
})
}
}
95 changes: 95 additions & 0 deletions ipv6md/addrportxor/addrportxor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Encapsulate an address and port together with a basic XOR definition
//
// Format description:
//
// Offsets Description
// ------- -----------
// 00 - 02 Dummy header to make it look like a real IPv6 address
// 02 - 04 Type
// 04 - 08 IPv4 address
// 08 - 10 Port number
// 10 - 12 Number of bytes to XOR; 0x0 means XOR everything
// 12 - 16 Key, where 0x0 marks the end of the key

package addrportxor

import (
"encoding/binary"
"errors"
"net"
"net/netip"

"github.com/mullvad/ipv6md"
"github.com/mullvad/ipv6md/addrport"
"github.com/mullvad/ipv6md/utils"
)

var (
ErrInvalidKeyLength = errors.New("invalid key length")
ErrInvalidKey = errors.New("invalid key")
)

// DecodedAddrPortXOR is used by the Decode function to encapsulate the
// returned values.
type DecodedAddrPortXOR struct {
AddrPort netip.AddrPort
XORBytes uint16
XORKey []byte
}

// Encode encodes the given address, port and XOR encryption details in an IPv6
// formatted slice of bytes.
func Encode(addrPort string, xorBytes uint16, xorKey []byte) (net.IP, error) {
if len(xorKey) == 0 || len(xorKey) > 4 {
return nil, ErrInvalidKeyLength
}

data, err := addrport.Encode(addrPort)
if err != nil {
return nil, err
}

binary.LittleEndian.PutUint16(data[2:4], ipv6md.AddrPortXOR.ToUint16())
binary.LittleEndian.PutUint16(data[10:12], xorBytes)
copy(data[12:16], xorKey)

return net.IP(data[:]), nil
}

// Decode assumes an IPv4 address and port, along with a 16-bit integer and a
// 1-4 bytes long key, has been encoded within the IPv6 address. It returns a
// netip.AddrPort with the information and the XOR bytes and key.
func Decode(ip net.IP) (*DecodedAddrPortXOR, error) {
if ip == nil {
return nil, addrport.ErrAddrPortInvalidIP
}

data := []byte(ip.To16())
if len(data) != 16 {
return nil, addrport.ErrAddrPortInvalidIPLen
}

addr := utils.ToNetIPAddr(data[4:8])
port := binary.LittleEndian.Uint16(data[8:10])
ap := netip.AddrPortFrom(addr, port)

xorBytes := binary.LittleEndian.Uint16(data[10:12])

var xorKey []byte
for _, b := range data[12:16] {
if b == 0x00 {
continue
}

xorKey = append(xorKey, b)
}
if len(xorKey) == 0 {
return nil, ErrInvalidKey
}

return &DecodedAddrPortXOR{
AddrPort: ap,
XORBytes: xorBytes,
XORKey: xorKey,
}, nil
}
Loading

0 comments on commit 7348dd2

Please sign in to comment.