{"id":21833553,"url":"https://github.com/ghvstcode/tcp-proxy","last_synced_at":"2025-03-21T13:47:18.623Z","repository":{"id":117358124,"uuid":"574257113","full_name":"Ghvstcode/TCP-Proxy","owner":"Ghvstcode","description":null,"archived":false,"fork":false,"pushed_at":"2022-12-04T22:20:35.000Z","size":7,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-21T08:07:17.856Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ghvstcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-04T22:20:28.000Z","updated_at":"2022-12-04T22:20:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"7f577aac-514e-40d3-b8f1-c17c582b9cc6","html_url":"https://github.com/Ghvstcode/TCP-Proxy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ghvstcode%2FTCP-Proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ghvstcode%2FTCP-Proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ghvstcode%2FTCP-Proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ghvstcode%2FTCP-Proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ghvstcode","download_url":"https://codeload.github.com/Ghvstcode/TCP-Proxy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244807639,"owners_count":20513677,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-27T19:32:01.826Z","updated_at":"2025-03-21T13:47:18.600Z","avatar_url":"https://github.com/Ghvstcode.png","language":"Go","readme":"\n**What is this?**\n\nThis is a configurable TCP proxy that proxys connections from ports to targets as specified in a `config.json` file. The configuration file has the construct of an \"app\" Which has  a name, an array of ports and an array of targets.\n```\n{  \n  \"Apps\": [  \n    {  \n\t    \"Name\": \"app name\",  \n      \"Ports\": [  \n        5001,  \n        5200,  \n        5300,  \n        5400  \n      ],  \n      \"Targets\": [  \n        \"echo-server:1234\",  \n        \"echo-server:5678\"  \n      ]  \n    }\n```\nThis proxy listens on all the ports for all the provided apps and forwards connections to their corresponding targets.\n\n**Demo**\n\n![ezgif-5-c1d4a5c96b](https://user-images.githubusercontent.com/46195831/204164196-070c3756-46fb-45ee-b4e0-a9e49a89c218.gif)\n\n  **Features**\n\n  These are some features of this project\n  Healthchecks on Targets — A periodic health check is done all the various app targets to ensure connection on an apps ports gets forwarded to \"healthy\" targets. The logic for the health check lives here\u003clink to code line\u003e ! It runs in a sepreate goroutine \u0026 the `healthcheckItr` runs every 30 seconds. `healthcheckItr` iterates over all the backends on the server pool and checks if a target is alive by pinging it. The timeout period is 2 seconds. if we receive an error from the ping, we mark that particular \"backend\" as dead.\n  For every app, The health check for its targets are ran in a seperate goroutine. App A's target healthcheck runs in its own goroutine, App B's target healthcheck runs in its own goroutine, et cetra.\n\nLoad-balancing — There is a basic round-robin balancing happening on the app level between the various targets which have been marked live in the step above. I imagine a better balancing technique could be using the least connection technique to efficiently distribute traffic across the backends.\n\nListening on all available ports — As we saw above, It is possible for an app to have various ports \u0026 for our config file to contain various apps. It is a requirement for this tool to accept connections on all the ports for every app listed in the configuration file. How do we ensure the proxy is able to\n1. Accept connections on all the ports listed in the config file\n2. Associate the incoming connections on a said port to an app(So it can forward connections to the right target)\n3. Gracefully close all the listeners on proxy shutdown.\n   I got an idea from a pattern I'd seen in the Go standard library — The [http.Server](https://cs.opensource.google/go/go/+/refs/tags/go1.19.3:src/net/http/server.go;l=26890) ! The Server struct has a private field listeners which is a map with `net.Listener` as a key! That inspired the similarly named field on the Server struct in my implementation!\n```golang\ntype Server struct {  \n   listener      map[*net.Listener]string  \n   ......\n}\n```\nThe listener field is a map with a pointer to the listener as the key and the name of the app as the value.  When we run the start the program, Right after parsing the JSON configuration file, we iterate over all the apps and all their ports, creating listeners and setting the app name.\n\n```golang\nfor _, port := range val.Ports {  \n   l, err := net.Listen(\"tcp\", \"127.0.0.1:\"+strconv.Itoa(port))  \n   if err != nil {  \n      log.Println(err)  \n   }  \n   s.listener[\u0026l] = val.Name  \n}\n```\nLater on, when we receieve a connection that we need to proxy, The app name we set above becomes the key we use to retrieve the target for a specific application! Eventually, we need to shutdown the server, We iterate over all the various listeners and close them.\n```golang\nfor ln := range s.listener {  \n   if err := (*ln).Close(); err != nil {  \n      log.Fatal(\"Error occurred while closing listener\", err)  \n   }}\n   \n```\nAnother field present on the server struct is `Quit` which is a custom SigChannel type. The SigChannel type is essentailly a wrapper around the channel with a `sync.Once` present to ensure that whenever the channel is closed, it is done only once to prevent panics.\n```golang\ntype SigChannel struct {  \n   C      chan os.Signal   \n   once   sync.Once  \n}\n```\nWhenever the Quit channel receives a SIGTERM, it starts the process of shutting down the servers!\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghvstcode%2Ftcp-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fghvstcode%2Ftcp-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fghvstcode%2Ftcp-proxy/lists"}