Merge pull request 'develop' (#3) from develop into master
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #3
This commit is contained in:
commit
b8dfe35300
50
.drone.yml
Normal file
50
.drone.yml
Normal file
@ -0,0 +1,50 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: install
|
||||
image: golang:1.15
|
||||
environment:
|
||||
GOPROXY: http://10.20.0.25:3142|direct
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test -i ./...
|
||||
|
||||
- name: test
|
||||
image: golang:1.15
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go test ./...
|
||||
|
||||
- name: build (debian)
|
||||
image: golang:1.15-buster
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /go
|
||||
commands:
|
||||
- go build
|
||||
|
||||
- name: upload
|
||||
image: minio/mc
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
environment:
|
||||
ACCESS_KEY: 001f185da4dcda3000000000d
|
||||
SECRET_KEY:
|
||||
from_secret: s3_secret_key
|
||||
commands:
|
||||
- mc alias set s3 http://10.20.0.25:3900 $${ACCESS_KEY} $${SECRET_KEY}
|
||||
- mc cp mpbl3p s3/dissertation/binaries/debian/${DRONE_BRANCH}
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
temp: {}
|
117
.gitignore
vendored
Normal file
117
.gitignore
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
config.ini
|
||||
logs/
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,go
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,go
|
||||
|
||||
### Go ###
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Go Patch ###
|
||||
/vendor/
|
||||
/Godeps/
|
||||
|
||||
### Intellij+all ###
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Intellij+all Patch ###
|
||||
# Ignores the whole .idea folder and all .iml files
|
||||
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
|
||||
|
||||
.idea/
|
||||
|
||||
# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
|
||||
|
||||
*.iml
|
||||
modules.xml
|
||||
.idea/misc.xml
|
||||
*.ipr
|
||||
|
||||
# Sonarlint plugin
|
||||
.idea/sonarlint
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,go
|
15
Makefile
Normal file
15
Makefile
Normal file
@ -0,0 +1,15 @@
|
||||
manual:
|
||||
docker run --rm -v /tmp:/tmp -v ${PWD}:/app -w /app golang:1.15-buster go build -o /tmp/mpbl3p
|
||||
rsync -p /tmp/mpbl3p 10.21.12.101:
|
||||
rsync -p /tmp/mpbl3p 10.21.12.102:
|
||||
|
||||
manual-bsd:
|
||||
GOOS=freebsd go build -o /tmp/mpbl3p
|
||||
rsync -p /tmp/mpbl3p 10.21.12.121:
|
||||
rsync -p /tmp/mpbl3p 10.21.12.122:
|
||||
|
||||
getlogs:
|
||||
sftp 10.21.12.100:tcpdump.pcap logs/speedtest.pcap
|
||||
sftp 10.21.12.101:tcpdump.pcap logs/remote.pcap
|
||||
sftp 10.21.12.102:tcpdump.pcap logs/local.pcap
|
||||
sftp 10.21.12.103:tcpdump.pcap logs/client.pcap
|
16
README.md
16
README.md
@ -1,2 +1,18 @@
|
||||
# A Multi-Path Bidirectional Layer 3 Proxy
|
||||
|
||||
## Setup Notes
|
||||
### Linux
|
||||
#### Policy Based Routing
|
||||
|
||||
ip route flush 11
|
||||
ip route add table 11 to 1.1.1.0/24 dev eth1
|
||||
ip rule add from 1.1.1.4 table 11 priority 11
|
||||
|
||||
ip route flush 10
|
||||
ip route add table 10 to 1.1.1.0/24 dev eth2
|
||||
ip rule add from 1.1.1.5 table 10 priority 10
|
||||
|
||||
#### ARP Flux
|
||||
|
||||
sysctl -w net.ipv4.conf.all.arp_announce=1
|
||||
sysctl -w net.ipv4.conf.all.arp_ignore=2
|
||||
|
73
config/builder.go
Normal file
73
config/builder.go
Normal file
@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"mpbl3p/proxy"
|
||||
"mpbl3p/tcp"
|
||||
"mpbl3p/tun"
|
||||
)
|
||||
|
||||
// TODO: Delete this code as soon as an alternative is available
|
||||
type UselessMac struct{}
|
||||
|
||||
func (UselessMac) CodeLength() int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (UselessMac) Generate([]byte) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u UselessMac) Verify([]byte, []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c Configuration) Build() (*proxy.Proxy, error) {
|
||||
p := proxy.NewProxy(0)
|
||||
p.Generator = UselessMac{}
|
||||
|
||||
if c.Host.InterfaceName == "" {
|
||||
c.Host.InterfaceName = "nc%d"
|
||||
}
|
||||
|
||||
ss, err := tun.NewTun(c.Host.InterfaceName, 1500)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Source = ss
|
||||
p.Sink = ss
|
||||
|
||||
for _, peer := range c.Peers {
|
||||
switch peer.Method {
|
||||
case "TCP":
|
||||
err := buildTcp(p, peer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func buildTcp(p *proxy.Proxy, peer Peer) error {
|
||||
if peer.RemoteHost != "" {
|
||||
f, err := tcp.InitiateFlow(
|
||||
fmt.Sprintf("%s:", peer.LocalHost),
|
||||
fmt.Sprintf("%s:%d", peer.RemoteHost, peer.RemotePort),
|
||||
)
|
||||
|
||||
p.AddConsumer(f)
|
||||
p.AddProducer(f, UselessMac{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err := tcp.NewListener(p, fmt.Sprintf("%s:%d", peer.LocalHost, peer.LocalPort), UselessMac{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
34
config/config.go
Normal file
34
config/config.go
Normal file
@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
var v = validator.New()
|
||||
|
||||
type Configuration struct {
|
||||
Host Host
|
||||
Peers []Peer `validate:"dive"`
|
||||
}
|
||||
|
||||
type Host struct {
|
||||
PrivateKey string `validate:"required"`
|
||||
InterfaceName string
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
PublicKey string `validate:"required"`
|
||||
Method string `validate:"oneof=TCP"`
|
||||
|
||||
LocalHost string `validate:"omitempty,ip"`
|
||||
LocalPort uint `validate:"max=65535"`
|
||||
|
||||
RemoteHost string `validate:"required_with=RemotePort,omitempty,fqdn|ip"`
|
||||
RemotePort uint `validate:"required_with=RemoteHost,omitempty,max=65535"`
|
||||
|
||||
KeepAlive uint
|
||||
Timeout uint
|
||||
RetryWait uint
|
||||
}
|
||||
|
||||
func (c Configuration) Validate() error {
|
||||
return v.Struct(c)
|
||||
}
|
42
config/loader.go
Normal file
42
config/loader.go
Normal file
@ -0,0 +1,42 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
func LoadConfig(path string) (c Configuration, err error) {
|
||||
var file *ini.File
|
||||
file, err = ini.LoadSources(ini.LoadOptions{
|
||||
AllowShadows: true,
|
||||
AllowNonUniqueSections: true,
|
||||
}, path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if s := file.Section("Host"); s != nil {
|
||||
err = s.MapTo(&c.Host)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var sections []*ini.Section
|
||||
sections, err = file.SectionsByName("Peer")
|
||||
|
||||
for _, s := range sections {
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
p := Peer{}
|
||||
err = s.MapTo(&p)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
c.Peers = append(c.Peers, p)
|
||||
}
|
||||
|
||||
err = c.Validate()
|
||||
return
|
||||
}
|
12
go.mod
Normal file
12
go.mod
Normal file
@ -0,0 +1,12 @@
|
||||
module mpbl3p
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/go-playground/assert/v2 v2.0.1
|
||||
github.com/go-playground/validator/v10 v10.4.1
|
||||
github.com/pkg/taptun v0.0.0-20160424131934-bbbd335672ab
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
gopkg.in/ini.v1 v1.62.0
|
||||
)
|
45
go.sum
Normal file
45
go.sum
Normal file
@ -0,0 +1,45 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/pkg/taptun v0.0.0-20160424131934-bbbd335672ab h1:dAXDRtXYxj4sTR5WeRuTFJGH18QMT6AUpUgRwedI6es=
|
||||
github.com/pkg/taptun v0.0.0-20160424131934-bbbd335672ab/go.mod h1:N5a/Ll2ZNk5wjiLNW9LIiNtO9RNYcaYmcXSYKMYrlDg=
|
||||
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
36
main.go
Normal file
36
main.go
Normal file
@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mpbl3p/config"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.Llongfile)
|
||||
|
||||
log.Println("loading config...")
|
||||
|
||||
c, err := config.LoadConfig("config.ini")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("building config...")
|
||||
p, err := c.Build()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
log.Println("starting...")
|
||||
p.Start()
|
||||
|
||||
log.Println("running")
|
||||
|
||||
signals := make(chan os.Signal)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
||||
<-signals
|
||||
}
|
67
mocks/conn.go
Normal file
67
mocks/conn.go
Normal file
@ -0,0 +1,67 @@
|
||||
package mocks
|
||||
|
||||
import "time"
|
||||
|
||||
type MockPerfectBiConn struct {
|
||||
directionA chan byte
|
||||
directionB chan byte
|
||||
}
|
||||
|
||||
func NewMockPerfectBiConn(bufSize int) MockPerfectBiConn {
|
||||
return MockPerfectBiConn{
|
||||
directionA: make(chan byte, bufSize),
|
||||
directionB: make(chan byte, bufSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (bc MockPerfectBiConn) SideA() MockPerfectConn {
|
||||
return MockPerfectConn{inbound: bc.directionA, outbound: bc.directionB}
|
||||
}
|
||||
|
||||
func (bc MockPerfectBiConn) SideB() MockPerfectConn {
|
||||
return MockPerfectConn{inbound: bc.directionB, outbound: bc.directionA}
|
||||
}
|
||||
|
||||
type MockPerfectConn struct {
|
||||
inbound chan byte
|
||||
outbound chan byte
|
||||
}
|
||||
|
||||
func (c MockPerfectConn) SetWriteDeadline(time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c MockPerfectConn) Read(p []byte) (n int, err error) {
|
||||
for i := range p {
|
||||
if i == 0 {
|
||||
p[i] = <-c.inbound
|
||||
} else {
|
||||
select {
|
||||
case b := <-c.inbound:
|
||||
p[i] = b
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c MockPerfectConn) Write(p []byte) (n int, err error) {
|
||||
for _, b := range p {
|
||||
c.outbound <- b
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (c MockPerfectConn) NonBlockingRead(p []byte) (n int, err error) {
|
||||
for i := range p {
|
||||
select {
|
||||
case b := <-c.inbound:
|
||||
p[i] = b
|
||||
default:
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
20
mocks/mac.go
Normal file
20
mocks/mac.go
Normal file
@ -0,0 +1,20 @@
|
||||
package mocks
|
||||
|
||||
import "mpbl3p/shared"
|
||||
|
||||
type AlmostUselessMac struct{}
|
||||
|
||||
func (AlmostUselessMac) CodeLength() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
func (AlmostUselessMac) Generate([]byte) []byte {
|
||||
return []byte{'a', 'b', 'c', 'd'}
|
||||
}
|
||||
|
||||
func (u AlmostUselessMac) Verify(_, sum []byte) error {
|
||||
if !(sum[0] == 'a' && sum[1] == 'b' && sum[2] == 'c' && sum[3] == 'd') {
|
||||
return shared.ErrBadChecksum
|
||||
}
|
||||
return nil
|
||||
}
|
11
proxy/mac.go
Normal file
11
proxy/mac.go
Normal file
@ -0,0 +1,11 @@
|
||||
package proxy
|
||||
|
||||
type MacGenerator interface {
|
||||
CodeLength() int
|
||||
Generate([]byte) []byte
|
||||
}
|
||||
|
||||
type MacVerifier interface {
|
||||
CodeLength() int
|
||||
Verify(data []byte, sum []byte) error
|
||||
}
|
60
proxy/packet.go
Normal file
60
proxy/packet.go
Normal file
@ -0,0 +1,60 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
Data []byte
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
// create a packet from the raw data of an IP packet
|
||||
func NewPacket(data []byte) Packet {
|
||||
return Packet{
|
||||
Data: data,
|
||||
timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// rebuild a packet from the wrapped format
|
||||
func UnmarshalPacket(raw []byte, verifier MacVerifier) (Packet, error) {
|
||||
// the MAC is the last N bytes
|
||||
data := raw[:len(raw)-verifier.CodeLength()]
|
||||
sum := raw[len(raw)-verifier.CodeLength():]
|
||||
|
||||
if err := verifier.Verify(data, sum); err != nil {
|
||||
return Packet{}, err
|
||||
}
|
||||
|
||||
p := Packet{
|
||||
Data: data[:len(data)-8],
|
||||
}
|
||||
|
||||
unixTime := int64(binary.LittleEndian.Uint64(data[len(data)-8:]))
|
||||
p.timestamp = time.Unix(unixTime, 0)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// get the raw data of the IP packet
|
||||
func (p Packet) Raw() []byte {
|
||||
return p.Data
|
||||
}
|
||||
|
||||
// produce the wrapped format of a packet
|
||||
func (p Packet) Marshal(generator MacGenerator) []byte {
|
||||
// length of data + length of timestamp (8 byte) + length of checksum
|
||||
slice := make([]byte, len(p.Data)+8+generator.CodeLength())
|
||||
|
||||
copy(slice, p.Data)
|
||||
|
||||
unixTime := uint64(p.timestamp.Unix())
|
||||
binary.LittleEndian.PutUint64(slice[len(p.Data):], unixTime)
|
||||
|
||||
mac := generator.Generate(slice)
|
||||
copy(slice[len(p.Data)+8:], mac)
|
||||
|
||||
return slice
|
||||
}
|
34
proxy/packet_test.go
Normal file
34
proxy/packet_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"mpbl3p/mocks"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPacket_Marshal(t *testing.T) {
|
||||
testContent := []byte("A test string is the content of this packet.")
|
||||
testPacket := NewPacket(testContent)
|
||||
testMac := mocks.AlmostUselessMac{}
|
||||
|
||||
t.Run("Length", func(t *testing.T) {
|
||||
marshalled := testPacket.Marshal(testMac)
|
||||
|
||||
assert.Len(t, marshalled, len(testContent)+8+4)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnmarshalPacket(t *testing.T) {
|
||||
testContent := []byte("A test string is the content of this packet.")
|
||||
testPacket := NewPacket(testContent)
|
||||
testMac := mocks.AlmostUselessMac{}
|
||||
testMarshalled := testPacket.Marshal(testMac)
|
||||
|
||||
t.Run("Length", func(t *testing.T) {
|
||||
p, err := UnmarshalPacket(testMarshalled, testMac)
|
||||
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, p.Raw(), len(testContent))
|
||||
})
|
||||
}
|
128
proxy/proxy.go
Normal file
128
proxy/proxy.go
Normal file
@ -0,0 +1,128 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Producer interface {
|
||||
IsAlive() bool
|
||||
Produce(MacVerifier) (Packet, error)
|
||||
}
|
||||
|
||||
type Consumer interface {
|
||||
IsAlive() bool
|
||||
Consume(Packet, MacGenerator) error
|
||||
}
|
||||
|
||||
type Reconnectable interface {
|
||||
Reconnect() error
|
||||
}
|
||||
|
||||
type Source interface {
|
||||
Source() (Packet, error)
|
||||
}
|
||||
|
||||
type Sink interface {
|
||||
Sink(packet Packet) error
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
Source Source
|
||||
Sink Sink
|
||||
|
||||
Generator MacGenerator
|
||||
|
||||
proxyChan chan Packet
|
||||
sinkChan chan Packet
|
||||
}
|
||||
|
||||
func NewProxy(bufferSize int) *Proxy {
|
||||
return &Proxy{
|
||||
proxyChan: make(chan Packet, bufferSize),
|
||||
sinkChan: make(chan Packet, bufferSize),
|
||||
}
|
||||
}
|
||||
|
||||
func (p Proxy) Start() {
|
||||
go func() {
|
||||
for {
|
||||
if packet, err := p.Source.Source(); err != nil {
|
||||
panic(err)
|
||||
return
|
||||
} else {
|
||||
p.proxyChan <- packet
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
packet := <-p.sinkChan
|
||||
if err := p.Sink.Sink(packet); err != nil {
|
||||
panic(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p Proxy) AddConsumer(c Consumer) {
|
||||
go func() {
|
||||
_, reconnectable := c.(Reconnectable)
|
||||
|
||||
for once := true; reconnectable || once; once = false {
|
||||
if reconnectable {
|
||||
var err error
|
||||
for once := true; err != nil || once; once = false {
|
||||
log.Printf("attempting to connect `%v`\n", c)
|
||||
err = c.(Reconnectable).Reconnect()
|
||||
if !once {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
log.Printf("connected `%v`\n", c)
|
||||
}
|
||||
|
||||
for c.IsAlive() {
|
||||
if err := c.Consume(<-p.proxyChan, p.Generator); err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("closed connection `%v`\n", c)
|
||||
}()
|
||||
}
|
||||
|
||||
func (p Proxy) AddProducer(pr Producer, v MacVerifier) {
|
||||
go func() {
|
||||
_, reconnectable := pr.(Reconnectable)
|
||||
|
||||
for once := true; reconnectable || once; once = false {
|
||||
if reconnectable {
|
||||
var err error
|
||||
for once := true; err != nil || once; once = false {
|
||||
log.Printf("attempting to connect `%v`\n", pr)
|
||||
err = pr.(Reconnectable).Reconnect()
|
||||
if !once {
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
log.Printf("connected `%v`\n", pr)
|
||||
}
|
||||
|
||||
for pr.IsAlive() {
|
||||
if packet, err := pr.Produce(v); err != nil {
|
||||
log.Println(err)
|
||||
break
|
||||
} else {
|
||||
p.sinkChan <- packet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("closed connection `%v`\n", pr)
|
||||
}()
|
||||
}
|
6
shared/errors.go
Normal file
6
shared/errors.go
Normal file
@ -0,0 +1,6 @@
|
||||
package shared
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrBadChecksum = errors.New("the packet had a bad checksum")
|
||||
var ErrDeadConnection = errors.New("the connection is dead")
|
157
tcp/flow.go
Normal file
157
tcp/flow.go
Normal file
@ -0,0 +1,157 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"mpbl3p/proxy"
|
||||
"mpbl3p/shared"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrNotEnoughBytes = errors.New("not enough bytes")
|
||||
|
||||
type Conn interface {
|
||||
Read(b []byte) (n int, err error)
|
||||
Write(b []byte) (n int, err error)
|
||||
SetWriteDeadline(time.Time) error
|
||||
}
|
||||
|
||||
type InitiatedFlow struct {
|
||||
Local string
|
||||
Remote string
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
Flow
|
||||
}
|
||||
|
||||
type Flow struct {
|
||||
conn Conn
|
||||
isAlive bool
|
||||
}
|
||||
|
||||
func InitiateFlow(local, remote string) (*InitiatedFlow, error) {
|
||||
f := InitiatedFlow{
|
||||
Local: local,
|
||||
Remote: remote,
|
||||
}
|
||||
|
||||
return &f, nil
|
||||
}
|
||||
|
||||
func (f *InitiatedFlow) Reconnect() error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if f.isAlive {
|
||||
return nil
|
||||
}
|
||||
|
||||
localAddr, err := net.ResolveTCPAddr("tcp", f.Local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
remoteAddr, err := net.ResolveTCPAddr("tcp", f.Remote)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conn, err := net.DialTCP("tcp", localAddr, remoteAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = conn.SetWriteBuffer(0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.conn = conn
|
||||
f.isAlive = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Flow) IsAlive() bool {
|
||||
return f.isAlive
|
||||
}
|
||||
|
||||
func (f *InitiatedFlow) Consume(p proxy.Packet, g proxy.MacGenerator) error {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
|
||||
return f.Flow.Consume(p, g)
|
||||
}
|
||||
|
||||
func (f *Flow) Consume(p proxy.Packet, g proxy.MacGenerator) (err error) {
|
||||
if !f.isAlive {
|
||||
return shared.ErrDeadConnection
|
||||
}
|
||||
|
||||
data := p.Marshal(g)
|
||||
err = f.consumeMarshalled(data)
|
||||
if err != nil {
|
||||
f.isAlive = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *Flow) consumeMarshalled(data []byte) error {
|
||||
prefixedData := make([]byte, len(data)+4)
|
||||
binary.LittleEndian.PutUint32(prefixedData, uint32(len(data)))
|
||||
copy(prefixedData[4:], data)
|
||||
|
||||
err := f.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.conn.Write(prefixedData)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *InitiatedFlow) Produce(v proxy.MacVerifier) (proxy.Packet, error) {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
|
||||
return f.Flow.Produce(v)
|
||||
}
|
||||
|
||||
func (f *Flow) Produce(v proxy.MacVerifier) (proxy.Packet, error) {
|
||||
if !f.isAlive {
|
||||
return proxy.Packet{}, shared.ErrDeadConnection
|
||||
}
|
||||
|
||||
data, err := f.produceMarshalled()
|
||||
if err != nil {
|
||||
f.isAlive = false
|
||||
return proxy.Packet{}, err
|
||||
}
|
||||
|
||||
return proxy.UnmarshalPacket(data, v)
|
||||
}
|
||||
|
||||
func (f *Flow) produceMarshalled() ([]byte, error) {
|
||||
lengthBytes := make([]byte, 4)
|
||||
if n, err := io.LimitReader(f.conn, 4).Read(lengthBytes); err != nil {
|
||||
return nil, err
|
||||
} else if n != 4 {
|
||||
return nil, ErrNotEnoughBytes
|
||||
}
|
||||
|
||||
length := binary.LittleEndian.Uint32(lengthBytes)
|
||||
dataBytes := make([]byte, length)
|
||||
|
||||
var read uint32
|
||||
for read < length {
|
||||
if n, err := io.LimitReader(f.conn, int64(length-read)).Read(dataBytes[read:]); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
read += uint32(n)
|
||||
}
|
||||
}
|
||||
|
||||
return dataBytes, nil
|
||||
}
|
66
tcp/flow_test.go
Normal file
66
tcp/flow_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/go-playground/assert/v2"
|
||||
"github.com/stretchr/testify/require"
|
||||
"mpbl3p/mocks"
|
||||
"mpbl3p/proxy"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFlow_Consume(t *testing.T) {
|
||||
testContent := []byte("A test string is the content of this packet.")
|
||||
testPacket := proxy.NewPacket(testContent)
|
||||
testMac := mocks.AlmostUselessMac{}
|
||||
|
||||
t.Run("Length", func(t *testing.T) {
|
||||
testConn := mocks.NewMockPerfectBiConn(100)
|
||||
|
||||
flowA := Flow{conn: testConn.SideA(), isAlive: true}
|
||||
|
||||
err := flowA.Consume(testPacket, testMac)
|
||||
require.Nil(t, err)
|
||||
|
||||
buf := make([]byte, 100)
|
||||
n, err := testConn.SideB().Read(buf)
|
||||
require.Nil(t, err)
|
||||
|
||||
assert.Equal(t, len(testContent)+8+4+4, n)
|
||||
assert.Equal(t, uint32(len(testContent)+8+4), binary.LittleEndian.Uint32(buf[:len(buf)-4]))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFlow_Produce(t *testing.T) {
|
||||
testContent := "A test string is the content of this packet."
|
||||
testMarshalled := []byte("0000" + testContent + "00000000abcd")
|
||||
binary.LittleEndian.PutUint32(testMarshalled, uint32(len(testMarshalled)-4))
|
||||
|
||||
testMac := mocks.AlmostUselessMac{}
|
||||
|
||||
t.Run("Length", func(t *testing.T) {
|
||||
testConn := mocks.NewMockPerfectBiConn(100)
|
||||
|
||||
flowA := Flow{conn: testConn.SideA(), isAlive: true}
|
||||
|
||||
_, err := testConn.SideB().Write(testMarshalled)
|
||||
require.Nil(t, err)
|
||||
|
||||
p, err := flowA.Produce(testMac)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, len(testContent), len(p.Raw()))
|
||||
})
|
||||
|
||||
t.Run("Value", func(t *testing.T) {
|
||||
testConn := mocks.NewMockPerfectBiConn(100)
|
||||
|
||||
flowA := Flow{conn: testConn.SideA(), isAlive: true}
|
||||
|
||||
_, err := testConn.SideB().Write(testMarshalled)
|
||||
require.Nil(t, err)
|
||||
|
||||
p, err := flowA.Produce(testMac)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, testContent, string(p.Raw()))
|
||||
})
|
||||
}
|
42
tcp/listener.go
Normal file
42
tcp/listener.go
Normal file
@ -0,0 +1,42 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"log"
|
||||
"mpbl3p/proxy"
|
||||
"net"
|
||||
)
|
||||
|
||||
func NewListener(p *proxy.Proxy, local string, v proxy.MacVerifier) error {
|
||||
laddr, err := net.ResolveTCPAddr("tcp", local)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listener, err := net.ListenTCP("tcp", laddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.AcceptTCP()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = conn.SetWriteBuffer(0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
f := Flow{conn: conn, isAlive: true}
|
||||
|
||||
log.Printf("received new connection: %v\n", f)
|
||||
|
||||
p.AddConsumer(&f)
|
||||
p.AddProducer(&f, v)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
96
tun/tun.go
Normal file
96
tun/tun.go
Normal file
@ -0,0 +1,96 @@
|
||||
package tun
|
||||
|
||||
import (
|
||||
"github.com/pkg/taptun"
|
||||
"io"
|
||||
"log"
|
||||
"mpbl3p/proxy"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SourceSink struct {
|
||||
tun *taptun.Tun
|
||||
bufferSize int
|
||||
|
||||
up bool
|
||||
upMu sync.Mutex
|
||||
}
|
||||
|
||||
func NewTun(namingScheme string, bufferSize int) (ss *SourceSink, err error) {
|
||||
ss = &SourceSink{}
|
||||
|
||||
ss.tun, err = taptun.NewTun(namingScheme)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
ss.bufferSize = bufferSize
|
||||
|
||||
ss.upMu.Lock()
|
||||
go func() {
|
||||
defer ss.upMu.Unlock()
|
||||
|
||||
for {
|
||||
iface, err := net.InterfaceByName(ss.tun.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if strings.Contains(iface.Flags.String(), "up") {
|
||||
log.Println("tun is up")
|
||||
ss.up = true
|
||||
return
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *SourceSink) Source() (proxy.Packet, error) {
|
||||
if !t.up {
|
||||
t.upMu.Lock()
|
||||
t.upMu.Unlock()
|
||||
}
|
||||
|
||||
buf := make([]byte, t.bufferSize)
|
||||
|
||||
read, err := t.tun.Read(buf)
|
||||
if err != nil {
|
||||
return proxy.Packet{}, err
|
||||
}
|
||||
|
||||
if read == 0 {
|
||||
return proxy.Packet{}, io.EOF
|
||||
}
|
||||
|
||||
return proxy.NewPacket(buf[:read]), nil
|
||||
}
|
||||
|
||||
var good, bad float64
|
||||
|
||||
func (t *SourceSink) Sink(packet proxy.Packet) error {
|
||||
if !t.up {
|
||||
t.upMu.Lock()
|
||||
t.upMu.Unlock()
|
||||
}
|
||||
|
||||
_, err := t.tun.Write(packet.Raw())
|
||||
if err != nil {
|
||||
switch err.(type) {
|
||||
case *os.PathError:
|
||||
bad += 1
|
||||
log.Printf("packet loss: %f%%\n", bad*100/(good+bad))
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
good += 1
|
||||
return nil
|
||||
}
|
13
utils/utils.go
Normal file
13
utils/utils.go
Normal file
@ -0,0 +1,13 @@
|
||||
package utils
|
||||
|
||||
var NextId = make(chan int)
|
||||
|
||||
func init() {
|
||||
go func() {
|
||||
i := 0
|
||||
for {
|
||||
NextId <- i
|
||||
i += 1
|
||||
}
|
||||
}()
|
||||
}
|
Loading…
Reference in New Issue
Block a user