Browse Source

Use own DHCP and DNS server (#2)

Fix updating address in network model

Add new configuration key

Run DHCP client and server config on startup

Add DHCP client configuration to confd

Fix pointer in goroutines

dhclient

Use IP or IPNet Stringer

Set default network object to dhcp client

Remove obsolete configuration values

Set router as primary DNS server

Fix IPv4 DHCP server

Fix multiple issues including DNS not starting properly

Rename DhcpWorker to DhcpServerWorker

Start/stop DNS server worker

Shutdown worker if alive false

Support A and AAAA records

Add new db model DnsRecord

Merge NetIP, NetworkIP and NetworkIPNet

Co-authored-by: Lukas Matt <lukas@matt.wf>
Reviewed-on: #2
pull/4/head
zauberstuhl 1 year ago
parent
commit
4845152efb
27 changed files with 1222 additions and 500 deletions
  1. +0
    -3
      config/app.json.example
  2. +15
    -0
      db/migrations/000051_create_dns_records_table.up.sql
  3. +7
    -0
      db/migrations/000052_cleanup_networks_after_interface_deletion.up.sql
  4. +2
    -0
      go.mod
  5. +30
    -0
      go.sum
  6. +5
    -4
      helper/validators.go
  7. +30
    -43
      init.go
  8. +1
    -1
      model/connection.go
  9. +144
    -0
      model/dns_record.go
  10. +8
    -8
      model/ip_information.go
  11. +21
    -92
      model/network.go
  12. +32
    -0
      model/network_interface.go
  13. +2
    -2
      netfilter/nfq.go
  14. +96
    -26
      types/model.go
  15. +28
    -7
      types/worker.go
  16. +157
    -18
      worker/confd.go
  17. +0
    -177
      worker/dhcp.go
  18. +130
    -0
      worker/dhcp_client.go
  19. +275
    -0
      worker/dhcp_server.go
  20. +97
    -47
      worker/dns.go
  21. +14
    -6
      worker/frontend.go
  22. +16
    -6
      worker/iptable.go
  23. +21
    -9
      worker/nfq.go
  24. +15
    -8
      worker/portal.go
  25. +15
    -7
      worker/profiler.go
  26. +54
    -36
      worker/watcher.go
  27. +7
    -0
      workers.go

+ 0
- 3
config/app.json.example View File

@@ -16,9 +16,6 @@
"patternDirectory": "/usr/share/fengg-pattern",
"confd": {
"hostapd": "/etc/hostapd.conf",
"dhserver4": "/etc/dhserver4.conf",
"dhserver6": "/etc/dhserver6.conf",
"dnsserver": "/etc/dnsserver.conf",
"gateway": "https://up.fen.gg"
},
"session": {


+ 15
- 0
db/migrations/000051_create_dns_records_table.up.sql View File

@@ -0,0 +1,15 @@
create table dns_records (
id serial primary key,
created_at timestamp not null,
updated_at timestamp not null,
network_id int references networks(id),
type int not null,
ipv4 inet,
ipv6 inet,
name text not null,
value text not null,
priority int,
ttl int
);
create unique index dns_records_index_on_network_id_type_name_value on dns_records(network_id, type, name, value);
create index dns_records_index_on_network_id on dns_records(network_id);

+ 7
- 0
db/migrations/000052_cleanup_networks_after_interface_deletion.up.sql View File

@@ -0,0 +1,7 @@
create function cleanup_networks_after_interface_deletion() returns trigger as $networks$
begin
delete from networks where id = OLD.network_id;
return OLD;
end;
$networks$ language plpgsql;
create trigger trigger_cleanup_networks_after_interface_deletion after delete on network_interfaces for each row execute procedure cleanup_networks_after_interface_deletion();

+ 2
- 0
go.mod View File

@@ -15,6 +15,7 @@ require (
github.com/gin-gonic/gin v1.5.0
github.com/golang-migrate/migrate/v4 v4.7.1
github.com/google/gopacket v1.1.17
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.3.0
github.com/lytics/anomalyzer v0.0.0-20151102000650-13cee1061701
@@ -26,6 +27,7 @@ require (
github.com/rs/zerolog v1.16.0
github.com/sajari/regression v1.0.1
github.com/sirupsen/logrus v1.6.0
github.com/stretchr/objx v0.1.1 // indirect
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
gonum.org/v1/gonum v0.7.0 // indirect
gopkg.in/go-playground/validator.v9 v9.29.1


+ 30
- 0
go.sum View File

@@ -102,6 +102,7 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/fanliao/go-promise v0.0.0-20141029170127-1890db352a72/go.mod h1:PjfxuH4FZdUyfMdtBio2lsRr1AKEaVPwelzuHuh8Lqc=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
@@ -172,6 +173,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -226,6 +228,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8 h1:u+vle+5E78+cT/CSMD5/Y3NUpMgA83Yu2KhG+Zbco/k=
github.com/insomniacslk/dhcp v0.0.0-20200621044212-d74cd86ad5b8/go.mod h1:CfMdguCK66I5DAUJgGKyNz8aB6vO5dZzkm9Xep6WGvw=
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8 h1:R1oP0/QEyvaL7dm+mBQouQ9V1X6gqQr5taZA1yaq5zQ=
github.com/insomniacslk/dhcp v0.0.0-20201112113307-4de412bc85d8/go.mod h1:TKl4jN3Voofo4UJIicyNhWGp/nlQqQkFxmwIFTvBkKI=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
@@ -234,6 +238,9 @@ github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhB
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a h1:84IpUNXj4mCR9CuCEvSiCArMbzr/TMbuPIadKDwypkI=
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -277,11 +284,16 @@ github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOq
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7 h1:lez6TS6aAau+8wXUP3G9I3TGlmPFEq2CTxBaRqY6AGE=
github.com/mdlayher/ethernet v0.0.0-20190606142754-0394541c37b7/go.mod h1:U6ZQobyTjI/tJyq2HG+i/dfSoFUt8/aZCM+GKtmFk/Y=
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
github.com/mdlayher/netlink v1.0.0 h1:vySPY5Oxnn/8lxAPn2cK6kAzcZzYJl3KriSLO46OT18=
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
github.com/mdlayher/netlink v1.1.1 h1:VqG+Voq9V4uZ+04vjIrcSCWDpf91B1xxbP4QBUmUJE8=
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
github.com/mdlayher/raw v0.0.0-20190606142536-fef19f00fc18/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065 h1:aFkJ6lx4FPip+S+Uw4aTegFMct9shDvP+79PsSxpm3w=
github.com/mdlayher/raw v0.0.0-20191009151244-50f2db8cc065/go.mod h1:7EpbotpCmVZcu+KCX4g9WaRNuu11uyhiW7+Le1dKawg=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
@@ -400,12 +412,15 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/u-root/u-root v6.0.0+incompatible h1:YqPGmRoRyYmeg17KIWFRSyVq6LX5T6GSzawyA6wG6EE=
github.com/u-root/u-root v6.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/u-root/u-root v7.0.0+incompatible h1:u+KSS04pSxJGI5E7WE4Bs9+Zd75QjFv+REkjy/aoAc8=
github.com/u-root/u-root v7.0.0+incompatible/go.mod h1:RYkpo8pTHrNjW08opNd/U6p/RJE7K0D8fXO0d47+3YY=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -482,11 +497,16 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a h1:+HHJiFUXVOIS9mr1ThqkQD1N8vpFCfCShqADBM12KTc=
golang.org/x/net v0.0.0-20191204025024-5ee1b9f4859a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -525,20 +545,29 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e h1:9vRrk9YW2BTzLP0VCB9ZDjU4cPqkg+IDWL7XgxA1yxQ=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76 h1:Dho5nD6R3PcW2SH1or8vS0dszDaXRxIw55lBX7XiE5g=
golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY=
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -632,6 +661,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=


+ 5
- 4
helper/validators.go View File

@@ -20,6 +20,7 @@ import (
"strings"
"encoding/base64"

"tea.fen.gg/fengg/server/types"
"tea.fen.gg/fengg/server/model"
"gopkg.in/go-playground/validator.v9"
// NOTE if we upgrade gin
@@ -70,8 +71,8 @@ var IpValidator validator.Func = func(fieldLevel validator.FieldLevel) bool {
return true
}
}
if field, ok := fieldLevel.Field().Interface().(*model.NetworkIP); ok {
return len(field.IP) > 0
if field, ok := fieldLevel.Field().Interface().(*types.IP); ok {
return len(field.Raw) > 0
}
return false
}
@@ -83,8 +84,8 @@ var IpnetValidator validator.Func = func(fieldLevel validator.FieldLevel) bool {
return true
}
}
if field, ok := fieldLevel.Field().Interface().(*model.NetworkIPNet); ok {
return len(field.IPNet.IP) > 0 && len(field.IPNet.Mask) > 0
if field, ok := fieldLevel.Field().Interface().(*types.IPNet); ok {
return len(field.Raw.IP) > 0 && len(field.Raw.Mask) > 0
}
return false
}


+ 30
- 43
init.go View File

@@ -66,7 +66,7 @@ func init() {
wlConfig := model.NewConfiguration(types.WhiteListConfiguration)
if !wlConfig.Exists() {
whitelist := map[string]interface{}{
"entries": []string{"127.0.0.0/8", "::1/128", "10.0.66.254", "10.0.67.254"},
"entries": []string{"127.0.0.0/8", "::1/128"},
}
err = wlConfig.MarshalAndCreate(whitelist)
if err != nil {
@@ -74,40 +74,6 @@ func init() {
}
}

apConfig := model.NewConfiguration(types.AccessPointConfiguration)
if !apConfig.Exists() {
ap := make(map[string]interface{})
ap["network"] = "10.0.67.254/24"
ap["interface"] = "wlan0"
ap["ssid"] = "Fengg-AP"
ap["passphrase"] = "This password will be generated and should be changed by the user"

err = apConfig.MarshalAndCreate(ap)
if err != nil {
logger.Fatal().Err(err).Msg("cannot marshal and create configuration")
}
}

network := model.NewNetwork()
network.Name = "default"
if !network.Exists() {
ip, ipnet, err := net.ParseCIDR("10.0.66.254/24")
if err != nil {
logger.Fatal().Err(err).Msg("cannot parse CIDR")
}

// setup initial network
network.Address = &model.NetworkIP{ip}
network.Network = &model.NetworkIPNet{*ipnet}
network.DHCP = true
network.DNS = true
network.Client = false
err = network.Create()
if err != nil {
logger.Fatal().Err(err).Msg("cannot create default network")
}
}

// register network interfaces
// lookup the installed interfaces
interfaces, err := net.Interfaces()
@@ -127,13 +93,15 @@ func init() {
continue
}
// check if the interface is known
intfExists := false
var intfExists = false
var oldDbIntf model.NetworkInterface
for idx, dbIntf := range dbInterfaces {
if dbIntf.Name == intf.Name {
// remove slice entry
copy(dbInterfaces[idx:], dbInterfaces[idx+1:])
dbInterfaces = dbInterfaces[:len(dbInterfaces)-1]
intfExists = true
oldDbIntf = dbIntf
break
}
}
@@ -143,14 +111,33 @@ func init() {
_, bridgeIntf := os.Stat(fmt.Sprintf("/sys/class/net/%s/bridge", intf.Name))
_, tunDevice := os.Stat(fmt.Sprintf("/sys/class/net/%s/tun_flags", intf.Name))
// Bridges and loopback interfaces have 00:00:00:00:00:00 in /sys/class/net/lo/address
dbInterface := model.NetworkInterface{
Name: intf.Name,
NetworkID: network.ID,
WirelessCard: wirelessCard == nil,
PhysicalCard: physicalCard == nil,
BridgeMaster: bridgeIntf == nil,
TunTapDevice: tunDevice == nil,

// if the interface exists preserve the assigned network
// and default gateway configuration
dbInterface := &oldDbIntf
if !intfExists {
// create a new network object for the interface
network := model.NewNetwork()
network.Name = intf.Name
if !network.Exists() {
// setup initial network
network.DHCP = false
network.DNS = false
network.Client = true
err = network.Create()
if err != nil {
logger.Fatal().Err(err).Msg("cannot create default network")
}
}

dbInterface = model.NewNetworkInterface()
dbInterface.Name = intf.Name
dbInterface.NetworkID = network.ID
}
dbInterface.WirelessCard = wirelessCard == nil
dbInterface.PhysicalCard = physicalCard == nil
dbInterface.BridgeMaster = bridgeIntf == nil
dbInterface.TunTapDevice = tunDevice == nil

// look for the bridge ID if the interface is a bridge or if it was assigned to one
bridgeIDPath := fmt.Sprintf("/sys/class/net/%s/bridge/bridge_id", intf.Name)


+ 1
- 1
model/connection.go View File

@@ -107,7 +107,7 @@ func NewConnection() *Connection {
}

func (a *Connection) Valid() bool {
return a.Source.IP != nil && a.Destination.IP != nil
return a.Source.IP.Raw != nil && a.Destination.IP.Raw != nil
}

func (a *Connection) Exists() bool {


+ 144
- 0
model/dns_record.go View File

@@ -0,0 +1,144 @@
package model
//
// Fengg Security Gateway Server Application
// Copyright (C) 2020 Lukas Matt <support@fen.gg>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
import (
"fmt"
"time"

"tea.fen.gg/fengg/server/types"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)

const (
DnsRecordNamedInsertTmpl = `
INSERT INTO dns_records (
created_at, updated_at, network_id, type, ipv4, ipv6, name, value, priority, ttl
) VALUES (
now(), now(), :network_id, :type, :ipv4, :ipv6, :name, :value, :priority, :ttl
) RETURNING id;`

DnsRecordNamedUpdateTmpl = `
UPDATE dns_records
SET updated_at=now(), ipv4=:ipv4, ipv6=:ipv6, priority=:priority, ttl=:ttl
WHERE (network_id=:network_id, type=:type, name=:name, value=:value)
OR id=:id;`

DnsRecordNamedDeleteTmpl = `DELETE FROM dns_records WHERE %s;`

DnsRecordQueryTmpl = `SELECT * FROM dns_records %s;`
)

type DnsRecord struct {
ID uint `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`

NetworkID uint `db:"network_id" json:"networkId"`
Type int16 `db:"type" json:"type"`
IPv4 *types.IP `db:"ipv4" json:"ipv4"`
IPv6 *types.IP `db:"ipv6" json:"ipv6"`
Name string `db:"name" json:"name"`
Value string `db:"value" json:"value"`
Priority *int `db:"priority" json:"priority"`
TTL *int `db:"ttl" json:"ttl"`
}

type DnsRecords []DnsRecord

func NewDnsRecord() *DnsRecord {
return &DnsRecord{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}

// Exists directly updates the struct with the findings
func (record *DnsRecord) Exists() bool {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
logger.Error().Err(err).Msg("cannot open database")
return false
}
defer db.Close()

return db.Get(
record,
fmt.Sprintf(
DnsRecordQueryTmpl,
"WHERE (network_id=$1, type=$2, name=$3, value=$4) OR id=$5;",
),
record.NetworkID,
record.Type,
record.Name,
record.Value,
record.ID) == nil
}

func (record *DnsRecord) Update() error {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
return err
}
defer db.Close()

_, err = db.NamedExec(DnsRecordNamedUpdateTmpl, record)
return err
}

func (record *DnsRecord) Delete() error {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
return err
}
defer db.Close()

_, err = db.NamedExec(fmt.Sprintf(DnsRecordNamedDeleteTmpl, "id=:id"), record)
return err
}

func (record *DnsRecord) Create() error {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
return err
}
defer db.Close()

rows, err := db.NamedQuery(DnsRecordNamedInsertTmpl, record)
if err != nil {
return err
}
defer rows.Close()

for rows.Next() {
err = rows.Scan(&record.ID)
if err != nil {
return err
}
}
return nil
}

func (records *DnsRecords) FindByNetworkID(id uint) error {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
return err
}
defer db.Close()

return db.Select(records, fmt.Sprintf(DnsRecordQueryTmpl, "WHERE network_id=$1"), id)
}

+ 8
- 8
model/ip_information.go View File

@@ -57,7 +57,7 @@ type IPInformation struct {
ID uint `db:"id" json:"id"`
CreatedAt time.Time `db:"created_at" json:"createdAt"`
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`
IP types.NetIP `db:"ip" json:"ip"`
IP types.IP `db:"ip" json:"ip"`
MacAddress string `db:"mac" json:"macAddress"`
ReverseDNS string `db:"reverse_dns" json:"reverseDNS"`
NetBIOS string `db:"net_bios" json:"netBIOS"`
@@ -92,7 +92,7 @@ func NewIPInformationNested() *IPInformationNested {
}

func (info *IPInformation) Valid() bool {
return info.IP.Raw() != nil || info.ID > 0
return info.IP.Raw != nil || info.ID > 0
}

func (info *IPInformationNested) Scan(src interface{}) error {
@@ -130,7 +130,7 @@ func (info *IPInformation) Find() error {
}
defer db.Close()

if info.IP != nil {
if info.IP.Raw != nil {
return db.Get(info, fmt.Sprintf(IPInformationQueryTmpl, "ip=$1"), info.IP.String())
}
return db.Get(info, fmt.Sprintf(IPInformationQueryTmpl, "id=$1"), info.ID)
@@ -190,7 +190,7 @@ func (info *IPInformation) InternalNetwork() bool {
if network == nil || network.Network == nil {
continue
}
if network.Network.Contains(net.IP(info.IP)) {
if network.Network.Contains(info.IP.Raw) {
return true
}
}
@@ -202,7 +202,7 @@ func (info *IPInformation) LookupNetBios() error {
return nil
}

name, err := utils.Nbtscan(info.IP.Raw())
name, err := utils.Nbtscan(info.IP.Raw)
info.NetBIOS = name
return err
}
@@ -227,12 +227,12 @@ func (info *IPInformation) LookupRDNSAndGeoIP() {

geodb, err := geoip2.Open(config.App.GeoIPDatabase)
if err != nil {
logger.Error().Err(err).Msg("cannot open geoip database file")
logger.Debug().Err(err).Msg("cannot open geoip database file")
return
}
defer geodb.Close()

record, err := geodb.Country(info.IP.Raw())
record, err := geodb.Country(info.IP.Raw)
if err != nil {
logger.Debug().Err(err).Msg("cannot find record for ip address")
return
@@ -252,7 +252,7 @@ func (info *IPInformation) LookupMAC() error {
return nil
}

mac, err := utils.Arpscan(net.IP(info.IP))
mac, err := utils.Arpscan(info.IP.Raw)
info.MacAddress = mac.String()
return err
}

+ 21
- 92
model/network.go View File

@@ -18,11 +18,8 @@ package model
import (
"fmt"
"time"
"net"
"errors"
"encoding/json"
"database/sql/driver"

"tea.fen.gg/fengg/server/types"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
@@ -37,7 +34,8 @@ const (

NetworkNamedUpdateTmpl = `
UPDATE networks
SET updated_at=now(), network=:network, dhcp=:dhcp, dns=:dns, client=:client
SET updated_at=now(), address=:address, network=:network,
dhcp=:dhcp, dns=:dns, client=:client
WHERE name=:name OR id=:id;`

NetworkNamedDeleteTmpl = `DELETE FROM networks WHERE %s;`
@@ -51,8 +49,8 @@ type Network struct {
UpdatedAt time.Time `db:"updated_at" json:"updatedAt"`

Name string `db:"name" json:"name" binding="alphanum,min=4"`
Address *NetworkIP `db:"address" json:"address,omitempty" binding="ip"`
Network *NetworkIPNet `db:"network" json:"network,omitempty" binding="ipnet"`
Address *types.IP `db:"address" json:"address,omitempty" binding="ip"`
Network *types.IPNet `db:"network" json:"network,omitempty" binding="ipnet"`
DHCP bool `db:"dhcp" json:"dhcp"`
DNS bool `db:"dns" json:"dns"`
Client bool `db:"client" json:"client"`
@@ -60,91 +58,6 @@ type Network struct {

type Networks []Network

type NetworkIP struct {
net.IP
}

type NetworkIPNet struct {
net.IPNet
}

func (ip NetworkIP) MarshalJSON() ([]byte, error) {
address := ip.IP.String()
return json.Marshal(&address)
}

func (ipnet NetworkIPNet) MarshalJSON() ([]byte, error) {
network := ipnet.IPNet.String()
return json.Marshal(&network)
}

func (ip *NetworkIP) UnmarshalJSON(data []byte) error {
var ipString string
err := json.Unmarshal(data, &ipString)
if err != nil {
return err
}
address := net.ParseIP(ipString)
if address == nil {
return errors.New("cannot parse IP address")
}
ip.IP = address
return nil
}

func (ipnet *NetworkIPNet) UnmarshalJSON(data []byte) error {
var ipnetString string
err := json.Unmarshal(data, &ipnetString)
if err != nil {
return err
}
_, cidr, err := net.ParseCIDR(ipnetString)
if err != nil {
return err
}
ipnet.IPNet = *cidr
return nil
}

func (ip *NetworkIP) Scan(src interface{}) error {
switch data := src.(type) {
case []uint8:
address := net.ParseIP(string(data))
if address == nil {
return errors.New("cannot parse IP address")
}
ip.IP = address
return nil
}
return errors.New(fmt.Sprintf("unknown scanner type: %T", src))
}

func (ipnet *NetworkIPNet) Scan(src interface{}) error {
switch data := src.(type) {
case []uint8:
_, parsedIpNet, err := net.ParseCIDR(string(data))
if err == nil {
ipnet.IPNet = *parsedIpNet
}
return err
}
return errors.New(fmt.Sprintf("unknown scanner type: %T", src))
}

func (ip *NetworkIP) Value() (driver.Value, error) {
if ip == nil {
return nil, nil
}
return ip.IP.String(), nil
}

func (ipnet *NetworkIPNet) Value() (driver.Value, error) {
if ipnet == nil {
return nil, nil
}
return ipnet.IPNet.String(), nil
}

func NewNetwork() *Network {
return &Network{
CreatedAt: time.Now(),
@@ -155,6 +68,22 @@ func NewNetwork() *Network {
}
}

// Interface will return the related network interface
func (network *Network) Interface() (*NetworkInterface, error) {
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
return nil, err
}
defer db.Close()

intf := NewNetworkInterface()
return intf, db.Get(
intf,
fmt.Sprintf(NetworkInterfaceQueryTmpl, "WHERE network_id=$1"),
network.ID,
)
}

// Exists directly updates the struct with the findings
func (network *Network) Exists() bool {
db, err := sqlx.Connect(dbDriver, dbConnect)


+ 32
- 0
model/network_interface.go View File

@@ -69,6 +69,38 @@ func NewNetworkInterface() *NetworkInterface {
}
}

// BridgeMasterInterface will return nil if the interface is not part of a bridge
// or the lookup failed. If successful it will return the master network interface
func (intf *NetworkInterface) BridgeMasterInterface() *NetworkInterface {
// Return if this interface is not part of a birdge setup
if intf.BridgeID == nil {
return nil
}
// Return itself if it has the master role
if intf.BridgeMaster {
return intf
}
// Lookup the master interface
var masterIntf NetworkInterface
db, err := sqlx.Connect(dbDriver, dbConnect)
if err != nil {
logger.Error().Err(err).Msg("cannot open database")
return nil
}
defer db.Close()

err = db.Get(&masterIntf, fmt.Sprintf(
NetworkInterfaceQueryTmpl,
"WHERE bridge_master=true AND bridge_id=$1",
), intf.BridgeID)

if err != nil {
logger.Debug().Err(err).Msg("cannot find master bridge interface")
return nil
}
return &masterIntf
}

// Network fetches the assigned ip network configuration for the interface
func (intf *NetworkInterface) Network() (network *Network) {
network = &Network{ID: intf.NetworkID}


+ 2
- 2
netfilter/nfq.go View File

@@ -337,10 +337,10 @@ func (q *Queue) CacheConnection(packet gopacket.Packet) {
q.db.connections[key.String()].Count++
} else {
source := model.NewIPInformationNested()
source.IP = types.NetIP(ipv4.SrcIP)
source.IP.Raw = ipv4.SrcIP

destination := model.NewIPInformationNested()
destination.IP = types.NetIP(ipv4.DstIP)
destination.IP.Raw = ipv4.DstIP

conn := model.NewConnection()
conn.TransportProtocol = *ipProto


+ 96
- 26
types/model.go View File

@@ -61,9 +61,6 @@ type ReportDate struct {
time.Time
}

// NetIP represents a net.IP struct with a scanner interface for sqlx
type NetIP net.IP

// StatisticKey represents the keys for the available statistics
type StatisticKey string

@@ -137,37 +134,110 @@ func (reportDate *ReportDate) Scan(src interface{}) error {
return nil
}

func (ip NetIP) Value() (driver.Value, error) {
return ip.String(), nil
// IP represents net.IP with a custom scanner interface for sqlx and json
type IP struct {
Raw net.IP
}

// IP represents net.IPNet with a custom scanner interface for sqlx and json
type IPNet struct {
Raw net.IPNet
}

func (ip IP) MarshalJSON() ([]byte, error) {
address := ip.String()
return json.Marshal(&address)
}

func (ipnet IPNet) MarshalJSON() ([]byte, error) {
network := ipnet.String()
return json.Marshal(&network)
}

func (ip *IP) UnmarshalJSON(data []byte) error {
var ipString string
err := json.Unmarshal(data, &ipString)
if err != nil {
return err
}
address := net.ParseIP(ipString)
if address == nil {
return errors.New("cannot parse IP address")
}
ip.Raw = address
return nil
}

func (ipnet *IPNet) UnmarshalJSON(data []byte) error {
var ipnetString string
err := json.Unmarshal(data, &ipnetString)
if err != nil {
return err
}
_, cidr, err := net.ParseCIDR(ipnetString)
if err != nil {
return err
}
ipnet.Raw = *cidr
return nil
}

func (ip *NetIP) Scan(src interface{}) error {
switch source := src.(type) {
case string:
*ip = NetIP(net.ParseIP(source))
if *ip == nil {
return errors.New("cannot parse IP")
}
case []byte:
*ip = NetIP(net.ParseIP(string(source)))
if *ip == nil {
return errors.New("cannot parse IP")
}
default:
return errors.New("incompatible type for NetIP")
func (ip *IP) Scan(src interface{}) error {
var s *string
switch data := src.(type) {
case string:
s = &data
case []byte:
dataString := string(data)
s = &dataString
}

if s == nil {
return errors.New(fmt.Sprintf("unknown scanner type: %T", src))
}

address := net.ParseIP(*s)
if address == nil {
return errors.New("cannot parse IP address")
}
ip.Raw = address
return nil
}

func (ip *NetIP) MarshalJSON() ([]byte, error) {
stringify := ip.String()
return json.Marshal(&stringify)
func (ipnet *IPNet) Scan(src interface{}) error {
switch data := src.(type) {
case []uint8:
_, parsedIpNet, err := net.ParseCIDR(string(data))
if err == nil {
ipnet.Raw = *parsedIpNet
}
return err
}
return errors.New(fmt.Sprintf("unknown scanner type: %T", src))
}

func (ip *IP) Value() (driver.Value, error) {
if ip == nil || len(ip.Raw) == 0 {
return nil, nil
}
return ip.String(), nil
}

func (ipnet *IPNet) Contains(address net.IP) bool {
return ipnet.Raw.Contains(address)
}

func (ipnet *IPNet) Value() (driver.Value, error) {
if ipnet == nil || len(ipnet.Raw.IP) == 0 {
return nil, nil
}
return ipnet.String(), nil
}

func (ip NetIP) Raw() net.IP {
return net.IP(ip)
func (ip *IP) String() string {
return ip.Raw.String()
}

func (ip *NetIP) String() string {
return ip.Raw().String()
func (ipnet *IPNet) String() string {
return ipnet.Raw.String()
}

+ 28
- 7
types/worker.go View File

@@ -16,24 +16,45 @@ package types
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

import "github.com/rs/zerolog"
import (
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/rs/zerolog"
)

// ConfigurationKey represents the keys for the available system configuration
type ConfigurationKey string

const (
AccessPointConfiguration ConfigurationKey = "AccessPointConfiguration"
WhiteListConfiguration ConfigurationKey = "WhiteListConfiguration"
PortalConfiguration ConfigurationKey = "PortalConfigration"
PatternConfiguration ConfigurationKey = "PatternConfiguration"
NetworkConfiguration ConfigurationKey = "NetworkConfiguration"
AccessPointConfiguration ConfigurationKey = "AccessPointConfiguration"
WhiteListConfiguration ConfigurationKey = "WhiteListConfiguration"
PortalConfiguration ConfigurationKey = "PortalConfigration"
PatternConfiguration ConfigurationKey = "PatternConfiguration"
NetworkConfiguration ConfigurationKey = "NetworkConfiguration"
NetworkDNSConfiguration ConfigurationKey = "NetworkDNSConfiguration"
NetworkDHCPClientConfiguration ConfigurationKey = "NetworkDHCPClientConfiguration"
NetworkDHCPServerConfiguration ConfigurationKey = "NetworkDHCPServerConfiguration"
)

// Worker is the minimum functionality a worker struct has to to implement
type Worker interface {
Name() string
Alive() bool
Oneshot() bool
Shutdown()
GracefulShutdown() bool
Logger(zerolog.Logger)
Run()
}

type Nclient4Logger struct {
zerolog.Logger
}

func (nclient4 Nclient4Logger) PrintMessage(prefix string, message *dhcpv4.DHCPv4) {
if message != nil {
nclient4.Logger.Debug().Msgf("%s%s", prefix, message.String())
}
}

func (nclient4 Nclient4Logger) Printf(format string, v ...interface{}) {
nclient4.Logger.Info().Msgf(format, v)
}

+ 157
- 18
worker/confd.go View File

@@ -36,14 +36,18 @@ const (
type ConfdService string

type ConfdWorker struct {
alive bool
alive *bool
logger zerolog.Logger
workers []types.Worker
dnsWorker []*DnsWorker
dhcpWorker []*DhcpServerWorker
dhcpClientWorker []*DhcpClientWorker
}

func NewConfdWorker(workers []types.Worker) *ConfdWorker {
alive := false
return &ConfdWorker{
alive: true,
alive: &alive,
workers: workers,
}
}
@@ -53,11 +57,15 @@ func (ConfdWorker) Name() string {
}

func (worker ConfdWorker) Alive() bool {
return worker.alive
return worker.alive != nil && *worker.alive
}

func (worker ConfdWorker) Oneshot() bool {
return false
func (worker ConfdWorker) Shutdown() {
worker.alive = nil
}

func (worker ConfdWorker) GracefulShutdown() bool {
return worker.alive == nil
}

func (worker *ConfdWorker) Logger(logger zerolog.Logger) {
@@ -66,19 +74,22 @@ func (worker *ConfdWorker) Logger(logger zerolog.Logger) {

func (worker *ConfdWorker) Run() {
defer func() {
worker.alive = false
if !worker.GracefulShutdown() {
*worker.alive = false
}
worker.logger.Info().Msg("stopping confd worker")
}()

worker.logger.Info().Msg("starting confd worker")
*worker.alive = true

// start up pending jobs and monitor them via the watcher worker
watcher := NewWatcherWorker(worker.workers)
watcher := NewWatcherWorker(&worker.workers)
watcher.Logger(worker.logger)
go watcher.Run()

// listen on configuration signals
for {
for worker.Alive() {
switch <-configChan {
case types.AccessPointConfiguration:
hostapd, ok := config.App.Confd["hostapd"].(string)
@@ -176,17 +187,32 @@ func (worker *ConfdWorker) Run() {
continue
}
}
// apply new configuration
err = link.SetLinkUp()
if err != nil {
worker.logger.Warn().Err(err).
Str("name", networkInterface.Name).
Msg("cannot set link up on ethernet interface")
}
// XXX remove interface from bridge configuration if necessary.
// This should be changed as soon as we introduce bridge support.
masterInterface := networkInterface.BridgeMasterInterface()
if masterInterface != nil {
bridge, err := tenus.BridgeFromName(masterInterface.Name)
if err != nil {
worker.logger.Error().Err(err).
Str("SlaveName", networkInterface.Name).
Str("MasterName", masterInterface.Name).
Msg("unkown bridge interface")
continue
}

if !network.Client {
ip := network.Address.IP
ipnet := &network.Network.IPNet
err = bridge.RemoveSlaveIfc(link.NetInterface())
if err != nil {
worker.logger.Error().Err(err).
Str("SlaveName", networkInterface.Name).
Str("MasterName", masterInterface.Name).
Msg("cannot remove link from bridge interface")
continue
}
}
// apply new configuration
if network.Address != nil && network.Network != nil {
ip := network.Address.Raw
ipnet := &network.Network.Raw

err = link.SetLinkIp(ip, ipnet)
if err != nil {
@@ -200,6 +226,15 @@ func (worker *ConfdWorker) Run() {
Str("network", ipnet.String()).
Msg("assigned address and network configuration to interface")
}

err = link.SetLinkUp()
if err != nil {
worker.logger.Error().Err(err).
Str("name", networkInterface.Name).
Msg("cannot set link up on ethernet interface")
continue
}

worker.logger.Debug().
Str("name", networkInterface.Name).
Msg("successfully configured the network interface")
@@ -212,6 +247,110 @@ func (worker *ConfdWorker) Run() {
iptables := NewIPTablesWorker(config.App.NfqueueNum)
iptables.Logger(worker.logger)
go iptables.Run() // this is a oneshot worker and can be executed multiple times
case types.NetworkDNSConfiguration:
for idx, server := range worker.dnsWorker {
if server != nil {
worker.logger.Debug().Msgf("shutting down %s worker", (*server).Name())
(*server).Shutdown()
}
// remove entry
worker.dnsWorker[idx] = worker.dnsWorker[len(worker.dnsWorker)-1]
worker.dnsWorker = worker.dnsWorker[:len(worker.dnsWorker)-1]
}

var networks model.Networks
err := networks.FindAll()
if err != nil {
worker.logger.Error().Err(err).Msg("cannot find all network definitions")
continue
}

for idx, network := range networks {
if !network.DNS {
continue
}

var records model.DnsRecords
err = records.FindByNetworkID(network.ID)
if err != nil {
worker.logger.Error().Err(err).
Str("network", network.Name).
Msg("cannot fetch DNS records")
continue
}

dnsWorker := NewDnsWorker(idx, network, nil, records)
dnsWorker.Logger(worker.logger)
// attach the new worker instance to both slices. The watcher
// worker will start the new worker on next tick
worker.dnsWorker = append(worker.dnsWorker, dnsWorker)
worker.workers = append(worker.workers, dnsWorker)
}

worker.logger.Debug().Msg("successfully restarted the DNS server")
case types.NetworkDHCPClientConfiguration:
for idx, client := range worker.dhcpClientWorker {
if client != nil {
worker.logger.Debug().Msgf("shutting down %s worker", (*client).Name())
(*client).Shutdown()
}
// remove entry
worker.dhcpClientWorker[idx] = worker.dhcpClientWorker[len(worker.dhcpClientWorker)-1]
worker.dhcpClientWorker = worker.dhcpClientWorker[:len(worker.dhcpClientWorker)-1]
}

var networkInterfaces model.NetworkInterfaces
err := networkInterfaces.FindAll()
if err != nil {
worker.logger.Error().Err(err).Msg("cannot find all network interfaces")
continue
}

for _, networkInterface := range networkInterfaces {
// skip all non-client interfaces
network := networkInterface.Network()
intf, _ := net.InterfaceByName(networkInterface.Name)
linkDown := intf != nil && intf.Flags & 1 != 1
if network == nil || !network.Client || linkDown {
if linkDown {
worker.logger.Debug().Msg("skipping interface because link is down")
}
continue
}

dhcpClient := NewDhcpClientWorker(networkInterface)
dhcpClient.Logger(worker.logger)

worker.dhcpClientWorker = append(worker.dhcpClientWorker, dhcpClient)
worker.workers = append(worker.workers, dhcpClient)
}
worker.logger.Debug().Msg("successfully configured DHCP clients")
case types.NetworkDHCPServerConfiguration:
for idx, server := range worker.dhcpWorker {
if server != nil {
worker.logger.Debug().Msgf("shutting down %s worker", (*server).Name())
(*server).Shutdown()
}
// remove entry
worker.dhcpWorker[idx] = worker.dhcpWorker[len(worker.dhcpWorker)-1]
worker.dhcpWorker = worker.dhcpWorker[:len(worker.dhcpWorker)-1]
}

var networks model.Networks
err := networks.FindAll()
if err != nil {
worker.logger.Error().Err(err).Msg("cannot find all network definitions")
continue
}

dhcpWorker := NewDhcpServerWorker(networks)
dhcpWorker.Logger(worker.logger)
// attach the new worker instance to both slices. The watcher
// worker will start the new worker on next tick
worker.dhcpWorker = append(worker.dhcpWorker, dhcpWorker)
worker.workers = append(worker.workers, dhcpWorker)

worker.logger.Debug().Msg("successfully restarted the DHCP server")
case types.PortalConfiguration:
// nothing todo, yet
default:


+ 0
- 177
worker/dhcp.go View File

@@ -1,177 +0,0 @@
package worker
//
// Fengg Security Gateway Server Application
// Copyright (C) 2020 Lukas Matt <support@fen.gg>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
import (
cdlogger "github.com/coredhcp/coredhcp/logger"
cdconfig "github.com/coredhcp/coredhcp/config"
cdserver "github.com/coredhcp/coredhcp/server"
cdplugins "github.com/coredhcp/coredhcp/plugins"
cddns "github.com/coredhcp/coredhcp/plugins/dns"
cdfile "github.com/coredhcp/coredhcp/plugins/file"
cdleasetime "github.com/coredhcp/coredhcp/plugins/leasetime"
cdnbp "github.com/coredhcp/coredhcp/plugins/nbp"
cdnetmask "github.com/coredhcp/coredhcp/plugins/netmask"
cdprefix "github.com/coredhcp/coredhcp/plugins/prefix"
cdrange "github.com/coredhcp/coredhcp/plugins/range"
cdrouter "github.com/coredhcp/coredhcp/plugins/router"
cdserverid "github.com/coredhcp/coredhcp/plugins/serverid"

"github.com/sirupsen/logrus"
"github.com/rs/zerolog"
)

type DhcpWorker struct {
alive bool
logger zerolog.Logger
plugins []*cdplugins.Plugin
}

func NewDhcpWorker() *DhcpWorker {
return &DhcpWorker{
alive: true,
plugins: []*cdplugins.Plugin{
&cddns.Plugin,
&cdfile.Plugin,
&cdleasetime.Plugin,
&cdnbp.Plugin,
&cdnetmask.Plugin,
&cdprefix.Plugin,
&cdrange.Plugin,
&cdrouter.Plugin,
&cdserverid.Plugin,
},
}
}

func (DhcpWorker) Name() string {
return "Dhcp"
}

func (worker DhcpWorker) Alive() bool {
return worker.alive
}

func (worker DhcpWorker) Oneshot() bool {
return false
}

func (worker *DhcpWorker) Logger(logger zerolog.Logger) {
worker.logger = logger.With().Str("worker", worker.Name()).Logger()
}

func (worker *DhcpWorker) Run() {
defer func() {
worker.alive = false
worker.logger.Info().Msg("stopping dhcp worker")
}()

worker.logger.Info().Msg("starting dhcp worker")

cdlog := cdlogger.GetLogger("main")
cdlog.Logger.SetOutput(worker.logger)
cdlog.Logger.SetLevel(logrus.InfoLevel)

config := cdconfig.New()
config.Server4 = &cdconfig.ServerConfig{
Plugins: []cdconfig.PluginConfig{
{
Name: "lease_time",
Args: []string{"3600s"},
},
{
Name: "server_id",
Args: []string{"10.10.10.1"},
},
{
Name: "dns",
Args: []string{
"1.1.1.1",
"1.0.0.1",
},
},
{
Name: "router",
Args: []string{"192.168.1.1"},
},
{
Name: "netmask",
Args: []string{"255.255.255.0"},
},
{
Name: "range",
Args: []string{
"leases.txt",
"10.10.10.100",
"10.10.10.200",
"60s",
},
},
},
}
config.Server6 = &cdconfig.ServerConfig{
Plugins: []cdconfig.PluginConfig{
{
Name: "server_id",
Args: []string{
"LL",
"00:de:ad:be:ef:00",
},
},
{
Name: "file",
Args: []string{"leases.txt"},
},
{
Name: "dns",
Args: []string{
"2606:4700:4700::1111",
"2606:4700:4700::1001",
},
},
{
Name: "nbp",
Args: []string{"http://[2001:db8:a::1]/nbp"},
},
{
Name: "prefix",
Args: []string{
"2001:db8::/48",
"64",
},
},
},
}

// register plugins
for _, plugin := range worker.plugins {
if err := cdplugins.RegisterPlugin(plugin); err != nil {
worker.logger.Error().Err(err).Msgf("failed to register plugin: %s", plugin.Name)
return
}
}

// start dhcp server
srv, err := cdserver.Start(config)
if err != nil {
worker.logger.Error().Err(err).Msg("cannot start dhcp server")
return
}
if err := srv.Wait(); err != nil {
worker.logger.Error().Err(err).Msg("dhcp server stopped")
return
}
}

+ 130
- 0
worker/dhcp_client.go View File

@@ -0,0 +1,130 @@
package worker
//
// Fengg Security Gateway Server Application
// Copyright (C) 2020 Lukas Matt <support@fen.gg>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
import (
"time"
"context"
"net"

"tea.fen.gg/fengg/server/types"
"tea.fen.gg/fengg/server/model"
"github.com/insomniacslk/dhcp/dhcpv4"
"github.com/insomniacslk/dhcp/dhcpv4/nclient4"
"github.com/rs/zerolog"
)

type DhcpClientWorker struct {
alive *bool
networkInterface model.NetworkInterface
logger zerolog.Logger
}

func NewDhcpClientWorker(intf model.NetworkInterface) *DhcpClientWorker {
alive := false
return &DhcpClientWorker{
alive: &alive,
networkInterface: intf,
}
}

func (DhcpClientWorker) Name() string {
return "DhcpClient"
}

func (worker DhcpClientWorker) Alive() bool {
return worker.alive != nil && *worker.alive
}

func (worker *DhcpClientWorker) Shutdown() {
worker.alive = nil
}

func (worker DhcpClientWorker) GracefulShutdown() bool {
return worker.alive == nil
}

func (worker *DhcpClientWorker) Logger(logger zerolog.Logger) {
worker.logger = logger.With().
Str("worker", worker.Name()).
Str("interface", worker.networkInterface.Name).Logger()
}

func (worker *DhcpClientWorker) Run() {
defer func() {
if !worker.GracefulShutdown() {
*worker.alive = false
}
worker.logger.Info().Msg("stopping dhcp client worker")
}()

worker.logger.Info().Msg("starting dhcp client worker")
*worker.alive = true

ctx := context.Background()
// retry unless successful
for worker.Alive() {
time.Sleep(5 * time.Second)

worker.logger.Debug().Msg("running dhcp client iteration")

client, err := nclient4.New(
worker.networkInterface.Name,
nclient4.WithLogger(types.Nclient4Logger{worker.logger}),
)
if err != nil {
worker.logger.Error().Err(err).Msg("cannot initialize client")
return
}

lease, err := client.Request(ctx)
if err != nil {
worker.logger.Error().Err(err).Msg("dhcp request from offer failed")
return
}

network := worker.networkInterface.Network()
if network == nil {
worker.logger.Error().Err(err).Msg("cannot find network")
return
}

if lease.ACK != nil {
mask := lease.ACK.Options.Get(dhcpv4.OptionSubnetMask)
ipnet := net.IPNet{
Mask: mask, IP: lease.ACK.YourIPAddr.Mask(mask),
}

worker.logger.Debug().
Str("address", lease.ACK.YourIPAddr.String()).
Str("network", ipnet.String()).
Msg("found lease for client")

network.Address = &types.IP{Raw: lease.ACK.YourIPAddr}
network.Network = &types.IPNet{Raw: ipnet}

err = network.Update()
if err != nil {
worker.logger.Error().Err(err).Msg("dhcp request failed")
return
}
// trigger network configuration
ConfigChannel(types.NetworkConfiguration)
// shutdown dhcp client
worker.Shutdown()
}
}
}

+ 275
- 0
worker/dhcp_server.go View File

@@ -0,0 +1,275 @@
package worker
//
// Fengg Security Gateway Server Application
// Copyright (C) 2020 Lukas Matt <support@fen.gg>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//
import (
"os"
"net"
"fmt"
"time"

"github.com/insomniacslk/dhcp/dhcpv4"
cdlogger "github.com/coredhcp/coredhcp/logger"
cdconfig "github.com/coredhcp/coredhcp/config"
cdserver "github.com/coredhcp/coredhcp/server"
cdplugins "github.com/coredhcp/coredhcp/plugins"
cddns "github.com/coredhcp/coredhcp/plugins/dns"
cdfile "github.com/coredhcp/coredhcp/plugins/file"
cdleasetime "github.com/coredhcp/coredhcp/plugins/leasetime"
cdnbp "github.com/coredhcp/coredhcp/plugins/nbp"
cdnetmask "github.com/coredhcp/coredhcp/plugins/netmask"
cdprefix "github.com/coredhcp/coredhcp/plugins/prefix"
cdrange "github.com/coredhcp/coredhcp/plugins/range"
cdrouter "github.com/coredhcp/coredhcp/plugins/router"
cdserverid "github.com/coredhcp/coredhcp/plugins/serverid"

"tea.fen.gg/fengg/server/model"
"github.com/sirupsen/logrus"
"github.com/rs/zerolog"
)

type DhcpServerWorker struct {
alive *bool
logger zerolog.Logger
networks model.Networks
plugins []*cdplugins.Plugin
}

func NewDhcpServerWorker(networks model.Networks) *DhcpServerWorker {
alive := false
return &DhcpServerWorker{
alive: &alive,
networks: networks,
plugins: []*cdplugins.Plugin{
&cddns.Plugin,
&cdfile.Plugin,
&cdleasetime.Plugin,
&cdnbp.Plugin,
&cdnetmask.Plugin,
&cdprefix.Plugin,
&cdrange.Plugin,
&cdrouter.Plugin,
&cdserverid.Plugin,
},
}
}

func (DhcpServerWorker) Name() string {
return "DhcpServer"
}

func (worker DhcpServerWorker) Alive() bool {
return worker.alive != nil && *worker.alive
}

func (worker *DhcpServerWorker) Shutdown() {
worker.alive = nil
}

func (worker DhcpServerWorker) GracefulShutdown() bool {
return worker.alive == nil
}

func (worker *DhcpServerWorker) Logger(logger zerolog.Logger) {
worker.logger = logger.With().Str("worker", worker.Name()).Logger()
}

func (worker *DhcpServerWorker) Run() {
defer func() {
if !worker.GracefulShutdown() {
*worker.alive = false
}
worker.logger.Info().Msg("stopping dhcp worker")
}()

worker.logger.Info().Msg("starting dhcp worker")
*worker.alive = true

dir, err := os.Getwd()
if err != nil {
worker.logger.Warn().Msg("cannot retrieve current working directory. Using /tmp ...")
dir = "/tmp"
}

cdlog := cdlogger.GetLogger("main")
cdlog.Logger.SetOutput(worker.logger)
cdlog.Logger.SetLevel(logrus.DebugLevel)

// register plugins
for _, plugin := range worker.plugins {
if _, ok := cdplugins.RegisteredPlugins[plugin.Name]; ok {
continue
}

if err := cdplugins.RegisterPlugin(plugin); err != nil {
worker.logger.Error().Err(err).Msgf("failed to register plugin: %s", plugin.Name)
return
}
}

// create server configurations
var configs []*cdconfig.Config
for _, network := range worker.networks {
if !network.DHCP {
continue
}
// create lease files if it does not exist
ipv4Lease := fmt.Sprintf("%s/DHCP4-%d.lease", dir, network.ID)
// ipv6Lease := fmt.Sprintf("%s/DHCP6-%d.lease", dir, network.ID)

// calculate IP range for serving addresses
first, last := worker.ipRange(network.Address.Raw, &network.Network.Raw)
// fetch interface name
intf, err := network.Interface()
if err != nil {
worker.logger.Error().Err(err).Msg("failed to fetch network interface")
return
}

worker.logger.Debug().Msgf(
"listening on %s:%d with IP address %s and range %s to %s",
intf.Name,
dhcpv4.ServerPort,
net.IPv4zero,
first,
last,
)

config := cdconfig.New()
config.Server4 = &cdconfig.ServerConfig{
Addresses: []net.UDPAddr{
{
IP: net.IPv4zero,
// IP: network.Address.Raw,
Zone: intf.Name,
Port: dhcpv4.ServerPort,
},
},
Plugins: []cdconfig.PluginConfig{
{
Name: "lease_time",
Args: []string{"3600s"},
},
{
Name: "server_id",
Args: []string{network.Address.String()},
},
{
Name: "dns",
Args: []string{
network.Address.String(),
},
},
{
Name: "router",
Args: []string{network.Address.String()},
},
{
Name: "netmask",
Args: []string{
fmt.Sprintf(
"%d.%d.%d.%d",
network.Network.Raw.Mask[0],
network.Network.Raw.Mask[1],
network.Network.Raw.Mask[2],
network.Network.Raw.Mask[3],
),
},
},
{
Name: "range",
Args: []string{ipv4Lease, first, last, "60s"},
},
},
}
//config.Server6 = &cdconfig.ServerConfig{
// Plugins: []cdconfig.PluginConfig{
// {
// Name: "server_id",
// Args: []string{
// "LL",
// "00:de:ad:be:ef:00",
// },
// },
// {
// Name: "file",
// Args: []string{"/tmp/leases6.txt"},
// },
// {
// Name: "dns",
// Args: []string{
// "2606:4700:4700::1111",
// "2606:4700:4700::1001",
// },
// },
// {
// Name: "nbp",
// Args: []string{"http://[2001:db8:a::1]/nbp"},
// },
// {
// Name: "prefix",
// Args: []string{
// "2001:db8::/48",
// "64",
// },
// },
// },
//}
configs = append(configs, config)
}

// start dhcp server
var servers []*cdserver.Servers
for _, config := range configs {
srv, err := cdserver.Start(config)
if err != nil {
worker.logger.Error().Err(err).Msg("cannot start dhcp server")
return
}
servers = append(servers, srv)
}

for worker.Alive() {
worker.logger.Debug().Msgf("running %d servers", len(servers))
time.Sleep(10 * time.Second)
}

for _, server := range servers {
// gracefully shutdown all running services
server.Close()
}
}

func (worker DhcpServerWorker) ipRange(ip net.IP, ipnet *net.IPNet) (string, string) {
var addresses []string
gwAddress := ip.String()
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); worker.inc(ip) {
if ip.String() == gwAddress {
continue
}
addresses = append(addresses, ip.String())
}
return addresses[1], addresses[len(addresses) - 2]
}

func (worker DhcpServerWorker) inc(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

+ 97
- 47
worker/dns.go View File

@@ -17,36 +17,62 @@ package worker
//
import (
"fmt"
"net"
"time"

"tea.fen.gg/fengg/server/model"
"github.com/miekg/dns"
"github.com/rs/zerolog"
)

// DnsWorker is the DNS server running on the specified network interfaces
type DnsWorker struct {
alive bool
id int
alive *bool
logger zerolog.Logger
records map[string]string
network model.Network
records map[string]model.DnsRecord
fallbackServer net.IP
}

func NewDnsWorker() *DnsWorker {
return &DnsWorker{
alive: true,
records: map[string]string{
"test.service.": "159.69.247.180",
},
// NewDnsWorker will return a new instance of a DnsWorker. Setting network to nil will
// force the DNS server to listen on all interfaces. That should happen only once!
// In case the fallbackServer is nil 1.1.1.1 wil be used as default fallback server.
func NewDnsWorker(id int, network model.Network, fallbackServer net.IP, records model.DnsRecords) *DnsWorker {
mappedRecords := make(map[string]model.DnsRecord)
for _, record := range records {
mappedRecords[record.Name] = record
}

alive := false
worker := &DnsWorker{
id: id,
alive: &alive,
network: network,
records: mappedRecords,
}

if fallbackServer == nil {
// use default DNS fallback server
worker.fallbackServer = net.ParseIP("1.1.1.1")
}
return worker
}

func (DnsWorker) Name() string {
return "Dns"
func (worker DnsWorker) Name() string {
return fmt.Sprintf("Dns[%d]", worker.id)
}

func (worker DnsWorker) Alive() bool {
return worker.alive
return worker.alive != nil && *worker.alive
}

func (worker DnsWorker) Oneshot() bool {
return false
func (worker *DnsWorker) Shutdown() {
worker.alive = nil
}

func (worker DnsWorker) GracefulShutdown() bool {
return worker.alive == nil
}

func (worker *DnsWorker) Logger(logger zerolog.Logger) {
@@ -55,67 +81,91 @@ func (worker *DnsWorker) Logger(logger zerolog.Logger) {

func (worker *DnsWorker) Run() {
defer func() {
worker.alive = false
if !worker.GracefulShutdown() {
*worker.alive = false
}
worker.logger.Info().Msg("stopping dns worker")
}()

worker.logger.Info().Msg("starting dns worker")
*worker.alive = true

// attach request handler func
dns.HandleFunc(".", worker.handleDnsRequest)

// start server
port := 5353
server := &dns.Server{Addr: fmt.Sprintf(":%d", port), Net: "udp"}
worker.logger.Info().Msgf("starting at %d", port)
err := server.ListenAndServe()
port, address := 53, ""
if worker.network.Address != nil {
address = worker.network.Address.String()
}

server := &dns.Server{Addr: fmt.Sprintf("%s:%d", address, port), Net: "udp"}
defer server.Shutdown()
if err != nil {
worker.logger.Error().Err(err).Msg("failed to start dns server")

worker.logger.Info().Msgf("starting at %s:%d", address, port)
go func() {
defer func() {
*worker.alive = false
}()

err := server.ListenAndServe()
if err != nil {
worker.logger.Error().Err(err).Msg("failed to start dns server")
}
}()

for worker.Alive() {
worker.logger.Debug().Msgf("running at %s:%d", address, port)
time.Sleep(10 * time.Second)
}
}

func (worker *DnsWorker) handleDnsRequest(w dns.ResponseWriter, r *dns.Msg) {
m := new(dns.Msg)
m.SetReply(r)
m.Compress = false
func (worker *DnsWorker) handleDnsRequest(writer dns.ResponseWriter, reply *dns.Msg) {
message := new(dns.Msg)
message.SetReply(reply)
message.Compress = false

switch r.Opcode {
switch reply.Opcode {
case dns.OpcodeQuery:
worker.parseQuery(m)
worker.parseQuery(message)
}

w.WriteMsg(m)
writer.WriteMsg(message)
}

func (worker *DnsWorker) parseQuery(m *dns.Msg) {
for _, q := range m.Question {
switch q.Qtype {
func (worker *DnsWorker) parseQuery(message *dns.Msg) {
for _, question := range message.Question {
switch question.Qtype {
case dns.TypeA:
worker.logger.Info().Msgf("dns query %s", q.Name)
ip := worker.records[q.Name]
if ip != "" {
rr, err := dns.NewRR(fmt.Sprintf("%s A %s", q.Name, ip))
worker.logger.Debug().Msgf("dns A query %s", question.Name)
if record, ok := worker.records[question.Name]; ok && record.IPv4 != nil {
rr, err := dns.NewRR(fmt.Sprintf("%s A %s", question.Name, record.IPv4))
if err == nil {
message.Answer = append(message.Answer, rr)
}
}
case dns.TypeAAAA:
worker.logger.Debug().Msgf("dns AAAA query %s", question.Name)
if record, ok := worker.records[question.Name]; ok && record.IPv6 != nil {
rr, err := dns.NewRR(fmt.Sprintf("%s AAAA %s", question.Name, record.IPv6))
if err == nil {
m.Answer = append(m.Answer, rr)
message.Answer = append(message.Answer, rr)
}
}
}
}
// if no answer was given forward the message to external services
if len(message.Answer) == 0 {
worker.logger.Debug().Msgf("no answer, forwarding message to %s", worker.fallbackServer)

worker.logger.Info().Msgf("len %d", len(m.Answer))
if len(m.Answer) == 0 {
worker.logger.Info().Msg("forwarding message")
client := new(dns.Client)
outMessage := new(dns.Msg)
outMessage.Id = dns.Id()
outMessage.RecursionDesired = true
outMessage.Question = message.Question

c := new(dns.Client)
m1 := new(dns.Msg)
m1.Id = dns.Id()
m1.RecursionDesired = true
m1.Question = m.Question
in, _, err := c.Exchange(m1, "1.1.1.1:53")
in, _, err := client.Exchange(outMessage, fmt.Sprintf("%s:53", worker.fallbackServer))
if err == nil {
m.Answer = append(m.Answer, in.Answer...)
message.Answer = append(message.Answer, in.Answer...)
}
worker.logger.Info().Msgf(":: %v :: %v", err, in)
}
}

+ 14
- 6
worker/frontend.go View File

@@ -28,14 +28,15 @@ import (
)

type FrontendWorker struct {
alive bool
alive *bool
staticDirectory string
logger zerolog.Logger
}

func NewFrontendWorker(directory string) *FrontendWorker {
alive := false
return &FrontendWorker{
alive: true,
alive: &alive,
staticDirectory: directory,
}
}
@@ -45,11 +46,15 @@ func (FrontendWorker) Name() string {
}

func (worker FrontendWorker) Alive() bool {
return worker.alive
return worker.alive != nil && *worker.alive
}

func (worker FrontendWorker) Oneshot() bool {
return false
func (worker *FrontendWorker) Shutdown() {
worker.alive = nil
}

func (worker FrontendWorker) GracefulShutdown() bool {
return worker.alive == nil
}

func (worker *FrontendWorker) Logger(logger zerolog.Logger) {
@@ -58,11 +63,14 @@ func (worker *FrontendWorker) Logger(logger zerolog.Logger) {

func (worker *FrontendWorker) Run() {
defer func() {
worker.alive = false
if !worker.GracefulShutdown() {
*worker.alive = false
}
worker.logger.Info().Msg("stopping frontend worker")
}()

worker.logger.Info().Msg("starting frontend worker")
*worker.alive = true

// intercept sigterm to clean-up leftovers
sigChan := make(chan os.Signal)