From 63ba6da810486114b40204dce0eab40203996511 Mon Sep 17 00:00:00 2001 From: Peter Kurfer Date: Thu, 2 Apr 2020 00:58:44 +0200 Subject: [PATCH] Add basic DNS and DNS-over-TLS implementation - add random and incremental DNS fallback strategy - add option to handle DNS requests based on regex patterns - made Makefiles plugin independent - extended config - set default plugins directory in Dockerfile --- Dockerfile | 2 + config.yaml | 36 +++++++- go.mod | 2 +- go.sum | 15 +++- pkg/plugins/dns_mock/Makefile | 44 ++++++++++ pkg/plugins/dns_mock/fallback.go | 75 ++++++++++++++++ pkg/plugins/dns_mock/fallback_test.go | 106 +++++++++++++++++++++++ pkg/plugins/dns_mock/init.go | 25 ++++++ pkg/plugins/dns_mock/main.go | 75 ++++++++++++++++ pkg/plugins/dns_mock/protocol_options.go | 56 ++++++++++++ pkg/plugins/dns_mock/regex_handler.go | 86 ++++++++++++++++++ pkg/plugins/http_mock/Makefile | 2 +- pkg/plugins/http_mock/init.go | 21 +++++ pkg/plugins/http_mock/main.go | 26 ++---- pkg/plugins/tls_interceptor/Makefile | 2 +- 15 files changed, 546 insertions(+), 27 deletions(-) create mode 100644 pkg/plugins/dns_mock/Makefile create mode 100644 pkg/plugins/dns_mock/fallback.go create mode 100644 pkg/plugins/dns_mock/fallback_test.go create mode 100644 pkg/plugins/dns_mock/init.go create mode 100644 pkg/plugins/dns_mock/main.go create mode 100644 pkg/plugins/dns_mock/protocol_options.go create mode 100644 pkg/plugins/dns_mock/regex_handler.go create mode 100644 pkg/plugins/http_mock/init.go diff --git a/Dockerfile b/Dockerfile index ed9a6d1..11f3c66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,8 @@ RUN make CONTAINER=yes FROM scratch +ENV INETMOCK_PLUGINS_DIRECTORY=/app/plugins/ + WORKDIR /app COPY --from=build /etc/passwd /etc/group /etc/ diff --git a/config.yaml b/config.yaml index 85b12dd..bc06c52 100644 --- a/config.yaml +++ b/config.yaml @@ -38,4 +38,38 @@ endpoints: certCachePath: /tmp/inetmock/ target: ipAddress: 127.0.0.1 - port: 80 \ No newline at end of file + port: 80 + plainDns: + handler: dns_mock + listenAddress: 0.0.0.0 + port: 53 + options: + fallback: + strategy: incremental + args: + startIP: 10.0.0.0 + rules: + - pattern: ".*\\.google\\.com" + response: 1.1.1.1 + - pattern: ".*\\.reddit\\.com" + response: 2.2.2.2 + dnsOverTlsDowngrade: + handler: tls_interceptor + listenAddress: 0.0.0.0 + port: 853 + options: + ecdsaCurve: P256 + validity: + ca: + notBeforeRelative: 17520h + notAfterRelative: 17520h + domain: + notBeforeRelative: 168h + notAfterRelative: 168h + rootCaCert: + publicKey: ./ca.pem + privateKey: ./ca.key + certCachePath: /tmp/inetmock/ + target: + ipAddress: 127.0.0.1 + port: 53 \ No newline at end of file diff --git a/go.mod b/go.mod index 1a673e6..fb20a24 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/baez90/inetmock go 1.13 require ( + github.com/miekg/dns v1.1.29 github.com/spf13/cobra v0.0.6 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.4.0 go.uber.org/zap v1.14.1 - golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f // indirect gopkg.in/yaml.v2 v2.2.8 // indirect ) diff --git a/go.sum b/go.sum index 7439ac0..01a64f1 100644 --- a/go.sum +++ b/go.sum @@ -60,6 +60,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.29 h1:xHBEhR+t5RzcFJjBLJlax2daXOrTYtr9z4WdKEfWFzg= +github.com/miekg/dns v1.1.29/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -129,11 +131,14 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -141,10 +146,13 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478 h1:l5EDrHhldLYb3ZRHDUhXF7Om7MvYXnkV9/iQNo1lX6g= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -154,6 +162,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv 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/sys v0.0.0-20190924154521-2837fb4f24fe h1:6fAMxZRR6sl1Uq8U61gxU+kPTs2tR8uOySCbBP7BN/M= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -164,9 +174,10 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f h1:3MlESg/jvTr87F4ttA/q4B+uhe/q6qleC9/DP+IwQmY= -golang.org/x/tools v0.0.0-20191127201027-ecd32218bd7f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= diff --git a/pkg/plugins/dns_mock/Makefile b/pkg/plugins/dns_mock/Makefile new file mode 100644 index 0000000..e1074cd --- /dev/null +++ b/pkg/plugins/dns_mock/Makefile @@ -0,0 +1,44 @@ +VERSION = $(shell git describe --dirty --tags --always) +DIR = $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) +PKGS = $(shell go list ./...) +TEST_PKGS = $(shell find . -type f -name "*_test.go" -printf '%h\n' | sort -u) +GOARGS = GOOS=linux GOARCH=amd64 +GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" +GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo +GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l" +PLUGIN_NAME = $(shell basename $(DIR)).so +OUT_DIR = $(DIR)../../../plugins +DEBUG_PORT = 2345 + +.PHONY: deps format compile test cli-cover-report html-cover-report + +all: format compile test + +deps: + @go mod tidy + @go build -buildmode=plugin -v $(DIR)... + +format: + @go fmt $(PKGS) + +compile: deps + @mkdir -p $(OUT_DIR) +ifdef DEBUG + @echo 'Compiling for debugging...' + @$(GOARGS) go build $(GO_DEBUG_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAME) $(DIR) +else ifdef CONTAINER + @$(GOARGS) go build $(GO_CONTAINER_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAME) $(DIR) +else + @$(GOARGS) go build $(GO_BUILD_ARGS) -o $(OUT_DIR)/$(PLUGIN_NAMEs) $(DIR) +endif + +test: + @go test -coverprofile=./cov-raw.out -v $(TEST_PKGS) + @cat ./cov-raw.out | grep -v "generated" > ./cov.out + @rm -f $(DIR)$(PLUGIN_NAME) + +cli-cover-report: + @go tool cover -func=cov.out + +html-cover-report: + @go tool cover -html=cov.out -o .coverage.html \ No newline at end of file diff --git a/pkg/plugins/dns_mock/fallback.go b/pkg/plugins/dns_mock/fallback.go new file mode 100644 index 0000000..03022cb --- /dev/null +++ b/pkg/plugins/dns_mock/fallback.go @@ -0,0 +1,75 @@ +package main + +import ( + "encoding/binary" + "github.com/spf13/viper" + "math" + "math/rand" + "net" + "unsafe" +) + +const ( + randomIPStrategyName = "random" + incrementalIPStrategyName = "incremental" + startIPConfigKey = "startIP" +) + +var ( + fallbackStrategies map[string]ResolverFactory +) + +type ResolverFactory func(conf *viper.Viper) ResolverFallback + +func init() { + fallbackStrategies = make(map[string]ResolverFactory) + fallbackStrategies[incrementalIPStrategyName] = func(conf *viper.Viper) ResolverFallback { + return &incrementalIPFallback{ + latestIp: ipToInt32(net.ParseIP(conf.GetString(startIPConfigKey))), + } + } + fallbackStrategies[randomIPStrategyName] = func(conf *viper.Viper) ResolverFallback { + return &randomIPFallback{} + } +} + +func CreateResolverFallback(name string, config *viper.Viper) ResolverFallback { + if factory, ok := fallbackStrategies[name]; ok { + return factory(config) + } else { + return fallbackStrategies[randomIPStrategyName](config) + } +} + +type ResolverFallback interface { + GetIP() net.IP +} + +type incrementalIPFallback struct { + latestIp uint32 +} + +func (i *incrementalIPFallback) GetIP() net.IP { + if i.latestIp < math.MaxInt32 { + i.latestIp += 1 + } + return uint32ToIP(i.latestIp) +} + +type randomIPFallback struct { +} + +func (randomIPFallback) GetIP() net.IP { + return uint32ToIP(uint32(rand.Int31())) +} + +func uint32ToIP(i uint32) net.IP { + bytes := (*[4]byte)(unsafe.Pointer(&i))[:] + return net.IPv4(bytes[3], bytes[2], bytes[1], bytes[0]) +} + +func ipToInt32(ip net.IP) uint32 { + v4 := ip.To4() + result := binary.BigEndian.Uint32(v4) + return result +} diff --git a/pkg/plugins/dns_mock/fallback_test.go b/pkg/plugins/dns_mock/fallback_test.go new file mode 100644 index 0000000..e6098ec --- /dev/null +++ b/pkg/plugins/dns_mock/fallback_test.go @@ -0,0 +1,106 @@ +package main + +import ( + "net" + "reflect" + "testing" +) + +func Test_randomIPFallback_GetIP(t *testing.T) { + ra := randomIPFallback{} + for i := 0; i < 1000; i++ { + if got := ra.GetIP(); reflect.DeepEqual(got, net.IP{}) { + t.Errorf("GetIP() = %v", got) + } + } +} + +func Test_incrementalIPFallback_GetIP(t *testing.T) { + type fields struct { + latestIp uint32 + } + tests := []struct { + name string + fields fields + want []net.IP + }{ + { + name: "Expect the next icremental IP", + fields: fields{ + latestIp: 167772160, + }, + want: []net.IP{ + net.IPv4(10, 0, 0, 1), + }, + }, + { + name: "Expect a sequence of 5", + fields: fields{ + latestIp: 167772160, + }, + want: []net.IP{ + net.IPv4(10, 0, 0, 1), + net.IPv4(10, 0, 0, 2), + net.IPv4(10, 0, 0, 3), + net.IPv4(10, 0, 0, 4), + net.IPv4(10, 0, 0, 5), + }, + }, + { + name: "Expect next block to be incremented", + fields: fields{ + latestIp: 167772413, + }, + want: []net.IP{ + net.IPv4(10, 0, 0, 254), + net.IPv4(10, 0, 0, 255), + net.IPv4(10, 0, 1, 0), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &incrementalIPFallback{ + latestIp: tt.fields.latestIp, + } + for k := 0; k < len(tt.want); k++ { + if got := i.GetIP(); !reflect.DeepEqual(got, tt.want[k]) { + t.Errorf("GetIP() = %v, want %v", got, tt.want[k]) + } + } + }) + } +} + +func Test_ipToInt32(t *testing.T) { + type args struct { + ip net.IP + } + tests := []struct { + name string + args args + want uint32 + }{ + { + name: "Convert 188.193.106.113 to int", + args: args{ + ip: net.ParseIP("188.193.106.113"), + }, + want: 3166792305, + }, + { + name: "Convert 192.168.178.10 to int", + args: args{ + ip: net.ParseIP("192.168.178.10"), + }, + want: 3232281098, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := ipToInt32(tt.args.ip); got != tt.want { + t.Errorf("ipToInt32() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/plugins/dns_mock/init.go b/pkg/plugins/dns_mock/init.go new file mode 100644 index 0000000..7ee719c --- /dev/null +++ b/pkg/plugins/dns_mock/init.go @@ -0,0 +1,25 @@ +package main + +import ( + "github.com/baez90/inetmock/internal/plugins" + "github.com/baez90/inetmock/pkg/api" + "github.com/baez90/inetmock/pkg/logging" + "go.uber.org/zap" +) + +const ( + name = "dns_mock" +) + +func init() { + logger, _ := logging.CreateLogger() + logger = logger.With( + zap.String("ProtocolHandler", name), + ) + + plugins.Registry().RegisterHandler(name, func() api.ProtocolHandler { + return &dnsHandler{ + logger: logger, + } + }) +} diff --git a/pkg/plugins/dns_mock/main.go b/pkg/plugins/dns_mock/main.go new file mode 100644 index 0000000..7171ee6 --- /dev/null +++ b/pkg/plugins/dns_mock/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "fmt" + "github.com/baez90/inetmock/internal/config" + "github.com/miekg/dns" + "go.uber.org/zap" + "sync" +) + +type dnsHandler struct { + logger *zap.Logger + dnsServer []*dns.Server +} + +func (d *dnsHandler) Run(config config.HandlerConfig) { + options := loadFromConfig(config.Options()) + addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port()) + + handler := ®exHandler{ + fallback: options.Fallback, + logger: d.logger, + } + + for _, rule := range options.Rules { + d.logger.Info( + "register DNS rule", + zap.String("pattern", rule.pattern.String()), + zap.String("response", rule.response.String()), + ) + handler.AddRule(rule) + } + + d.logger = d.logger.With( + zap.String("address", addr), + ) + + d.dnsServer = []*dns.Server{ + { + Addr: addr, + Net: "udp", + Handler: handler, + }, + { + Addr: addr, + Net: "tcp", + Handler: handler, + }, + } + + for _, dnsServer := range d.dnsServer { + go d.startServer(dnsServer) + } +} + +func (d *dnsHandler) startServer(dnsServer *dns.Server) { + if err := dnsServer.ListenAndServe(); err != nil { + d.logger.Error( + "failed to start DNS server listener", + zap.Error(err), + ) + } +} + +func (d *dnsHandler) Shutdown(wg *sync.WaitGroup) { + for _, dnsServer := range d.dnsServer { + if err := dnsServer.Shutdown(); err != nil { + d.logger.Error( + "failed to shutdown server", + zap.Error(err), + ) + } + } + wg.Done() +} diff --git a/pkg/plugins/dns_mock/protocol_options.go b/pkg/plugins/dns_mock/protocol_options.go new file mode 100644 index 0000000..d2f4d1a --- /dev/null +++ b/pkg/plugins/dns_mock/protocol_options.go @@ -0,0 +1,56 @@ +package main + +import ( + "github.com/spf13/viper" + "net" + "regexp" +) + +const ( + rulesConfigKey = "rules" + patternConfigKey = "pattern" + responseConfigKey = "response" + fallbackStrategyConfigKey = "fallback.strategy" + fallbackArgsConfigKey = "fallback.args" +) + +type resolverRule struct { + pattern *regexp.Regexp + response net.IP +} + +type dnsOptions struct { + Rules []resolverRule + Fallback ResolverFallback +} + +func loadFromConfig(config *viper.Viper) dnsOptions { + options := dnsOptions{} + + anonRules := config.Get(rulesConfigKey).([]interface{}) + for _, rule := range anonRules { + innerData := rule.(map[interface{}]interface{}) + var err error + var compiledPattern *regexp.Regexp + var response net.IP + if compiledPattern, err = regexp.Compile(innerData[patternConfigKey].(string)); err != nil { + continue + } + + if response = net.ParseIP(innerData[responseConfigKey].(string)); response == nil { + continue + } + + options.Rules = append(options.Rules, resolverRule{ + pattern: compiledPattern, + response: response, + }) + } + + options.Fallback = CreateResolverFallback( + config.GetString(fallbackStrategyConfigKey), + config.Sub(fallbackArgsConfigKey), + ) + + return options +} diff --git a/pkg/plugins/dns_mock/regex_handler.go b/pkg/plugins/dns_mock/regex_handler.go new file mode 100644 index 0000000..fb3cc0e --- /dev/null +++ b/pkg/plugins/dns_mock/regex_handler.go @@ -0,0 +1,86 @@ +package main + +import ( + "github.com/miekg/dns" + "go.uber.org/zap" +) + +type regexHandler struct { + routes []resolverRule + fallback ResolverFallback + logger *zap.Logger +} + +func (r2 *regexHandler) AddRule(rule resolverRule) { + r2.routes = append(r2.routes, rule) +} + +func (r2 regexHandler) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.Compress = false + m.SetReply(r) + + switch r.Opcode { + case dns.OpcodeQuery: + r2.handleQuery(m) + } + if err := w.WriteMsg(m); err != nil { + r2.logger.Error( + "Failed to write DNS response message", + zap.Error(err), + ) + } +} + +func (r2 regexHandler) handleQuery(m *dns.Msg) { + for _, q := range m.Question { + r2.logger.Info( + "handling question", + zap.String("question", q.Name), + ) + switch q.Qtype { + case dns.TypeA: + for _, rule := range r2.routes { + if rule.pattern.MatchString(q.Name) { + m.Authoritative = true + answer := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 60, + }, + A: rule.response, + } + m.Answer = append(m.Answer, answer) + r2.logger.Info( + "matched DNS rule", + zap.String("pattern", rule.pattern.String()), + zap.String("response", rule.response.String()), + ) + return + } + } + r2.handleFallbackForMessage(m, q) + } + } +} + +func (r2 regexHandler) handleFallbackForMessage(m *dns.Msg, q dns.Question) { + fallbackIP := r2.fallback.GetIP() + answer := &dns.A{ + Hdr: dns.RR_Header{ + Name: q.Name, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + Ttl: 60, + }, + A: fallbackIP, + } + r2.logger.Info( + "Falling back to generated IP", + zap.String("response", fallbackIP.String()), + ) + m.Authoritative = true + m.Answer = append(m.Answer, answer) +} diff --git a/pkg/plugins/http_mock/Makefile b/pkg/plugins/http_mock/Makefile index 47da7bf..e1074cd 100644 --- a/pkg/plugins/http_mock/Makefile +++ b/pkg/plugins/http_mock/Makefile @@ -6,7 +6,7 @@ GOARGS = GOOS=linux GOARCH=amd64 GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l" -PLUGIN_NAME = http_mock.so +PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../../plugins DEBUG_PORT = 2345 diff --git a/pkg/plugins/http_mock/init.go b/pkg/plugins/http_mock/init.go new file mode 100644 index 0000000..f7d0ea0 --- /dev/null +++ b/pkg/plugins/http_mock/init.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/baez90/inetmock/internal/plugins" + "github.com/baez90/inetmock/pkg/api" + "github.com/baez90/inetmock/pkg/logging" + "go.uber.org/zap" +) + +func init() { + logger, _ := logging.CreateLogger() + logger = logger.With( + zap.String("ProtocolHandler", name), + ) + plugins.Registry().RegisterHandler(name, func() api.ProtocolHandler { + return &httpHandler{ + logger: logger, + router: &RegexpHandler{}, + } + }) +} diff --git a/pkg/plugins/http_mock/main.go b/pkg/plugins/http_mock/main.go index 10f4075..ced8971 100644 --- a/pkg/plugins/http_mock/main.go +++ b/pkg/plugins/http_mock/main.go @@ -4,9 +4,6 @@ import ( "bytes" "fmt" "github.com/baez90/inetmock/internal/config" - "github.com/baez90/inetmock/internal/plugins" - "github.com/baez90/inetmock/pkg/api" - "github.com/baez90/inetmock/pkg/logging" "github.com/baez90/inetmock/pkg/path" "go.uber.org/zap" "net/http" @@ -19,13 +16,13 @@ const ( name = "http_mock" ) -type httpPlugin struct { +type httpHandler struct { logger *zap.Logger router *RegexpHandler server *http.Server } -func (p *httpPlugin) Run(config config.HandlerConfig) { +func (p *httpHandler) Run(config config.HandlerConfig) { options := loadFromConfig(config.Options()) addr := fmt.Sprintf("%s:%d", config.ListenAddress(), config.Port()) p.server = &http.Server{Addr: addr, Handler: p.router} @@ -40,7 +37,7 @@ func (p *httpPlugin) Run(config config.HandlerConfig) { go p.startServer() } -func (p *httpPlugin) Shutdown(wg *sync.WaitGroup) { +func (p *httpHandler) Shutdown(wg *sync.WaitGroup) { if err := p.server.Close(); err != nil { p.logger.Error( "failed to shutdown HTTP server", @@ -51,7 +48,7 @@ func (p *httpPlugin) Shutdown(wg *sync.WaitGroup) { wg.Done() } -func (p *httpPlugin) startServer() { +func (p *httpHandler) startServer() { if err := p.server.ListenAndServe(); err != nil { p.logger.Error( "failed to start http listener", @@ -60,7 +57,7 @@ func (p *httpPlugin) startServer() { } } -func (p *httpPlugin) setupRoute(rule targetRule) { +func (p *httpHandler) setupRoute(rule targetRule) { var compiled *regexp.Regexp var err error if compiled, err = regexp.Compile(rule.pattern); err != nil { @@ -101,16 +98,3 @@ func createHandlerForTarget(logger *zap.Logger, targetPath string) http.Handler http.ServeFile(writer, request, targetFilePath) }) } - -func init() { - logger, _ := logging.CreateLogger() - logger = logger.With( - zap.String("ProtocolHandler", name), - ) - plugins.Registry().RegisterHandler(name, func() api.ProtocolHandler { - return &httpPlugin{ - logger: logger, - router: &RegexpHandler{}, - } - }) -} diff --git a/pkg/plugins/tls_interceptor/Makefile b/pkg/plugins/tls_interceptor/Makefile index dc01810..db2df0a 100644 --- a/pkg/plugins/tls_interceptor/Makefile +++ b/pkg/plugins/tls_interceptor/Makefile @@ -6,7 +6,7 @@ GOARGS = GOOS=linux GOARCH=amd64 GO_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" GO_CONTAINER_BUILD_ARGS = -buildmode=plugin -ldflags="-w -s" -a -installsuffix cgo GO_DEBUG_BUILD_ARGS = -buildmode=plugin -gcflags "all=-N -l" -PLUGIN_NAME = tls_interceptor.so +PLUGIN_NAME = $(shell basename $(DIR)).so OUT_DIR = $(DIR)../../../plugins DEBUG_PORT = 2345