Browse Source

Initial commit

Eliezer Croitoru 7 months ago
commit
28d8f784c2
6 changed files with 664 additions and 0 deletions
  1. 27 0
      LICENSE
  2. 54 0
      Makefile
  3. 34 0
      build.sh
  4. 479 0
      icap-js-injection-example.go
  5. 4 0
      pack.sh
  6. 66 0
      tproxy-utils.go

+ 27 - 0
LICENSE

@@ -0,0 +1,27 @@
+Copyright (c) 2018, Eliezer Croitoru NgTech LTD
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+* Neither the name of bgu-icap-example nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

+ 54 - 0
Makefile

@@ -0,0 +1,54 @@
+all: linux windows macos freebsd openbsd netbsd solaris arm5 arm6 arm7 arm8 mips64 mips64le mips
+
+update:
+	go get -v -u github.com/elico/icap
+	go get -v -u gopkg.in/redis.v3
+	go get -v -u github.com/patrickmn/go-cache
+clean:
+	echo "cleaning"
+	rm ./bin/*
+	rmdir ./bin
+	rm bgu-icap-example.tar.xz
+linux: linux64 linux86
+
+linux64:	
+	./build.sh "linux" "amd64"
+linux86:
+	./build.sh "linux" "386"
+windows:
+	./build.sh "windows" "386"
+	./build.sh "windows" "amd64"
+macos:
+	./build.sh "darwin" "amd64"
+	./build.sh "darwin" "386"
+
+freebsd:
+	./build.sh "freebsd" "386"
+	./build.sh "freebsd" "amd64"
+
+openbsd:
+	./build.sh "openbsd" "386"
+	./build.sh "openbsd" "amd64"
+
+netbsd:
+	./build.sh "netbsd" "386"
+	./build.sh "netbsd" "amd64"
+
+solaris:
+	./build.sh "solaris" "amd64"
+arm5:
+	./build.sh "linux" "arm" "5"
+arm6:
+	./build.sh "linux" "arm" "6"
+arm7:
+	./build.sh "linux" "arm" "7"
+arm8:
+	./build.sh "linux" "arm64"
+mips:
+	./build.sh "linux" "mips"
+mips64:
+	./build.sh "linux" "mips64"
+mips64le:
+	./build.sh "linux" "mips64le"
+pack:
+	./pack.sh

+ 34 - 0
build.sh

@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# List of arches
+#darwin_386    freebsd_386   freebsd_arm   linux_amd64   netbsd_386    netbsd_arm    openbsd_386   plan9_386     windows_386
+#darwin_amd64  freebsd_amd64 linux_386     linux_arm     netbsd_amd64  obj           openbsd_amd64 tool          windows_amd64
+
+mkdir bin >/dev/null 2>&1
+export BINARY=`basename $PWD`_
+export GOOS=$1
+export GOARCH=$2
+export GOARM=$3
+export CGO_ENABLED=0
+case $GOOS in 
+	windows)
+		go build -o "./bin/`echo $BINARY``echo $GOOS`_`echo $GOARCH`.exe"
+	;;
+	linux)
+		case $GOARCH in 
+			arm)
+				go build -o "./bin/`echo $BINARY``echo $GOOS`_`echo $GOARCH$GOARM`"
+			;;
+			*)
+				go build -o "./bin/`echo $BINARY``echo $GOOS`_`echo $GOARCH`"
+			;;
+		esac
+
+	;;
+	*)
+		go build -o "./bin/`echo $BINARY``echo $GOOS`_`echo $GOARCH`"
+	;;
+esac
+echo -n "finished building for: "
+echo -n $GOOS
+echo -n "_"
+echo  $GOARCH

+ 479 - 0
icap-js-injection-example.go

@@ -0,0 +1,479 @@
+/*
+An example of how to use go-icap.
+
+Run this program and Squid on the same machine.
+Put the following lines in squid.conf:
+
+acl GET method GET
+
+icap_enable on
+icap_service service_req reqmod_precache icap://127.0.0.1:1344/injectjs/
+adaptation_access service_req allow GET
+adaptation_access service_req deny all
+
+(The ICAP server needs to be started before Squid is.)
+
+Set your browser to use the Squid proxy.
+
+ Some Refrences:
+ - https://groups.google.com/forum/#!topic/golang-nuts/J-Y4LtdGNSw
+*/
+package main
+
+import (
+	"bytes"
+	"compress/gzip"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"os"
+	"reflect"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/asaskevich/govalidator"
+
+	"github.com/elico/icap"
+	"github.com/patrickmn/go-cache"
+	"gopkg.in/redis.v3"
+)
+
+var (
+	isTag          = "HTML-JS-Injector"
+	debug          *bool
+	address        *string
+	maxConnections *string
+	redisAddress   *string
+	fullOverride   = false
+	upnkeyTimeout  *int
+	useGoCache     *bool
+	err            error
+	letters        = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+)
+
+var goCacheLocal *cache.Cache
+var redisDB redis.Client
+
+// GlobalHTTPClient --
+var GlobalHTTPClient *http.Client
+
+func overrideExists(req *icap.Request) bool {
+	if *debug {
+		fmt.Println("Checking Override")
+	}
+	if fullOverride {
+		if *debug {
+			fmt.Println("Full Override active")
+		}
+		return true
+	}
+	if _, acceptExists := req.Request.Header["Accept"]; acceptExists {
+		if *debug {
+			fmt.Println(req.Request.Header["Accept"][0])
+		}
+		if strings.Contains(req.Request.Header["Accept"][0], "MoreCache/Override") {
+			if *debug {
+				fmt.Println("Override true")
+			}
+			return true
+		}
+	}
+	if *debug {
+		fmt.Println("Override false")
+	}
+	return false
+}
+
+func noCache(req *icap.Request) bool {
+	if *debug {
+		fmt.Println("Checking Request or response for \"no-cache\" => ")
+	}
+
+	if _, cacheControlExists := req.Request.Header["Cache-Control"]; cacheControlExists {
+		if *debug {
+			fmt.Println("Cache-Control Exists in the request: ")
+			fmt.Println(req.Request.Header["Cache-Control"][0])
+		}
+		if strings.Contains(req.Request.Header["Cache-Control"][0], "no-cache") {
+			if *debug {
+				fmt.Println("\"no-cache\" Exists in the request!")
+			}
+			return true
+		}
+	}
+	if _, cacheControlExists := req.Response.Header["Cache-Control"]; cacheControlExists {
+		if *debug {
+			fmt.Println("Cache-Control Exists in the response: ")
+			fmt.Println("Cache-Control Header =>", reflect.TypeOf(req.Response.Header["Cache-Control"]))
+
+			fmt.Println("Reflect tpyeof Cache-Control Header =>", reflect.TypeOf(req.Response.Header["Cache-Control"]))
+			fmt.Println("len Cache-Control Header =>", len(req.Response.Header["Cache-Control"]))
+			fmt.Println(req.Response.Header["Cache-Control"])
+		}
+		if len(req.Response.Header["Cache-Control"]) > 0 && strings.Contains(strings.Join(req.Response.Header["Cache-Control"], ", "), "no-cache") {
+			fmt.Println("\"no-cache\" Exists in the response!")
+			return true
+		}
+	}
+
+	if *debug {
+		fmt.Println("Cache-Control headers Doesn't Exists in this requset and response")
+	}
+	return false
+}
+
+func wrongMethod(req *icap.Request) bool {
+	if *debug {
+		fmt.Println("Checking Request method => ", req.Request.Method, req.Request.URL.String())
+	}
+
+	if req.Request.Method == "GET" {
+		return false
+	}
+	return true
+
+}
+
+func htmlJSInject(w icap.ResponseWriter, req *icap.Request) {
+	localDebug := false
+	useTProxy := false
+	if strings.Contains(req.URL.RawQuery, "debug=1") {
+		localDebug = true
+	}
+	if strings.Contains(req.URL.RawQuery, "tproxy=1") {
+		useTProxy = true
+	}
+
+	h := w.Header()
+	h.Set("isTag", isTag)
+	h.Set("Service", "HTML JS Injector ICAP serivce")
+
+	if *debug {
+		fmt.Fprintln(os.Stderr, "Printing the full ICAP request")
+		fmt.Fprintln(os.Stderr, req)
+		fmt.Fprintln(os.Stderr, req.Request)
+		fmt.Fprintln(os.Stderr, req.Response)
+	}
+	switch req.Method {
+	case "OPTIONS":
+		h.Set("Methods", "REQMOD, RESPMOD")
+		h.Set("Options-TTL", "1800")
+		h.Set("Allow", "204, 206")
+		h.Set("Preview", "0")
+		h.Set("Transfer-Preview", "*")
+		h.Set("Max-Connections", *maxConnections)
+		h.Set("X-Include", "X-Client-IP, X-Authenticated-Groups, X-Authenticated-User, X-Subscriber-Id, X-Server-Ip, X-Store-Id")
+		w.WriteHeader(200, nil, false)
+	case "REQMOD":
+		modified := false
+		nullBody := false
+		allow206 := false
+		allow204 := false
+
+		if _, allow204Exists := req.Header["Allow"]; allow204Exists {
+			if strings.Contains(req.Header["Allow"][0], "204") {
+				allow204 = true
+			}
+		}
+
+		if *debug || localDebug {
+			for k, v := range req.Header {
+				fmt.Fprintln(os.Stderr, "The ICAP headers:")
+				fmt.Fprintln(os.Stderr, "key size:", len(req.Header[k]))
+				fmt.Fprintln(os.Stderr, "key:", k, "value:", v)
+			}
+		}
+		xClientIP := h.Get("X-Client-IP")
+
+		if _, encapsulationExists := req.Header["Encapsulated"]; encapsulationExists {
+			if strings.Contains(req.Header["Encapsulated"][0], "null-body=") {
+				nullBody = true
+			}
+		}
+
+		if _, allow206Exists := req.Header["Allow"]; allow206Exists {
+			if strings.Contains(req.Header["Allow"][0], "206") {
+				allow206 = true
+			}
+		}
+		_, _, _, _ = nullBody, allow206, modified, allow204
+
+		if wrongMethod(req) {
+			if *debug {
+				fmt.Println("This request has a", req.Request.Method, "method which is not being analyzed")
+			}
+			w.WriteHeader(204, nil, false)
+			return
+		}
+
+		if *debug || localDebug {
+			for k, v := range req.Request.Header {
+				fmt.Fprintln(os.Stderr, "key:", k, "value:", v)
+			}
+		}
+
+		if strings.HasPrefix(req.Request.URL.String(), "http://") {
+			if *debug {
+				fmt.Println("XYZ HTTP url match:", req.Request.URL.String())
+			}
+			var client *http.Client
+			if useTProxy && govalidator.IsIP(xClientIP) {
+				tproxyClient := GlobalHTTPClients[xClientIP]
+				if tproxyClient == nil {
+					client = CreateTproxyHTTPClient(xClientIP)
+				}
+			} else {
+				client = GlobalHTTPClient
+			}
+			newReq, err := http.NewRequest(req.Request.Method, req.Request.URL.String(), nil)
+			newReq.Header = req.Request.Header
+			switch {
+			case newReq.Header.Get("Accept-Encoding") == "gzip":
+				newReq.Header.Del("Accept-Encoding")
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed Accept-Encoding Header since it contains only gzip")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), "gzip, "):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), "gzip, ", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \"gzip, \" From Accept-Encoding Header")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), ", gzip"):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), ", gzip", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \", gzip\" From Accept-Encoding Header")
+				}
+			default:
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "No gzip In Accept-Encoding Header")
+				}
+				// no gzip
+			}
+			switch {
+			case newReq.Header.Get("Accept-Encoding") == "deflate":
+				newReq.Header.Del("Accept-Encoding")
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed Accept-Encoding Header since it contains only deflate")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), "deflate, "):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), "deflate, ", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \"deflate, \" From Accept-Encoding Header")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), ", deflate"):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), ", deflate", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \", deflate\" From Accept-Encoding Header")
+				}
+			default:
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "No deflate In Accept-Encoding Header")
+				}
+				// no gzip
+			}
+			switch {
+			case newReq.Header.Get("Accept-Encoding") == "sdch":
+				newReq.Header.Del("Accept-Encoding")
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed Accept-Encoding Header since it contains only sdch")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), "sdch, "):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), "sdch, ", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \"sdch, \" From Accept-Encoding Header")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), ", sdch"):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), ", sdch", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \", sdch\" From Accept-Encoding Header")
+				}
+			default:
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "No sdch In Accept-Encoding Header")
+				}
+				// no gzip
+			}
+
+			switch {
+			case newReq.Header.Get("Accept-Encoding") == "br":
+				newReq.Header.Del("Accept-Encoding")
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed Accept-Encoding Header since it contains only br")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), "br, "):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), "br, ", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \"br, \" From Accept-Encoding Header")
+				}
+			case strings.Contains(newReq.Header.Get("Accept-Encoding"), ", br"):
+				newReq.Header.Set("Accept-Encoding", strings.Replace(newReq.Header.Get("Accept-Encoding"), ", br", "", -1))
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Removed \", br\" From Accept-Encoding Header")
+				}
+			default:
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "No rb In Accept-Encoding Header")
+				}
+				// no gzip
+			}
+
+			if len(newReq.Header.Get("Accept-Encoding")) < 1 {
+				newReq.Header.Del("Accept-Encoding")
+				if *debug {
+					fmt.Println(req.Request.URL.String(), "Deleteing Accept-Encoding Header since it's empty:", newReq.Header.Get("Accept-Encoding"))
+				}
+			}
+
+			if *debug {
+				fmt.Println(req.Request.URL.String(), "Accept-Encoding Header:", newReq.Header.Get("Accept-Encoding"))
+			}
+
+			originalResp, err := client.Do(newReq)
+			if err != nil {
+				fmt.Println(err)
+				w.WriteHeader(204, nil, false)
+				return
+			}
+			resp := new(http.Response)
+			resp.Status = originalResp.Status
+			resp.StatusCode = originalResp.StatusCode
+			resp.Proto = originalResp.Proto
+			resp.ProtoMajor = originalResp.ProtoMajor
+			resp.ProtoMinor = originalResp.ProtoMinor
+			resp.Header = originalResp.Header
+			if *debug {
+				fmt.Println(req.Request.URL.String(), "Response Header:", resp.Header)
+			}
+			resp.Request = originalResp.Request
+			// What if it is a CONNECT request, .. shouldn't happen
+			content, _ := ioutil.ReadAll(originalResp.Body)
+			pageContent := ""
+
+			if originalResp.Header.Get("Content-Encoding") == "gzip" || strings.Contains(originalResp.Header.Get("Content-Encoding"), "gzip ") || strings.Contains(originalResp.Header.Get("Content-Encoding"), ",gzip") {
+				rdata := bytes.NewReader(content)
+				r, _ := gzip.NewReader(rdata)
+				s, _ := ioutil.ReadAll(r)
+				pageContent = string(s)
+			} else {
+				pageContent = string(content)
+			}
+			if len(pageContent) > 0 {
+				//resp.Body = ioutil.NopCloser(bytes.NewBufferString(pageContent))
+				w.WriteHeader(200, resp, true)
+				io.WriteString(w, pageContent)
+			} else {
+				w.WriteHeader(200, resp, false)
+			}
+			return
+		}
+
+		// What I have been using for captive portal
+		//io.WriteString(w, challengePage)
+
+		if *debug {
+			fmt.Println("end of the line 204 response!.. Shouldn't happen.")
+		}
+		w.WriteHeader(204, nil, false)
+		return
+	case "RESPMOD":
+		w.WriteHeader(204, nil, false)
+		return
+	default:
+		w.WriteHeader(405, nil, false)
+		if *debug || localDebug {
+			fmt.Fprintln(os.Stderr, "Invalid request method")
+		}
+	}
+}
+
+func defaultIcap(w icap.ResponseWriter, req *icap.Request) {
+	localDebug := false
+	if strings.Contains(req.URL.RawQuery, "debug=1") {
+		localDebug = true
+	}
+
+	h := w.Header()
+	h.Set("isTag", isTag)
+	h.Set("Service", "YouTube GoogleVideo Predictor ICAP serivce")
+
+	if *debug || localDebug {
+		fmt.Fprintln(os.Stderr, "Printing the full ICAP request")
+		fmt.Fprintln(os.Stderr, req)
+		fmt.Fprintln(os.Stderr, req.Request)
+	}
+	switch req.Method {
+	case "OPTIONS":
+		h.Set("Methods", "REQMOD, RESPMOD")
+		h.Set("Options-TTL", "1800")
+		h.Set("Allow", "204")
+		h.Set("Preview", "0")
+		h.Set("Transfer-Preview", "*")
+		h.Set("Max-Connections", *maxConnections)
+		h.Set("This-Server", "Default ICAP url which bypass all requests adaptation")
+		h.Set("X-Include", "X-Client-IP, X-Authenticated-Groups, X-Authenticated-User, X-Subscriber-Id, X-Server-Ip, X-Store-Id")
+		w.WriteHeader(200, nil, false)
+	case "REQMOD":
+		if *debug || localDebug {
+			fmt.Fprintln(os.Stderr, "Default REQMOD, you should use the apropriate ICAP service URL")
+		}
+		w.WriteHeader(204, nil, false)
+	case "RESPMOD":
+		if *debug || localDebug {
+			fmt.Fprintln(os.Stderr, "Default RESPMOD, you should use the apropriate ICAP service URL")
+		}
+		w.WriteHeader(204, nil, false)
+	default:
+		w.WriteHeader(405, nil, false)
+		if *debug || localDebug {
+			fmt.Fprintln(os.Stderr, "Invalid request method")
+		}
+	}
+}
+
+func init() {
+	fmt.Fprintln(os.Stderr, "Starting YouTube GoogleVideo Predictor ICAP serivce")
+
+	debug = flag.Bool("d", false, "Debug mode can be \"1\" or \"0\" for no")
+	address = flag.String("p", "127.0.0.1:1344", "Listening address for the ICAP service")
+	maxConnections = flag.String("maxcon", "4000", "Maximum number of connections for the ICAP service")
+	redisAddress = flag.String("redis-address", "127.0.0.1:6379", "Redis DB address to store youtube tokens")
+	upnkeyTimeout = flag.Int("cache-key-timeout", 360, "Redis or GoCache DB key timeout in Minutes")
+	useGoCache = flag.Bool("Use GoCache", true, "GoCache DB is used by default and if disabled then Redis is used")
+
+	flag.Parse()
+}
+
+func main() {
+	fmt.Fprintln(os.Stderr, "running YouTube GoogleVideo Predictor ICAP serivce :D")
+
+	if *debug {
+		fmt.Fprintln(os.Stderr, "Config Variables:")
+		fmt.Fprintln(os.Stderr, "Debug: => "+strconv.FormatBool(*debug))
+		fmt.Fprintln(os.Stderr, "Listen Address: => "+*address)
+		fmt.Fprintln(os.Stderr, "Redis DB Address: => "+*redisAddress)
+		fmt.Fprintln(os.Stderr, "Maximum number of Connections: => "+*maxConnections)
+	}
+
+	if *useGoCache {
+		goCacheLocal = cache.New(time.Duration(*upnkeyTimeout)*time.Minute, 10*time.Minute)
+	} else {
+		redisDB = *redis.NewClient(&redis.Options{
+			Addr:     *redisAddress,
+			Password: "", // no password set
+			DB:       0,  // use default DB
+		})
+	}
+
+	GlobalHTTPClient = &http.Client{}
+	GlobalHTTPClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
+		return errors.New("redirect")
+	}
+
+	icap.HandleFunc("/injectjs/", htmlJSInject)
+	icap.HandleFunc("/", defaultIcap)
+	icap.ListenAndServe(*address, nil)
+}

+ 4 - 0
pack.sh

@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+echo "PACKING: `basename $PWD`.tar.xz"
+tar cvfJ `basename $PWD`.tar.xz bin LICENSE
+

+ 66 - 0
tproxy-utils.go

@@ -0,0 +1,66 @@
+package main
+
+import (
+	"errors"
+	"fmt"
+	"net"
+	"net/http"
+
+	"github.com/asaskevich/govalidator"
+	"github.com/elico/go-linux-tproxy"
+)
+
+//  GlobalHTTPClients the map which hold the http client for use by tproxy
+var GlobalHTTPClients = map[string]*http.Client{}
+
+func noRedirect(req *http.Request, via []*http.Request) error {
+	return errors.New("Don't redirect")
+}
+
+// CreateTproxyHTTPClient is creating a uniqe http  client per client source IP addres
+func CreateTproxyHTTPClient(srcIP string) *http.Client {
+	var netTransport = &http.Transport{
+		Dial: (func(network, addr string) (net.Conn, error) {
+			// Resolve address
+			//if the address is an IP
+			host, port, err := net.SplitHostPort(addr)
+			if err != nil {
+				return nil, err
+			}
+			switch {
+			case govalidator.IsIP(host):
+				srvConn, err := tproxy.TCPDial(srcIP, addr)
+				if err != nil {
+					return nil, err
+				}
+				return srvConn, nil
+			case govalidator.IsDNSName(host):
+
+				ips, err := net.LookupIP(host)
+				if err != nil {
+					return nil, err
+				}
+				for i, ip := range ips {
+					srvConn, err := tproxy.TCPDial(srcIP, net.JoinHostPort(ip.String(), port))
+					if err != nil {
+						fmt.Println(err)
+						if i == len(ips) {
+							return srvConn, nil
+						}
+						continue
+					}
+					fmt.Println("returning a srvconn")
+					return srvConn, nil
+				}
+				srvConn, err := tproxy.TCPDial(srcIP, addr)
+				if err != nil {
+					return nil, err
+				}
+				return srvConn, nil
+			}
+			return nil, nil
+		}),
+	}
+	client := &http.Client{Transport: netTransport, CheckRedirect: noRedirect}
+	return client
+}