Skip to content

Commit eb67752

Browse files
committed
Add Connector
1 parent eb81b40 commit eb67752

4 files changed

Lines changed: 207 additions & 0 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,11 @@ type = tcp
6363
local_ip = 127.0.0.1
6464
local_port = 22
6565
remote_port = 2200
66+
```
67+
68+
Connector:
69+
```
70+
sudo ./connector
71+
-s http[s]://[<user>:<pass>@]<server host>:<server port> # Server API
72+
[-i <interval>] # Update Interval, default 300s
6673
```

cmd/connector/main.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"log"
8+
"net"
9+
"net/http"
10+
"net/url"
11+
"os"
12+
"os/signal"
13+
"syscall"
14+
"time"
15+
16+
"github.com/Max-Sum/fcbreak"
17+
"github.com/akamensky/argparse"
18+
"github.com/coreos/go-iptables/iptables"
19+
)
20+
21+
var (
22+
TempChainName = "FCBREAK-CONNECTOR-TEMP"
23+
ChainName = "FCBREAK-CONNECTOR"
24+
)
25+
26+
func main() {
27+
parser := argparse.NewParser("fcbreak-connector", "Retrive address and write to iptables, Linux only currently")
28+
// Create string flag
29+
server := parser.String("s", "server", &argparse.Options{Required: true, Help: "Server Address, including protocol and user/pass"})
30+
interval := parser.Int("i", "interval", &argparse.Options{Default: 300, Help: "Interval between updates in seconds"})
31+
// Parse input
32+
err := parser.Parse(os.Args)
33+
if err != nil {
34+
// In case of error print error and print usage
35+
// This can also be done by passing -h or --help flags
36+
fmt.Print(parser.Usage(err))
37+
os.Exit(1)
38+
}
39+
40+
serverUrl, err := url.Parse(*server)
41+
if err != nil {
42+
log.Printf("Failed to parse server URL: %v\n", err)
43+
}
44+
serverHostname := serverUrl.Hostname()
45+
46+
// Gracefully Stop
47+
sigc := make(chan os.Signal, 1)
48+
signal.Notify(sigc,
49+
syscall.SIGHUP,
50+
syscall.SIGINT,
51+
syscall.SIGTERM,
52+
syscall.SIGQUIT)
53+
for {
54+
svcs, err := RefreshServices(*server)
55+
if err != nil {
56+
return
57+
}
58+
err = UpdateIPTables(svcs, serverHostname)
59+
if err != nil {
60+
log.Printf("Update iptables error: %v\n", err)
61+
}
62+
select {
63+
case <-time.After(time.Duration(*interval) * time.Second):
64+
svcs, err = RefreshServices(*server)
65+
if err != nil {
66+
return
67+
}
68+
err = UpdateIPTables(svcs, serverHostname)
69+
if err != nil {
70+
log.Printf("Update iptables error: %v\n", err)
71+
}
72+
case <-sigc:
73+
ClearIPTables()
74+
return
75+
}
76+
}
77+
}
78+
79+
func RefreshServices(server string) (map[string]*fcbreak.ServiceInfo, error) {
80+
svcs := make(map[string]*fcbreak.ServiceInfo)
81+
addr := fmt.Sprintf("%s/services", server)
82+
req, err := http.NewRequest("GET", addr, nil)
83+
if err != nil {
84+
log.Printf("HTTP new request error: %v\n", err)
85+
return svcs, err
86+
}
87+
// Update svcs
88+
resp, err := http.DefaultClient.Do(req)
89+
if err != nil {
90+
log.Printf("HTTP Get error: %v\n", err)
91+
return svcs, err
92+
}
93+
by, err := io.ReadAll(resp.Body)
94+
if err != nil {
95+
log.Printf("HTTP Body error: %v\n", err)
96+
return svcs, err
97+
}
98+
resp.Body.Close()
99+
if resp.StatusCode != http.StatusOK {
100+
log.Printf("HTTP error: %s\n", string(by))
101+
return svcs, err
102+
}
103+
if err := json.Unmarshal(by, &svcs); err != nil {
104+
log.Printf("JSON parsing: %v\n", err)
105+
return svcs, err
106+
}
107+
log.Println("Successfully retrieve services.")
108+
return svcs, nil
109+
}
110+
111+
func UpdateIPTables(svcs map[string]*fcbreak.ServiceInfo, serverHostname string) error {
112+
// Server IP
113+
ips, err := net.LookupIP(serverHostname)
114+
if err != nil {
115+
return err
116+
}
117+
// Update iptables
118+
ipt, err := iptables.New(iptables.IPFamily(iptables.ProtocolIPv4))
119+
if err != nil {
120+
log.Fatalf("Failed to new up an IPtables intance: %v", err)
121+
}
122+
if exists, err := ipt.ChainExists("nat", "OUTPUT"); !exists || err != nil {
123+
log.Fatalf("iptables nat OUTPUT chain doesn't exists, possibly a permission problem.")
124+
}
125+
if exists, err := ipt.ChainExists("nat", "PREROUTING"); !exists || err != nil {
126+
log.Fatalf("iptables nat PREROUTING chain doesn't exists, possibly a permission problem.")
127+
}
128+
// Write in Temp chain
129+
if err = ipt.NewChain("nat", TempChainName); err != nil {
130+
return err
131+
}
132+
defer ipt.ClearAndDeleteChain("nat", TempChainName)
133+
for name, svc := range svcs {
134+
remote_ip, remote_port, err := net.SplitHostPort(svc.RemoteAddr)
135+
if err != nil {
136+
return err
137+
}
138+
// IPv4 only now
139+
if remote_ip == "" || remote_ip == "0.0.0.0" || remote_ip == "[::]" {
140+
for _, ip := range ips {
141+
ipv4 := ip.To4()
142+
if ipv4 == nil {
143+
continue
144+
}
145+
err = ipt.AppendUnique("nat", TempChainName, "-p", "tcp", "-d", ipv4.String(), "--dport", remote_port, "-j", "DNAT", "--to-destination", svc.ExposedAddr)
146+
if err != nil {
147+
return err
148+
}
149+
}
150+
} else {
151+
err = ipt.AppendUnique("nat", TempChainName, "-p", "tcp", "-d", remote_ip, "--dport", remote_port, "-j", "DNAT", "--to-destination", svc.ExposedAddr)
152+
if err != nil {
153+
return err
154+
}
155+
}
156+
log.Printf("Successfully Update Service [%s]\n", name)
157+
}
158+
// Replace Old Chain
159+
exists, err := ipt.ChainExists("nat", ChainName)
160+
if err != nil {
161+
return err
162+
}
163+
if exists {
164+
if err = ipt.ClearAndDeleteChain("nat", ChainName); err != nil {
165+
return err
166+
}
167+
}
168+
if err = ipt.RenameChain("nat", TempChainName, ChainName); err != nil {
169+
return err
170+
}
171+
// Direct flow
172+
if err = ipt.AppendUnique("nat", "PREROUTING", "-j", ChainName); err != nil {
173+
return err
174+
}
175+
if err = ipt.AppendUnique("nat", "OUTPUT", "-j", ChainName); err != nil {
176+
return err
177+
}
178+
return nil
179+
}
180+
181+
func ClearIPTables() {
182+
// Update iptables
183+
ipt, err := iptables.New(iptables.IPFamily(iptables.ProtocolIPv4))
184+
if err != nil {
185+
log.Fatalf("Failed to new up an IPtables intance: %v", err)
186+
}
187+
ipt.DeleteIfExists("nat", "OUTPUT", "-j", ChainName)
188+
ipt.DeleteIfExists("nat", "PREROUTING", "-j", ChainName)
189+
exists, _ := ipt.ChainExists("nat", ChainName)
190+
if exists {
191+
ipt.ClearAndDeleteChain("nat", ChainName)
192+
}
193+
exists, _ = ipt.ChainExists("nat", TempChainName)
194+
if exists {
195+
ipt.ClearAndDeleteChain("nat", TempChainName)
196+
}
197+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212
)
1313

1414
require (
15+
github.com/coreos/go-iptables v0.6.0 // indirect
1516
github.com/gin-contrib/sse v0.1.0 // indirect
1617
github.com/go-playground/locales v0.14.0 // indirect
1718
github.com/go-playground/universal-translator v0.18.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/akamensky/argparse v1.3.1 h1:kP6+OyvR0fuBH6UhbE6yh/nskrDEIQgEA1SUXDPjx4g=
22
github.com/akamensky/argparse v1.3.1/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
3+
github.com/coreos/go-iptables v0.6.0 h1:is9qnZMPYjLd8LYqmm/qlE+wwEgJIkTYdhV3rfZo4jk=
4+
github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
35
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
46
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
57
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

0 commit comments

Comments
 (0)