github.com/ergo-services/ergo@v1.999.224/proto/dist/epmd.go (about)

     1  package dist
     2  
     3  import (
     4  	"context"
     5  	"encoding/binary"
     6  	"fmt"
     7  	"net"
     8  	"strconv"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/ergo-services/ergo/lib"
    14  )
    15  
    16  type registeredNode struct {
    17  	port   uint16
    18  	hidden bool
    19  	hi     uint16
    20  	lo     uint16
    21  	extra  []byte
    22  }
    23  
    24  type epmd struct {
    25  	port       uint16
    26  	nodes      map[string]registeredNode
    27  	nodesMutex sync.Mutex
    28  }
    29  
    30  func startServerEPMD(ctx context.Context, host string, port uint16) error {
    31  	lc := net.ListenConfig{}
    32  	listener, err := lc.Listen(ctx, "tcp", net.JoinHostPort(host, strconv.Itoa(int(port))))
    33  	if err != nil {
    34  		lib.Log("Can't start embedded EPMD service: %s", err)
    35  		return err
    36  	}
    37  
    38  	epmd := epmd{
    39  		port:  port,
    40  		nodes: make(map[string]registeredNode),
    41  	}
    42  	go epmd.serve(listener)
    43  	lib.Log("Started embedded EMPD service and listen port: %d", port)
    44  
    45  	return nil
    46  }
    47  
    48  func (e *epmd) serve(l net.Listener) {
    49  	for {
    50  		c, err := l.Accept()
    51  		if err != nil {
    52  			lib.Log("EPMD server stopped: %s", err.Error())
    53  			return
    54  		}
    55  		lib.Log("EPMD accepted new connection from %s", c.RemoteAddr().String())
    56  		go e.handle(c)
    57  	}
    58  }
    59  
    60  func (e *epmd) handle(c net.Conn) {
    61  	var name string
    62  	var node registeredNode
    63  	buf := make([]byte, 1024)
    64  
    65  	defer c.Close()
    66  	for {
    67  		n, err := c.Read(buf)
    68  		lib.Log("Request from EPMD client: %v", buf[:n])
    69  		if err != nil {
    70  			lib.Log("EPMD unregistering node: '%s'", name)
    71  			e.nodesMutex.Lock()
    72  			delete(e.nodes, name)
    73  			e.nodesMutex.Unlock()
    74  			return
    75  		}
    76  		if len(buf) < 6 {
    77  			lib.Log("Too short")
    78  			return
    79  		}
    80  
    81  		buf = buf[:n]
    82  		// buf[0:1] - length
    83  		if uint16(n-2) != binary.BigEndian.Uint16(buf[0:2]) {
    84  			continue
    85  		}
    86  
    87  		switch buf[2] {
    88  		case epmdAliveReq:
    89  			name, node, err = e.readAliveReq(buf[3:])
    90  			if err != nil {
    91  				// send error and close connection
    92  				e.sendAliveResp(c, 1)
    93  				return
    94  			}
    95  
    96  			// check if node with this name is already registered
    97  			e.nodesMutex.Lock()
    98  			_, exist := e.nodes[name]
    99  			e.nodesMutex.Unlock()
   100  			if exist {
   101  				// send error and close connection
   102  				e.sendAliveResp(c, 1)
   103  				return
   104  			}
   105  
   106  			// send alive response
   107  			if err := e.sendAliveResp(c, 0); err != nil {
   108  				return
   109  			}
   110  
   111  			// register new node
   112  			e.nodesMutex.Lock()
   113  			e.nodes[name] = node
   114  			e.nodesMutex.Unlock()
   115  
   116  			// enable keep alive on this connection
   117  			if tcp, ok := c.(*net.TCPConn); ok {
   118  				tcp.SetKeepAlive(true)
   119  				tcp.SetKeepAlivePeriod(15 * time.Second)
   120  				tcp.SetNoDelay(true)
   121  			}
   122  			continue
   123  
   124  		case epmdPortPleaseReq:
   125  			requestedName := string(buf[3:n])
   126  
   127  			e.nodesMutex.Lock()
   128  			node, exist := e.nodes[requestedName]
   129  			e.nodesMutex.Unlock()
   130  
   131  			if exist == false {
   132  				lib.Log("EPMD: looking for '%s'. Not found", name)
   133  				c.Write([]byte{epmdPortResp, 1})
   134  				return
   135  			}
   136  			e.sendPortPleaseResp(c, requestedName, node)
   137  			return
   138  
   139  		case epmdNamesReq:
   140  			e.sendNamesResp(c, buf[3:n])
   141  			return
   142  
   143  		default:
   144  			lib.Log("unknown EPMD request")
   145  			return
   146  		}
   147  
   148  	}
   149  }
   150  
   151  func (e *epmd) readAliveReq(req []byte) (string, registeredNode, error) {
   152  	if len(req) < 10 {
   153  		return "", registeredNode{}, fmt.Errorf("Malformed EPMD request %v", req)
   154  	}
   155  	// Name length
   156  	l := binary.BigEndian.Uint16(req[8:10])
   157  	// Name
   158  	name := string(req[10 : 10+l])
   159  	// Hidden
   160  	hidden := false
   161  	if req[2] == 72 {
   162  		hidden = true
   163  	}
   164  	// node
   165  	node := registeredNode{
   166  		port:   binary.BigEndian.Uint16(req[0:2]),
   167  		hidden: hidden,
   168  		hi:     binary.BigEndian.Uint16(req[4:6]),
   169  		lo:     binary.BigEndian.Uint16(req[6:8]),
   170  		extra:  req[10+l:],
   171  	}
   172  
   173  	return name, node, nil
   174  }
   175  
   176  func (e *epmd) sendAliveResp(c net.Conn, code int) error {
   177  	buf := make([]byte, 4)
   178  	buf[0] = epmdAliveResp
   179  	buf[1] = byte(code)
   180  
   181  	// Creation. Ergo doesn't use it. Just for Erlang nodes.
   182  	binary.BigEndian.PutUint16(buf[2:], uint16(1))
   183  	_, err := c.Write(buf)
   184  	return err
   185  }
   186  
   187  func (e *epmd) sendPortPleaseResp(c net.Conn, name string, node registeredNode) {
   188  	buf := make([]byte, 12+len(name)+2+len(node.extra))
   189  	buf[0] = epmdPortResp
   190  
   191  	// Result 0
   192  	buf[1] = 0
   193  	// Port
   194  	binary.BigEndian.PutUint16(buf[2:4], uint16(node.port))
   195  	// Hidden
   196  	if node.hidden {
   197  		buf[4] = 72
   198  	} else {
   199  		buf[4] = 77
   200  	}
   201  	// Protocol TCP
   202  	buf[5] = 0
   203  	// Highest version
   204  	binary.BigEndian.PutUint16(buf[6:8], uint16(node.hi))
   205  	// Lowest version
   206  	binary.BigEndian.PutUint16(buf[8:10], uint16(node.lo))
   207  	// Name
   208  	binary.BigEndian.PutUint16(buf[10:12], uint16(len(name)))
   209  	offset := 12 + len(name)
   210  	copy(buf[12:offset], name)
   211  	// Extra
   212  	l := len(node.extra)
   213  	copy(buf[offset:offset+l], node.extra)
   214  	// send
   215  	c.Write(buf)
   216  	return
   217  }
   218  
   219  func (e *epmd) sendNamesResp(c net.Conn, req []byte) {
   220  	var str strings.Builder
   221  	var s string
   222  	var buf [4]byte
   223  
   224  	binary.BigEndian.PutUint32(buf[0:4], uint32(e.port))
   225  	str.WriteString(string(buf[0:]))
   226  
   227  	e.nodesMutex.Lock()
   228  	for k, v := range e.nodes {
   229  		// io:format("name ~ts at port ~p~n", [NodeName, Port]).
   230  		s = fmt.Sprintf("name %s at port %d\n", k, v.port)
   231  		str.WriteString(s)
   232  	}
   233  	e.nodesMutex.Unlock()
   234  
   235  	c.Write([]byte(str.String()))
   236  	return
   237  }