diff --git a/README.md b/README.md index 4ae0d6b..0ef62e5 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ glider -verbose -listen :8443 -forward SCHEME://HOST:PORT #### Help
-`glider -help` (click) +glider -help ```bash Usage: glider [-listen URL]... [-forward URL]... [OPTION]... @@ -228,7 +228,7 @@ glider 0.16.0, https://github.com/nadoo/glider #### Schemes
-`glider -scheme all` (click) +glider -scheme all ```bash KCP scheme: @@ -343,7 +343,7 @@ TLS and Websocket with a specified proxy protocol: #### Examples
-`glider -example` (click) +glider -example ```bash Examples: @@ -396,9 +396,8 @@ Examples: - service=dhcpd,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...] - service=dhcpd,eth1,192.168.1.100,192.168.1.199,720 - service=dhcpd,eth2,192.168.2.100,192.168.2.199,720,fc:23:34:9e:25:01=192.168.2.101 - -- dhcpd-failover: only serves requests when there's no other dhcp server exists - service=dhcpd-failover,INTERFACE,START_IP,END_IP,LEASE_MINUTES[,MAC=IP,MAC=IP...] + - note: only serve requests when there's no other dhcp server exists ## Linux Service diff --git a/go.mod b/go.mod index f3fe182..665b6cc 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/nadoo/ipset v0.4.1-0.20220218075046-ca3cdce74266 github.com/xtaci/kcp-go/v5 v5.6.1 golang.org/x/crypto v0.0.0-20220214200702-86341886e292 - golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c + golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99 ) require ( diff --git a/go.sum b/go.sum index aa7574f..ab579f2 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,7 @@ github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqo github.com/jsimonetti/rtnetlink v0.0.0-20201110080708-d2c240429e6c/go.mod h1:huN4d1phzjhlOsNIjFsw2SVRbwIHj3fJDMEU2SDPTmg= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/klauspost/cpuid v1.2.4/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A= @@ -164,8 +165,8 @@ golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c h1:sSIdNI2Dd6vGv47bKc/xArpfxVmEz2+3j0E6I484xC4= -golang.org/x/sys v0.0.0-20220222200937-f2425489ef4c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99 h1:Us899Z5PCfOrSgeCYWobI1/bSigAz9Rhf8+fz5Grkzc= +golang.org/x/sys v0.0.0-20220224003255-dbe011f71a99/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/main.go b/main.go index 89650ca..8f36aed 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,6 @@ import ( "net" "os" "os/signal" - "strings" "syscall" "time" @@ -81,8 +80,11 @@ func main() { // run services for _, s := range config.Services { - args := strings.Split(s, ",") - go service.Run(args[0], args[1:]...) + service, err := service.New(s) + if err != nil { + log.Fatal(err) + } + go service.Run() } sigCh := make(chan os.Signal, 1) diff --git a/pkg/sockopt/bind_darwin.go b/pkg/sockopt/bind_darwin.go deleted file mode 100644 index 2088d3d..0000000 --- a/pkg/sockopt/bind_darwin.go +++ /dev/null @@ -1,21 +0,0 @@ -package sockopt - -import ( - "net" - "syscall" - - "golang.org/x/sys/unix" -) - -func BindControl(iface *net.Interface) func(network, address string, c syscall.RawConn) error { - return func(network, address string, c syscall.RawConn) (err error) { - return c.Control(func(fd uintptr) { - switch network { - case "tcp4", "udp4": - err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, iface.Index) - case "tcp6", "udp6": - err = unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, iface.Index) - } - }) - } -} diff --git a/pkg/sockopt/bind_linux.go b/pkg/sockopt/bind_linux.go deleted file mode 100644 index 91cc025..0000000 --- a/pkg/sockopt/bind_linux.go +++ /dev/null @@ -1,16 +0,0 @@ -package sockopt - -import ( - "net" - "syscall" - - "golang.org/x/sys/unix" -) - -func BindControl(iface *net.Interface) func(network, address string, c syscall.RawConn) error { - return func(network, address string, c syscall.RawConn) (err error) { - return c.Control(func(fd uintptr) { - err = unix.BindToDevice(int(fd), iface.Name) - }) - } -} diff --git a/pkg/sockopt/sockopt.go b/pkg/sockopt/sockopt.go new file mode 100644 index 0000000..edca48d --- /dev/null +++ b/pkg/sockopt/sockopt.go @@ -0,0 +1,31 @@ +package sockopt + +import ( + "net" + "syscall" +) + +// Options is the options struct. +type Options struct { + bindIface *net.Interface + reuseAddr bool +} + +// Option is the function paramater. +type Option func(opts *Options) + +// Bind sets the bind interface option. +func Bind(intf *net.Interface) Option { return func(opts *Options) { opts.bindIface = intf } } + +// ReuseAddr sets the reuse addr option. +func ReuseAddr() Option { return func(opts *Options) { opts.reuseAddr = true } } + +// Control returns a control function for the net.Dialer and net.ListenConfig. +func Control(opts ...Option) func(network, address string, c syscall.RawConn) error { + option := &Options{} + for _, opt := range opts { + opt(option) + } + + return control(option) +} diff --git a/pkg/sockopt/sockopt_darwin.go b/pkg/sockopt/sockopt_darwin.go new file mode 100644 index 0000000..bef050a --- /dev/null +++ b/pkg/sockopt/sockopt_darwin.go @@ -0,0 +1,28 @@ +package sockopt + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +func control(opt *Options) func(network, address string, c syscall.RawConn) error { + return func(network, address string, c syscall.RawConn) (err error) { + return c.Control(func(fd uintptr) { + + if opt.bindIface != nil { + switch network { + case "tcp4", "udp4": + unix.SetsockoptInt(int(fd), unix.IPPROTO_IP, unix.IP_BOUND_IF, opt.bindIface.Index) + case "tcp6", "udp6": + unix.SetsockoptInt(int(fd), unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF, opt.bindIface.Index) + } + } + if opt.reuseAddr { + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + } + + }) + } +} diff --git a/pkg/sockopt/sockopt_linux.go b/pkg/sockopt/sockopt_linux.go new file mode 100644 index 0000000..be6284b --- /dev/null +++ b/pkg/sockopt/sockopt_linux.go @@ -0,0 +1,23 @@ +package sockopt + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +func control(opt *Options) func(network, address string, c syscall.RawConn) error { + return func(network, address string, c syscall.RawConn) (err error) { + return c.Control(func(fd uintptr) { + + if opt.bindIface != nil { + unix.BindToDevice(int(fd), opt.bindIface.Name) + } + if opt.reuseAddr { + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1) + unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEPORT, 1) + } + + }) + } +} diff --git a/pkg/sockopt/bind_others.go b/pkg/sockopt/sockopt_others.go similarity index 51% rename from pkg/sockopt/bind_others.go rename to pkg/sockopt/sockopt_others.go index c5262e0..44ffc55 100644 --- a/pkg/sockopt/bind_others.go +++ b/pkg/sockopt/sockopt_others.go @@ -8,4 +8,4 @@ import ( "syscall" ) -func BindControl(iface *net.Interface) func(string, string, syscall.RawConn) error { return nil } +func control(opt *Options) func(string, string, syscall.RawConn) error { return nil } diff --git a/proxy/direct.go b/proxy/direct.go index 99d9de3..2487a59 100644 --- a/proxy/direct.go +++ b/proxy/direct.go @@ -89,7 +89,7 @@ func (d *Direct) dial(network, addr string, localIP net.IP) (net.Conn, error) { dialer := &net.Dialer{LocalAddr: la, Timeout: d.dialTimeout} if d.iface != nil { - dialer.Control = sockopt.BindControl(d.iface) + dialer.Control = sockopt.Control(sockopt.Bind(d.iface)) } c, err := dialer.Dial(network, addr) @@ -117,7 +117,7 @@ func (d *Direct) DialUDP(network, addr string) (net.PacketConn, net.Addr, error) lc := &net.ListenConfig{} if d.iface != nil { - lc.Control = sockopt.BindControl(d.iface) + lc.Control = sockopt.Control(sockopt.Bind(d.iface)) } pc, err := lc.ListenPacket(context.Background(), network, la) diff --git a/service/dhcpd/cilent.go b/service/dhcpd/cilent.go index 55316f6..54976f2 100644 --- a/service/dhcpd/cilent.go +++ b/service/dhcpd/cilent.go @@ -11,16 +11,15 @@ import ( ) func discovery(intf *net.Interface) (found bool) { - lc := &net.ListenConfig{} - lc.Control = sockopt.BindControl(intf) + lc := &net.ListenConfig{Control: sockopt.Control(sockopt.Bind(intf), sockopt.ReuseAddr())} - pc, err := lc.ListenPacket(context.Background(), "udp4", "255.255.255.255:68") + pc, err := lc.ListenPacket(context.Background(), "udp4", ":68") if err != nil { return } defer pc.Close() - discovery, err := dhcpv4.NewDiscovery(intf.HardwareAddr, dhcpv4.WithBroadcast(true)) + discovery, err := dhcpv4.NewDiscovery(intf.HardwareAddr, dhcpv4.WithBroadcast(true), dhcpv4.WithRequestedOptions(dhcpv4.OptionDomainNameServer)) if err != nil { return } @@ -37,10 +36,6 @@ func discovery(intf *net.Interface) (found bool) { return } - msg, err := dhcpv4.FromBytes(buf[:n]) - if err != nil || msg.TransactionID != discovery.TransactionID { - return - } - - return true + _, err = dhcpv4.FromBytes(buf[:n]) + return err == nil } diff --git a/service/dhcpd/dhcpd.go b/service/dhcpd/dhcpd.go index 44b7b98..7a0585e 100644 --- a/service/dhcpd/dhcpd.go +++ b/service/dhcpd/dhcpd.go @@ -3,6 +3,7 @@ package dhcpd import ( "bytes" "errors" + "fmt" "net" "net/netip" "strconv" @@ -17,60 +18,60 @@ import ( "github.com/nadoo/glider/service" ) -var leaseTime = time.Hour * 12 - func init() { - service.Register("dhcpd", &dhcpd{}) - service.Register("dhcpd-failover", &dhcpd{detect: true}) + service.Register("dhcpd", NewService) + service.Register("dhcpd-failover", NewFailOverService) } type dhcpd struct { mu sync.Mutex - detect bool failover bool - intface *net.Interface + + name string + pool *Pool + lease time.Duration + iface *net.Interface + server *server4.Server } -// Run runs the service. -func (d *dhcpd) Run(args ...string) { +// NewService returns a new dhcpd Service. +func NewService(args ...string) (service.Service, error) { return New(false, args...) } + +// NewService returns a new dhcpd Service with failover mode on. +func NewFailOverService(args ...string) (service.Service, error) { return New(true, args...) } + +// New returns a new dhcpd instance. +func New(failover bool, args ...string) (*dhcpd, error) { if len(args) < 4 { - log.F("[dhcpd] not enough parameters, exiting") - return + return nil, errors.New("not enough parameters, exiting") } iface, start, end, leaseMin := args[0], args[1], args[2], args[3] - if i, err := strconv.Atoi(leaseMin); err != nil { - leaseTime = time.Duration(i) * time.Minute - } - intf, ip, mask, err := ifaceAddr(iface) if err != nil { - log.F("[dhcpd] get ip of interface '%s' error: %s", iface, err) - return - } - d.intface = intf - - if d.detect { - d.setFailoverMode(discovery(intf)) - go d.detectServer(time.Second * 60) + return nil, fmt.Errorf("get ip of interface '%s' error: %s", iface, err) } startIP, err := netip.ParseAddr(start) if err != nil { - log.F("[dhcpd] startIP %s is not valid: %s", start, err) - return + return nil, fmt.Errorf("startIP %s is not valid: %s", start, err) } endIP, err := netip.ParseAddr(end) if err != nil { - log.F("[dhcpd] endIP %s is not valid: %s", end, err) - return + return nil, fmt.Errorf("endIP %s is not valid: %s", end, err) } - pool, err := NewPool(leaseTime, startIP, endIP) + var lease = time.Hour * 12 + if i, err := strconv.Atoi(leaseMin); err == nil { + lease = time.Duration(i) * time.Minute + } else { + return nil, fmt.Errorf("LEASE_MINUTES %s is not valid: %s", end, err) + } + + pool, err := NewPool(lease, startIP, endIP) if err != nil { - log.F("[dhcpd] error in pool init: %s", err) - return + return nil, fmt.Errorf("error in pool init: %s", err) } // static ips @@ -84,17 +85,33 @@ func (d *dhcpd) Run(args ...string) { } } - laddr := net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67} - server, err := server4.NewServer(iface, &laddr, d.handleDHCP(ip, mask, pool)) - if err != nil { - log.F("[dhcpd] error in server creation: %s", err) - return + dhcpd := &dhcpd{ + name: intf.Name, + iface: intf, + pool: pool, + lease: lease, + failover: failover, } - log.F("[dhcpd] Listening on interface %s(%s/%d.%d.%d.%d), server detection: %t", - iface, ip, mask[0], mask[1], mask[2], mask[3], d.detect) + if dhcpd.server, err = server4.NewServer( + iface, &net.UDPAddr{IP: net.IPv4(0, 0, 0, 0), Port: 67}, + dhcpd.handleDHCP(ip, mask, pool)); err != nil { + return nil, fmt.Errorf("error in server creation: %s", err) + } - server.Serve() + log.F("[dhcpd] Listening on interface %s(%s/%d.%d.%d.%d), failover mode: %t", + iface, ip, mask[0], mask[1], mask[2], mask[3], dhcpd.getFailover()) + + return dhcpd, nil +} + +// Run runs the service. +func (d *dhcpd) Run() { + if d.failover { + d.setFailover(discovery(d.iface)) + go d.detect(time.Second * 60) + } + d.server.Serve() } func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4.Handler { @@ -108,24 +125,24 @@ func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4 replyType = dhcpv4.MessageTypeAck case dhcpv4.MessageTypeRelease: pool.ReleaseIP(m.ClientHWAddr) - log.F("[dpcpd] %v released ip %v", m.ClientHWAddr, m.ClientIPAddr) + log.F("[dpcpd] %s:%v released ip %v", d.name, m.ClientHWAddr, m.ClientIPAddr) return case dhcpv4.MessageTypeDecline: pool.ReleaseIP(m.ClientHWAddr) - log.F("[dpcpd] received decline message from %v", m.ClientHWAddr) + log.F("[dpcpd] %s: received decline message from %v", d.name, m.ClientHWAddr) return default: - log.F("[dpcpd] can't handle type %v", reqType) + log.F("[dpcpd] %s: can't handle type %v", d.name, reqType) return } - if d.inFailoverMode() || bytes.Equal(d.intface.HardwareAddr, m.ClientHWAddr) { + if d.getFailover() || bytes.Equal(d.iface.HardwareAddr, m.ClientHWAddr) { return } replyIP, err := pool.LeaseIP(m.ClientHWAddr) if err != nil { - log.F("[dpcpd] can not assign IP, error %s", err) + log.F("[dpcpd] %s: can not assign IP, error %s", d.name, err) return } @@ -139,10 +156,10 @@ func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4 // RFC 2131, Section 4.3.1. Server Identifier: MUST dhcpv4.WithOption(dhcpv4.OptServerIdentifier(serverIP)), // RFC 2131, Section 4.3.1. IP lease time: MUST - dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(leaseTime)), + dhcpv4.WithOption(dhcpv4.OptIPAddressLeaseTime(d.lease)), ) if err != nil { - log.F("[dpcpd] can not create reply message, error %s", err) + log.F("[dpcpd] %s: can not create reply message, error %s", d.name, err) return } @@ -151,37 +168,37 @@ func (d *dhcpd) handleDHCP(serverIP net.IP, mask net.IPMask, pool *Pool) server4 } if _, err := conn.WriteTo(reply.ToBytes(), peer); err != nil { - log.F("[dpcpd] could not write to client %s(%s): %s", peer, reply.ClientHWAddr, err) + log.F("[dpcpd] %s: could not write to client %s(%s): %s", d.name, peer, reply.ClientHWAddr, err) return } - log.F("[dpcpd] lease %v to client %v", replyIP, reply.ClientHWAddr) + log.F("[dpcpd] %s: lease %v to client %v", d.name, replyIP, reply.ClientHWAddr) } } -func (d *dhcpd) inFailoverMode() bool { +func (d *dhcpd) getFailover() bool { d.mu.Lock() defer d.mu.Unlock() return d.failover } -func (d *dhcpd) setFailoverMode(v bool) { +func (d *dhcpd) setFailover(v bool) { d.mu.Lock() defer d.mu.Unlock() if d.failover != v { if v { - log.F("[dpcpd] existing dhcp server detected, enter failover mode") + log.F("[dpcpd] %s: dhcp server detected, enter failover mode", d.iface.Name) } else { - log.F("[dpcpd] no dhcp server detected, exit failover mode") + log.F("[dpcpd] %s: no dhcp server detected, exit failover mode", d.iface.Name) } } d.failover = v } -func (d *dhcpd) detectServer(interval time.Duration) { +func (d *dhcpd) detect(interval time.Duration) { for { - d.setFailoverMode(discovery(d.intface)) + d.setFailover(discovery(d.iface)) time.Sleep(interval) } } diff --git a/service/service.go b/service/service.go index 6d14e44..9b3f7a0 100644 --- a/service/service.go +++ b/service/service.go @@ -1,29 +1,29 @@ package service import ( + "errors" "strings" - - "github.com/nadoo/glider/pkg/log" ) -// Service is a server that can be run. -type Service interface { - Run(args ...string) -} +var creators = make(map[string]Creator) -var services = make(map[string]Service) +// Service is a server that can be run. +type Service interface{ Run() } + +// Creator is a function to create services. +type Creator func(args ...string) (Service, error) // Register is used to register a service. -func Register(name string, s Service) { - services[strings.ToLower(name)] = s +func Register(name string, c Creator) { + creators[strings.ToLower(name)] = c } -// Run runs a service. -func Run(name string, args ...string) { - svc, ok := services[strings.ToLower(name)] - if !ok { - log.F("[service] unknown service name: %s", name) - return +// New calls the registered creator to create services. +func New(s string) (Service, error) { + args := strings.Split(s, ",") + c, ok := creators[strings.ToLower(args[0])] + if ok { + return c(args[1:]...) } - svc.Run(args...) + return nil, errors.New("unknown service name: '" + args[0] + "'") }