Skip to content

Commit

Permalink
Basic functionality works and is persistent
Browse files Browse the repository at this point in the history
  • Loading branch information
jaym committed Sep 3, 2017
1 parent e9b9c45 commit d508c74
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 39 deletions.
84 changes: 84 additions & 0 deletions ip_tables_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package lsrv

import (
"log"
"strconv"

"github.com/coreos/go-iptables/iptables"
)

type IPTablesManager struct {
ipt *iptables.IPTables
}

func NewIPTablesManager() (*IPTablesManager, error) {
ipt, err := iptables.New()
if err != nil {
return nil, err
} else {
manager := new(IPTablesManager)
manager.ipt = ipt
return manager, nil
}
}

func (manager *IPTablesManager) Initialize() error {
ipt := manager.ipt

ipt.NewChain("nat", "LSRV")
ipt.AppendUnique("nat", "OUTPUT", "-jLSRV")

return nil
}

func (manager *IPTablesManager) AddRule(source_addr string, source_port uint16,
dest_addr string, dest_port uint16) error {

ipt := manager.ipt
rulespec := rule_for(source_addr, source_port, dest_addr, dest_port)

ipt.AppendUnique("nat", "LSRV", rulespec...)

return nil
}

func (manager *IPTablesManager) RemoveRule(source_addr string, source_port uint16,
dest_addr string, dest_port uint16) error {

ipt := manager.ipt
rulespec := rule_for(source_addr, source_port, dest_addr, dest_port)

return ipt.Delete("nat", "LSRV", rulespec...)
}

func (manager *IPTablesManager) Cleanup() error {
ipt := manager.ipt

chains, err := ipt.ListChains("nat")
if err != nil {
return err
}

containsChain := false
for _, elem := range chains {
if elem == "LSRV" {
containsChain = true
}
}

if containsChain {
log.Println("Deleting chain LSRV")
ipt.Delete("nat", "OUTPUT", "-jLSRV")
ipt.ClearChain("nat", "LSRV")
ipt.DeleteChain("nat", "LSRV")
}
return nil
}

func rule_for(service_addr string, service_port uint16,
dest_addr string, dest_port uint16) []string {

return []string{"-p", "tcp", "-d", dest_addr, "--dport",
strconv.FormatUint(uint64(dest_port), 10), "-j", "DNAT",
"--to", service_addr + ":" + strconv.FormatUint(uint64(service_port), 10)}
}
89 changes: 50 additions & 39 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package lsrv

import (
"bufio"
"encoding/binary"
"fmt"
"log"
"net"
"strconv"
Expand All @@ -11,33 +11,29 @@ import (
"github.com/coreos/go-iptables/iptables"
)

type ServiceEntry struct {
service_name string
service_address string
service_port uint16

// The service will respond to the address/port below
dest_address string
dest_port uint16
}
// type ServiceEntry struct {
// service_name string
// service_address string
// service_port uint16
//
// // The service will respond to the address/port below
// dest_address string
// dest_port uint16
// }

func Spawn(socket_path string) error {
ipt, iperr := initializeIpTables()

if iperr != nil {
log.Fatal("Could not initialize IPTables: ", iperr)
}

ln, err := net.Listen("unix", socket_path)

if err != nil {
log.Fatal("Listen error: ", err)
return err
}

services := []ServiceEntry{}
// services := []ServiceEntry{}
start_ip := net.IPv4(172, 22, 0, 1)

manager := NewServiceManager("./state", start_ip)

for {
//TODO: err check
fd, _ := ln.Accept()
Expand All @@ -64,7 +60,7 @@ func Spawn(socket_path string) error {
// OK
if len(command_split) != 5 {
log.Println("Could not parse command")
fd.Close()
fmt.Fprintf(fd, "ERROR Could not parse command")
} else {
service_port, serr := strconv.ParseUint(command_split[3], 10, 16)
dest_port, derr := strconv.ParseUint(command_split[4], 10, 16)
Expand All @@ -75,26 +71,41 @@ func Spawn(socket_path string) error {
break
}

entry := ServiceEntry{
service_name: command_split[1],
service_address: command_split[2],
service_port: uint16(service_port),
dest_address: ipAdd(start_ip, len(services)).String(),
dest_port: uint16(dest_port),
}

services = append(services, entry)
err = materialize(ipt, &services)
entry, err := manager.Add(command_split[1], command_split[2], uint16(service_port), uint16(dest_port))

if err != nil {
log.Println(err)
break
fmt.Fprintf(fd, "ERROR %s\n", err.Error())
} else {
fmt.Fprintf(fd, "OK %s\n", entry.DestAddress)
}

log.Println("Added ", entry)
}
case "DELETE":
if len(command_split) != 2 {
fmt.Fprintln(fd, "ERROR Could not parse command")
} else {
err := manager.Delete(command_split[1])
if err != nil {
fmt.Fprintf(fd, "ERROR %s\n", err.Error())
} else {
fmt.Fprintln(fd, "OK")
}
}
case "GETHOSTBYNAME":
if len(command_split) != 2 {
fmt.Fprintln(fd, "ERROR Could not parse command")
} else {
entry, err := manager.GetServiceEntry(command_split[1])
if err != nil {
fmt.Fprintf(fd, "ERROR %s\n", err.Error())
} else {
fmt.Fprintf(fd, "OK %s\n", entry.DestAddress)
}
}
default:
log.Println("Unknown command: ", command_split[0])
fmt.Fprintln(fd, "ERROR Unknown Command")
}
fd.Close()
}
Expand All @@ -111,9 +122,9 @@ func materialize(ipt *iptables.IPTables, services *[]ServiceEntry) error {
}

for _, entry := range *services {
rulespec := []string{"-p", "tcp", "-d", entry.dest_address, "--dport",
strconv.FormatUint(uint64(entry.dest_port), 10), "-j", "DNAT",
"--to", entry.service_address + ":" + strconv.FormatUint(uint64(entry.service_port), 10)}
rulespec := []string{"-p", "tcp", "-d", entry.DestAddress, "--dport",
strconv.FormatUint(uint64(entry.DestPort), 10), "-j", "DNAT",
"--to", entry.ServiceAddress + ":" + strconv.FormatUint(uint64(entry.ServicePort), 10)}
err = ipt.Append("nat", "LSRV", rulespec...)
if err != nil {
log.Fatal(err)
Expand All @@ -123,12 +134,12 @@ func materialize(ipt *iptables.IPTables, services *[]ServiceEntry) error {
return nil
}

func ipAdd(start net.IP, add int) net.IP { // IPv4 only
start = start.To4()
result := make(net.IP, 4)
binary.BigEndian.PutUint32(result, binary.BigEndian.Uint32(start)+uint32(add))
return result
}
// func ipAdd(start net.IP, add int) net.IP { // IPv4 only
// start = start.To4()
// result := make(net.IP, 4)
// binary.BigEndian.PutUint32(result, binary.BigEndian.Uint32(start)+uint32(add))
// return result
// }

func initializeIpTables() (*iptables.IPTables, error) {
ipt, err := iptables.New()
Expand Down
146 changes: 146 additions & 0 deletions services.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package lsrv

import (
"encoding/binary"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"os"
)

type ServiceManager struct {
// ip_allocator *IPAllocator
services map[string]ServiceEntry
state_path string
start_ip net.IP
ipt_man *IPTablesManager
}

type ServiceEntry struct {
ServiceAddress string
ServicePort uint16

// The service will respond to the address/port below
DestAddress string
DestPort uint16
}

func NewServiceManager(state_path string, start_ip net.IP) *ServiceManager {
manager := new(ServiceManager)
manager.state_path = state_path
manager.start_ip = start_ip

ipt_man, err := NewIPTablesManager()

if err != nil {
log.Fatal(err)
}

manager.ipt_man = ipt_man

if _, err := os.Stat(state_path); os.IsNotExist(err) {
manager.services = make(map[string]ServiceEntry)
} else {
manager.services = load(state_path)
}

manager.ipt_man.Cleanup()
manager.ipt_man.Initialize()

// TODO: What if start_ip has changed
for service_name, entry := range manager.services {
log.Printf("Loaded %s: %+v\n", service_name, entry)
manager.ipt_man.AddRule(entry.ServiceAddress, entry.ServicePort, entry.DestAddress, entry.DestPort)
}

return manager
}

func (manager *ServiceManager) Add(service_name string, service_address string,
service_port uint16, dest_port uint16) (ServiceEntry, error) {

entry, present := manager.services[service_name]
if present {
return entry, fmt.Errorf("Entry for service %s already exists", service_name)
}

entry = ServiceEntry{
ServiceAddress: service_address,
ServicePort: service_port,
DestAddress: ipAdd(manager.start_ip, len(manager.services)).String(),
DestPort: dest_port,
}

manager.services[service_name] = entry
manager.serialize()
manager.ipt_man.AddRule(entry.ServiceAddress, entry.ServicePort, entry.DestAddress, entry.DestPort)
return entry, nil
}

func (manager *ServiceManager) Delete(service_name string) error {
entry, err := manager.GetServiceEntry(service_name)

if err != nil {
return err
}

err = manager.ipt_man.RemoveRule(entry.ServiceAddress, entry.ServicePort,
entry.DestAddress, entry.DestPort)

if err == nil {
delete(manager.services, service_name)
manager.serialize()
}

return err
}

func (manager *ServiceManager) List() []ServiceEntry {
tmp := make([]ServiceEntry, 0, len(manager.services))

for _, value := range manager.services {
tmp = append(tmp, value)
}

return tmp
}

func (manager *ServiceManager) GetServiceEntry(service_name string) (ServiceEntry, error) {
entry, present := manager.services[service_name]

if present {
return entry, nil
} else {
return ServiceEntry{}, fmt.Errorf("Service not found")
}
}

func (manager *ServiceManager) serialize() {
services_json, _ := json.Marshal(manager.services)
// This is not safe. This file should be moved into place
err := ioutil.WriteFile(manager.state_path, services_json, 0644)
if err != nil {
log.Fatal(err)
}
}

func load(path string) map[string]ServiceEntry {
raw, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}

var services map[string]ServiceEntry
json.Unmarshal(raw, &services)

return services
}

func ipAdd(start net.IP, add int) net.IP { // IPv4 only
start = start.To4()
result := make(net.IP, 4)
binary.BigEndian.PutUint32(result, binary.BigEndian.Uint32(start)+uint32(add))
return result
}

0 comments on commit d508c74

Please sign in to comment.