summaryrefslogtreecommitdiffhomepage
path: root/pkg/dhcp/server.go
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/dhcp/server.go')
-rw-r--r--pkg/dhcp/server.go393
1 files changed, 0 insertions, 393 deletions
diff --git a/pkg/dhcp/server.go b/pkg/dhcp/server.go
deleted file mode 100644
index 6a1972860..000000000
--- a/pkg/dhcp/server.go
+++ /dev/null
@@ -1,393 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package dhcp
-
-import (
- "context"
- "fmt"
- "io"
- "log"
- "sync"
- "time"
-
- "gvisor.googlesource.com/gvisor/pkg/tcpip"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/buffer"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/network/ipv4"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/stack"
- "gvisor.googlesource.com/gvisor/pkg/tcpip/transport/udp"
- "gvisor.googlesource.com/gvisor/pkg/waiter"
-)
-
-// Server is a DHCP server.
-type Server struct {
- conn conn
- broadcast tcpip.FullAddress
- addrs []tcpip.Address // TODO: use a tcpip.AddressMask or range structure
- cfg Config
- cfgopts []option // cfg to send to client
-
- handlers []chan header
-
- mu sync.Mutex
- leases map[tcpip.LinkAddress]serverLease
-}
-
-// conn is a blocking read/write network endpoint.
-type conn interface {
- Read() (buffer.View, tcpip.FullAddress, error)
- Write([]byte, *tcpip.FullAddress) error
-}
-
-type epConn struct {
- ctx context.Context
- wq *waiter.Queue
- ep tcpip.Endpoint
- we waiter.Entry
- inCh chan struct{}
-}
-
-func newEPConn(ctx context.Context, wq *waiter.Queue, ep tcpip.Endpoint) *epConn {
- c := &epConn{
- ctx: ctx,
- wq: wq,
- ep: ep,
- }
- c.we, c.inCh = waiter.NewChannelEntry(nil)
- wq.EventRegister(&c.we, waiter.EventIn)
-
- go func() {
- <-ctx.Done()
- wq.EventUnregister(&c.we)
- }()
-
- return c
-}
-
-func (c *epConn) Read() (buffer.View, tcpip.FullAddress, error) {
- for {
- var addr tcpip.FullAddress
- v, _, err := c.ep.Read(&addr)
- if err == tcpip.ErrWouldBlock {
- select {
- case <-c.inCh:
- continue
- case <-c.ctx.Done():
- return nil, tcpip.FullAddress{}, io.EOF
- }
- }
- if err != nil {
- return v, addr, fmt.Errorf("read: %v", err)
- }
- return v, addr, nil
- }
-}
-
-func (c *epConn) Write(b []byte, addr *tcpip.FullAddress) error {
- _, resCh, err := c.ep.Write(tcpip.SlicePayload(b), tcpip.WriteOptions{To: addr})
- if err != nil && resCh == nil {
- return fmt.Errorf("write: %v", err)
- }
-
- if resCh != nil {
- select {
- case <-resCh:
- case <-c.ctx.Done():
- return fmt.Errorf("dhcp server address resolution: %v", tcpip.ErrAborted)
- }
-
- if _, _, err := c.ep.Write(tcpip.SlicePayload(b), tcpip.WriteOptions{To: addr}); err != nil {
- return fmt.Errorf("write: %v", err)
- }
- }
- return nil
-}
-
-func newEPConnServer(ctx context.Context, stack *stack.Stack, addrs []tcpip.Address, cfg Config) (*Server, error) {
- wq := new(waiter.Queue)
- ep, err := stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, wq)
- if err != nil {
- return nil, fmt.Errorf("dhcp: server endpoint: %v", err)
- }
- if err := ep.Bind(tcpip.FullAddress{Port: ServerPort}); err != nil {
- return nil, fmt.Errorf("dhcp: server bind: %v", err)
- }
- if err := ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
- return nil, fmt.Errorf("dhcp: server setsockopt: %v", err)
- }
- c := newEPConn(ctx, wq, ep)
- return NewServer(ctx, c, addrs, cfg)
-}
-
-// NewServer creates a new DHCP server and begins serving.
-// The server continues serving until ctx is done.
-func NewServer(ctx context.Context, c conn, addrs []tcpip.Address, cfg Config) (*Server, error) {
- if cfg.ServerAddress == "" {
- return nil, fmt.Errorf("dhcp: server requires explicit server address")
- }
- s := &Server{
- conn: c,
- addrs: addrs,
- cfg: cfg,
- cfgopts: cfg.encode(),
- broadcast: tcpip.FullAddress{
- Addr: "\xff\xff\xff\xff",
- Port: ClientPort,
- },
-
- handlers: make([]chan header, 8),
- leases: make(map[tcpip.LinkAddress]serverLease),
- }
-
- for i := 0; i < len(s.handlers); i++ {
- ch := make(chan header, 8)
- s.handlers[i] = ch
- go s.handler(ctx, ch)
- }
-
- go s.expirer(ctx)
- go s.reader(ctx)
- return s, nil
-}
-
-func (s *Server) expirer(ctx context.Context) {
- t := time.NewTicker(1 * time.Minute)
- defer t.Stop()
- for {
- select {
- case <-t.C:
- s.mu.Lock()
- for linkAddr, lease := range s.leases {
- if time.Since(lease.start) > s.cfg.LeaseLength {
- lease.state = leaseExpired
- s.leases[linkAddr] = lease
- }
- }
- s.mu.Unlock()
- case <-ctx.Done():
- return
- }
- }
-}
-
-// reader listens for all incoming DHCP packets and fans them out to
-// handling goroutines based on XID as session identifiers.
-func (s *Server) reader(ctx context.Context) {
- for {
- v, _, err := s.conn.Read()
- if err != nil {
- return
- }
-
- h := header(v)
- if !h.isValid() || h.op() != opRequest {
- continue
- }
- xid := h.xid()
-
- // Fan out the packet to a handler goroutine.
- //
- // Use a consistent handler for a given xid, so that
- // packets from a particular client are processed
- // in order.
- ch := s.handlers[int(xid)%len(s.handlers)]
- select {
- case <-ctx.Done():
- return
- case ch <- h:
- default:
- // drop the packet
- }
- }
-}
-
-func (s *Server) handler(ctx context.Context, ch chan header) {
- for {
- select {
- case h := <-ch:
- if h == nil {
- return
- }
- opts, err := h.options()
- if err != nil {
- continue
- }
- // TODO: Handle DHCPRELEASE and DHCPDECLINE.
- msgtype, err := opts.dhcpMsgType()
- if err != nil {
- continue
- }
- switch msgtype {
- case dhcpDISCOVER:
- s.handleDiscover(h, opts)
- case dhcpREQUEST:
- s.handleRequest(h, opts)
- }
- case <-ctx.Done():
- return
- }
- }
-}
-
-func (s *Server) handleDiscover(hreq header, opts options) {
- linkAddr := tcpip.LinkAddress(hreq.chaddr()[:6])
- xid := hreq.xid()
-
- s.mu.Lock()
- lease := s.leases[linkAddr]
- switch lease.state {
- case leaseNew:
- if len(s.leases) < len(s.addrs) {
- // Find an unused address.
- // TODO: avoid building this state on each request.
- alloced := make(map[tcpip.Address]bool)
- for _, lease := range s.leases {
- alloced[lease.addr] = true
- }
- for _, addr := range s.addrs {
- if !alloced[addr] {
- lease = serverLease{
- start: time.Now(),
- addr: addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- break
- }
- }
- } else {
- // No more addresses, take an expired address.
- for k, oldLease := range s.leases {
- if oldLease.state == leaseExpired {
- delete(s.leases, k)
- lease = serverLease{
- start: time.Now(),
- addr: lease.addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- break
- }
- }
- log.Printf("server has no more addresses")
- s.mu.Unlock()
- return
- }
- case leaseOffer, leaseAck, leaseExpired:
- lease = serverLease{
- start: time.Now(),
- addr: s.leases[linkAddr].addr,
- xid: xid,
- state: leaseOffer,
- }
- s.leases[linkAddr] = lease
- }
- s.mu.Unlock()
-
- // DHCPOFFER
- opts = options{{optDHCPMsgType, []byte{byte(dhcpOFFER)}}}
- opts = append(opts, s.cfgopts...)
- h := make(header, headerBaseSize+opts.len()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.yiaddr(), lease.addr)
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(h), &s.broadcast)
-}
-
-func (s *Server) nack(hreq header) {
- // DHCPNACK
- opts := options([]option{
- {optDHCPMsgType, []byte{byte(dhcpNAK)}},
- {optDHCPServer, []byte(s.cfg.ServerAddress)},
- })
- h := make(header, headerBaseSize+opts.len()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(h), &s.broadcast)
-}
-
-func (s *Server) handleRequest(hreq header, opts options) {
- linkAddr := tcpip.LinkAddress(hreq.chaddr()[:6])
- xid := hreq.xid()
-
- reqopts, err := hreq.options()
- if err != nil {
- s.nack(hreq)
- return
- }
- var reqcfg Config
- if err := reqcfg.decode(reqopts); err != nil {
- s.nack(hreq)
- return
- }
- if reqcfg.ServerAddress != s.cfg.ServerAddress {
- // This request is for a different DHCP server. Ignore it.
- return
- }
-
- s.mu.Lock()
- lease := s.leases[linkAddr]
- switch lease.state {
- case leaseOffer, leaseAck, leaseExpired:
- lease = serverLease{
- start: time.Now(),
- addr: s.leases[linkAddr].addr,
- xid: xid,
- state: leaseAck,
- }
- s.leases[linkAddr] = lease
- }
- s.mu.Unlock()
-
- if lease.state == leaseNew {
- // TODO: NACK or accept request
- return
- }
-
- // DHCPACK
- opts = []option{{optDHCPMsgType, []byte{byte(dhcpACK)}}}
- opts = append(opts, s.cfgopts...)
- h := make(header, headerBaseSize+opts.len()+1)
- h.init()
- h.setOp(opReply)
- copy(h.xidbytes(), hreq.xidbytes())
- copy(h.yiaddr(), lease.addr)
- copy(h.chaddr(), hreq.chaddr())
- h.setOptions(opts)
- s.conn.Write([]byte(h), &s.broadcast)
-}
-
-type leaseState int
-
-const (
- leaseNew leaseState = iota
- leaseOffer
- leaseAck
- leaseExpired
-)
-
-type serverLease struct {
- start time.Time
- addr tcpip.Address
- xid xid
- state leaseState
-}