Compare commits
8 commits
Author | SHA1 | Date | |
---|---|---|---|
90925b6f84 | |||
feb2aa23ab | |||
d51b6dc8e8 | |||
a5971197af | |||
b0aa0492c3 | |||
5bf702a1a4 | |||
9fdd7baa24 | |||
305bfee2f5 |
8 changed files with 209 additions and 27 deletions
54
.github/workflows/test-and-build.yml
vendored
Normal file
54
.github/workflows/test-and-build.yml
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
|
||||
name: test-and-build
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
tags: ['v*']
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
test-and-build:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
container:
|
||||
image: luzifer/gh-arch-env
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
GOPATH: /go
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
show-progress: false
|
||||
|
||||
- name: Marking workdir safe
|
||||
run: git config --global --add safe.directory /__w/ipt-loadbalancer/ipt-loadbalancer
|
||||
|
||||
- name: 'Lint and test code'
|
||||
run: |
|
||||
go test -cover -v ./...
|
||||
golangci-lint run ./...
|
||||
|
||||
- name: Build release
|
||||
run: make publish
|
||||
|
||||
- name: Extract changelog
|
||||
run: 'awk "/^#/ && ++c==2{exit}; /^#/f" "History.md" | tail -n +2 >release_changelog.md'
|
||||
|
||||
- name: Release
|
||||
uses: ncipollo/release-action@v1
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
with:
|
||||
artifacts: '.build/*'
|
||||
bodyFile: release_changelog.md
|
||||
draft: false
|
||||
generateReleaseNotes: false
|
||||
|
||||
...
|
16
History.md
16
History.md
|
@ -1,5 +1,17 @@
|
|||
# 0.4.0 / 2024-04-15
|
||||
|
||||
* Improve logging for up/down changes
|
||||
|
||||
# 0.3.0 / 2024-04-15
|
||||
|
||||
* Add support for hostnames in addresses
|
||||
* Update dependencies
|
||||
* CI: Cleanup and update workflow
|
||||
|
||||
# 0.2.0 / 2024-03-17
|
||||
|
||||
* CI: Add Github Actions publishing
|
||||
|
||||
# 0.1.0 / 2024-03-11
|
||||
|
||||
* Initial version
|
||||
|
||||
|
||||
|
|
4
Makefile
Normal file
4
Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
default:
|
||||
|
||||
publish:
|
||||
bash ci/build.sh
|
60
ci/build.sh
Normal file
60
ci/build.sh
Normal file
|
@ -0,0 +1,60 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
osarch=(
|
||||
linux/amd64
|
||||
linux/arm
|
||||
linux/arm64
|
||||
)
|
||||
|
||||
function go_package() {
|
||||
cd "${4}"
|
||||
|
||||
local outname="${3}"
|
||||
[[ $1 == windows ]] && outname="${3}.exe"
|
||||
|
||||
log "=> Building ${3} for ${1}/${2}..."
|
||||
CGO_ENABLED=0 GOARCH=$2 GOOS=$1 go build \
|
||||
-ldflags "-s -w -X main.version=${version}" \
|
||||
-mod=readonly \
|
||||
-trimpath \
|
||||
-o "${outname}"
|
||||
|
||||
if [[ $1 == linux ]]; then
|
||||
log "=> Packging ${3} as ${3}_${1}_${2}.tgz..."
|
||||
tar -czf "${builddir}/${3}_${1}_${2}.tgz" "${outname}"
|
||||
else
|
||||
log "=> Packging ${3} as ${3}_${1}_${2}.zip..."
|
||||
zip "${builddir}/${3}_${1}_${2}.zip" "${outname}"
|
||||
fi
|
||||
|
||||
rm "${outname}"
|
||||
}
|
||||
|
||||
function go_package_all() {
|
||||
for oa in "${osarch[@]}"; do
|
||||
local os=$(cut -d / -f 1 <<<"${oa}")
|
||||
local arch=$(cut -d / -f 2 <<<"${oa}")
|
||||
(go_package "${os}" "${arch}" "${1}" "${2}")
|
||||
done
|
||||
}
|
||||
|
||||
function log() {
|
||||
echo "[$(date +%H:%M:%S)] $@" >&2
|
||||
}
|
||||
|
||||
root=$(pwd)
|
||||
builddir="${root}/.build"
|
||||
version="$(git describe --tags --always || echo dev)"
|
||||
|
||||
log "Building version ${version}..."
|
||||
|
||||
log "Resetting output directory..."
|
||||
rm -rf "${builddir}"
|
||||
mkdir -p "${builddir}"
|
||||
|
||||
log "Building Server..."
|
||||
go_package_all "ipt-loadbalancer" "."
|
||||
|
||||
log "Generating SHA256SUMS file..."
|
||||
(cd "${builddir}" && sha256sum * | tee SHA256SUMS)
|
13
go.mod
13
go.mod
|
@ -1,27 +1,24 @@
|
|||
module git.luzifer.io/luzifer/ipt-loadbalancer
|
||||
|
||||
go 1.22.0
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/Luzifer/go_helpers/v2 v2.23.0
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0
|
||||
github.com/coreos/go-iptables v0.7.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/rodaine/table v1.1.1
|
||||
github.com/rodaine/table v1.2.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.18.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
gopkg.in/validator.v2 v2.0.1 // indirect
|
||||
)
|
||||
|
|
18
go.sum
18
go.sum
|
@ -1,5 +1,5 @@
|
|||
github.com/Luzifer/go_helpers/v2 v2.23.0 h1:VowDwOCl6nOt+GVqKUX/do6a94pEeqNTRHb29MsoGX4=
|
||||
github.com/Luzifer/go_helpers/v2 v2.23.0/go.mod h1:BSGkJ/dxqs7AxsfZt8zjJb4R6YB5dONS+/ad7foLUrk=
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0 h1:abACOhsn6a6c6X22jq42mZM1wuOM0Ihfa6yzssrjrOg=
|
||||
github.com/Luzifer/go_helpers/v2 v2.24.0/go.mod h1:KSVUdAJAav5cWGyB5oKGxmC27HrKULVTOxwPS/Kr+pc=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
|
||||
github.com/Luzifer/rconfig/v2 v2.5.0/go.mod h1:eGWUPQeCPv/Pr/p0hjmwFgI20uqvwi/Szen69hUzGzU=
|
||||
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
|
||||
|
@ -32,8 +32,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rodaine/table v1.1.1 h1:zBliy3b4Oj6JRmncse2Z85WmoQvDrXOYuy0JXCt8Qz8=
|
||||
github.com/rodaine/table v1.1.1/go.mod h1:iqTRptjn+EVcrVBYtNMlJ2wrJZa3MpULUmcXFpfcziA=
|
||||
github.com/rodaine/table v1.2.0 h1:38HEnwK4mKSHQJIkavVj+bst1TEY7j9zhLMWu4QJrMA=
|
||||
github.com/rodaine/table v1.2.0/go.mod h1:wejb/q/Yd4T/SVmBSRMr7GCq3KlcZp3gyNYdLSBhkaE=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
|
@ -43,23 +43,23 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
|
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
|
||||
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -4,6 +4,7 @@ package iptables
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
|
||||
coreosIptables "github.com/coreos/go-iptables/iptables"
|
||||
"github.com/mitchellh/hashstructure/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -175,6 +177,26 @@ func (c *Client) buildServiceTable(service string, cType chainType) (rules [][]s
|
|||
}
|
||||
|
||||
for _, nt := range c.services[service] {
|
||||
var (
|
||||
bindAddr, localAddr, targetAddr string
|
||||
err error
|
||||
)
|
||||
|
||||
if bindAddr, err = c.translateToIP(nt.BindAddr); err != nil {
|
||||
logrus.WithError(err).WithField("bind_addr", nt.BindAddr).Error("invalid address")
|
||||
continue
|
||||
}
|
||||
|
||||
if targetAddr, err = c.translateToIP(nt.Addr); err != nil {
|
||||
logrus.WithError(err).WithField("target_addr", nt.Addr).Error("invalid address")
|
||||
continue
|
||||
}
|
||||
|
||||
if localAddr, err = c.translateToIP(nt.LocalAddr); err != nil {
|
||||
logrus.WithError(err).WithField("local_addr", nt.LocalAddr).Error("invalid address")
|
||||
continue
|
||||
}
|
||||
|
||||
switch cType {
|
||||
case chainTypeDNAT:
|
||||
rules = append(rules, []string{
|
||||
|
@ -183,21 +205,21 @@ func (c *Client) buildServiceTable(service string, cType chainType) (rules [][]s
|
|||
"--probability", strconv.FormatFloat(nt.Weight/weightLeft, 'f', probPrecision, probBitsize),
|
||||
|
||||
"-p", nt.Proto,
|
||||
"-d", nt.BindAddr,
|
||||
"-d", bindAddr,
|
||||
"--dport", strconv.Itoa(nt.BindPort),
|
||||
|
||||
"-j", "DNAT",
|
||||
"--to-destination", fmt.Sprintf("%s:%d", nt.Addr, nt.Port),
|
||||
"--to-destination", fmt.Sprintf("%s:%d", targetAddr, nt.Port),
|
||||
})
|
||||
|
||||
case chainTypeSNAT:
|
||||
rules = append(rules, []string{
|
||||
"-p", nt.Proto,
|
||||
"-d", nt.Addr,
|
||||
"-d", targetAddr,
|
||||
"--dport", strconv.Itoa(nt.Port),
|
||||
|
||||
"-j", "SNAT",
|
||||
"--to-source", nt.LocalAddr,
|
||||
"--to-source", localAddr,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -243,6 +265,29 @@ func (*Client) tableName(components ...string) string {
|
|||
return strings.Join(parts, "_")
|
||||
}
|
||||
|
||||
func (*Client) translateToIP(addr string) (string, error) {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip != nil {
|
||||
// We got either valid IPv4 or IPv6: Just return that.
|
||||
return ip.String(), nil
|
||||
}
|
||||
|
||||
// Was no IP, might be a hostname: Look it up
|
||||
ips, err := net.LookupIP(addr)
|
||||
if err != nil {
|
||||
// Definitely was none.
|
||||
return "", fmt.Errorf("resolving %q to ip: %w", addr, err)
|
||||
}
|
||||
|
||||
if len(ips) == 0 {
|
||||
// Maybe was one but had no addresses.
|
||||
return "", fmt.Errorf("resolving %q did not yield IPs", addr)
|
||||
}
|
||||
|
||||
// Had one or more addresses, we take the first one
|
||||
return ips[0].String(), nil
|
||||
}
|
||||
|
||||
func (n NATTarget) equals(c NATTarget) bool {
|
||||
nh, _ := hashstructure.Hash(n, hashstructure.FormatV2, nil)
|
||||
ch, _ := hashstructure.Hash(c, hashstructure.FormatV2, nil)
|
||||
|
|
|
@ -76,14 +76,24 @@ func (m Monitor) updateRoutingTargets(checker healthcheck.Checker) (err error) {
|
|||
}
|
||||
|
||||
if err := checker.Check(m.svc.HealthCheck.Settings, t); err != nil {
|
||||
logger.WithError(err).Debug("detected target down")
|
||||
changed = changed || m.ipt.UnregisterServiceTarget(m.svc.Name, tgt)
|
||||
if m.ipt.UnregisterServiceTarget(m.svc.Name, tgt) {
|
||||
logger.WithError(err).Warn("detected target down")
|
||||
changed = true
|
||||
} else {
|
||||
logger.WithError(err).Debug("detected target down")
|
||||
}
|
||||
|
||||
down = append(down, t.String())
|
||||
return
|
||||
}
|
||||
|
||||
logger.Debug("target up")
|
||||
changed = changed || m.ipt.RegisterServiceTarget(m.svc.Name, tgt)
|
||||
if m.ipt.RegisterServiceTarget(m.svc.Name, tgt) {
|
||||
logger.Info("target up")
|
||||
changed = true
|
||||
} else {
|
||||
logger.Debug("target up")
|
||||
}
|
||||
|
||||
up = append(up, t.String())
|
||||
}()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue