func sessionConnectionHandler(conn net.Conn) { if err := conn.SetDeadline(time.Now().Add(messageTimeout)); err != nil { if debug { log.Println("Weird error setting deadline:", err, "on", conn.RemoteAddr()) } return } message, err := protocol.ReadMessage(conn) if err != nil { return } switch msg := message.(type) { case protocol.JoinSessionRequest: ses := findSession(string(msg.Key)) if debug { log.Println(conn.RemoteAddr(), "session lookup", ses, hex.EncodeToString(msg.Key)[:5]) } if ses == nil { protocol.WriteMessage(conn, protocol.ResponseNotFound) conn.Close() return } if !ses.AddConnection(conn) { if debug { log.Println("Failed to add", conn.RemoteAddr(), "to session", ses) } protocol.WriteMessage(conn, protocol.ResponseAlreadyConnected) conn.Close() return } if err := protocol.WriteMessage(conn, protocol.ResponseSuccess); err != nil { if debug { log.Println("Failed to send session join response to ", conn.RemoteAddr(), "for", ses) } return } if err := conn.SetDeadline(time.Time{}); err != nil { if debug { log.Println("Weird error setting deadline:", err, "on", conn.RemoteAddr()) } conn.Close() return } default: if debug { log.Println("Unexpected message from", conn.RemoteAddr(), message) } protocol.WriteMessage(conn, protocol.ResponseUnexpectedMessage) conn.Close() } }
func (c *staticClient) join() error { if err := protocol.WriteMessage(c.conn, protocol.JoinRelayRequest{}); err != nil { return err } message, err := protocol.ReadMessage(c.conn) if err != nil { return err } switch msg := message.(type) { case protocol.Response: if msg.Code != 0 { return fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message) } case protocol.RelayFull: return fmt.Errorf("relay full") default: return fmt.Errorf("protocol error: expecting response got %v", msg) } return nil }
func JoinSession(invitation protocol.SessionInvitation) (net.Conn, error) { addr := net.JoinHostPort(net.IP(invitation.Address).String(), strconv.Itoa(int(invitation.Port))) conn, err := net.Dial("tcp", addr) if err != nil { return nil, err } request := protocol.JoinSessionRequest{ Key: invitation.Key, } conn.SetDeadline(time.Now().Add(10 * time.Second)) err = protocol.WriteMessage(conn, request) if err != nil { return nil, err } message, err := protocol.ReadMessage(conn) if err != nil { return nil, err } conn.SetDeadline(time.Time{}) switch msg := message.(type) { case protocol.Response: if msg.Code != 0 { return nil, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message) } return conn, nil default: return nil, fmt.Errorf("protocol error: expecting response got %v", msg) } }
func GetInvitationFromRelay(uri *url.URL, id syncthingprotocol.DeviceID, certs []tls.Certificate) (protocol.SessionInvitation, error) { if uri.Scheme != "relay" { return protocol.SessionInvitation{}, fmt.Errorf("Unsupported relay scheme: %v", uri.Scheme) } rconn, err := dialer.Dial("tcp", uri.Host) if err != nil { return protocol.SessionInvitation{}, err } conn := tls.Client(rconn, configForCerts(certs)) conn.SetDeadline(time.Now().Add(10 * time.Second)) if err := performHandshakeAndValidation(conn, uri); err != nil { return protocol.SessionInvitation{}, err } defer conn.Close() request := protocol.ConnectRequest{ ID: id[:], } if err := protocol.WriteMessage(conn, request); err != nil { return protocol.SessionInvitation{}, err } message, err := protocol.ReadMessage(conn) if err != nil { return protocol.SessionInvitation{}, err } switch msg := message.(type) { case protocol.Response: return protocol.SessionInvitation{}, fmt.Errorf("Incorrect response code %d: %s", msg.Code, msg.Message) case protocol.SessionInvitation: l.Debugln("Received invitation", msg, "via", conn.LocalAddr()) ip := net.IP(msg.Address) if len(ip) == 0 || ip.IsUnspecified() { msg.Address = conn.RemoteAddr().(*net.TCPAddr).IP[:] } return msg, nil default: return protocol.SessionInvitation{}, fmt.Errorf("protocol error: unexpected message %v", msg) } }
func (c *ProtocolClient) Serve() { c.stop = make(chan struct{}) c.stopped = make(chan struct{}) defer close(c.stopped) if err := c.connect(); err != nil { l.Debugln("Relay connect:", err) return } l.Debugln(c, "connected", c.conn.RemoteAddr()) if err := c.join(); err != nil { c.conn.Close() l.Infoln("Relay join:", err) return } if err := c.conn.SetDeadline(time.Time{}); err != nil { l.Infoln("Relay set deadline:", err) return } l.Debugln(c, "joined", c.conn.RemoteAddr(), "via", c.conn.LocalAddr()) defer c.cleanup() c.mut.Lock() c.connected = true c.mut.Unlock() messages := make(chan interface{}) errors := make(chan error, 1) go messageReader(c.conn, messages, errors) timeout := time.NewTimer(c.timeout) for { select { case message := <-messages: timeout.Reset(c.timeout) l.Debugf("%s received message %T", c, message) switch msg := message.(type) { case protocol.Ping: if err := protocol.WriteMessage(c.conn, protocol.Pong{}); err != nil { l.Infoln("Relay write:", err) return } l.Debugln(c, "sent pong") case protocol.SessionInvitation: ip := net.IP(msg.Address) if len(ip) == 0 || ip.IsUnspecified() { msg.Address = c.conn.RemoteAddr().(*net.TCPAddr).IP[:] } c.Invitations <- msg default: l.Infoln("Relay: protocol error: unexpected message %v", msg) return } case <-c.stop: l.Debugln(c, "stopping") return case err := <-errors: l.Infoln("Relay received:", err) return case <-timeout.C: l.Debugln(c, "timed out") return } } }
func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) { conn := tls.Server(tcpConn, config) err := conn.Handshake() if err != nil { if debug { log.Println("Protocol connection TLS handshake:", conn.RemoteAddr(), err) } conn.Close() return } state := conn.ConnectionState() if (!state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol != protocol.ProtocolName) && debug { log.Println("Protocol negotiation error") } certs := state.PeerCertificates if len(certs) != 1 { if debug { log.Println("Certificate list error") } conn.Close() return } id := syncthingprotocol.NewDeviceID(certs[0].Raw) messages := make(chan interface{}) errors := make(chan error, 1) outbox := make(chan interface{}) // Read messages from the connection and send them on the messages // channel. When there is an error, send it on the error channel and // return. Applies also when the connection gets closed, so the pattern // below is to close the connection on error, then wait for the error // signal from messageReader to exit. go messageReader(conn, messages, errors) pingTicker := time.NewTicker(pingInterval) timeoutTicker := time.NewTimer(networkTimeout) joined := false for { select { case message := <-messages: timeoutTicker.Reset(networkTimeout) if debug { log.Printf("Message %T from %s", message, id) } switch msg := message.(type) { case protocol.JoinRelayRequest: if atomic.LoadInt32(&overLimit) > 0 { protocol.WriteMessage(conn, protocol.RelayFull{}) if debug { log.Println("Refusing join request from", id, "due to being over limits") } conn.Close() limitCheckTimer.Reset(time.Second) continue } outboxesMut.RLock() _, ok := outboxes[id] outboxesMut.RUnlock() if ok { protocol.WriteMessage(conn, protocol.ResponseAlreadyConnected) if debug { log.Println("Already have a peer with the same ID", id, conn.RemoteAddr()) } conn.Close() continue } outboxesMut.Lock() outboxes[id] = outbox outboxesMut.Unlock() joined = true protocol.WriteMessage(conn, protocol.ResponseSuccess) case protocol.ConnectRequest: requestedPeer := syncthingprotocol.DeviceIDFromBytes(msg.ID) outboxesMut.RLock() peerOutbox, ok := outboxes[requestedPeer] outboxesMut.RUnlock() if !ok { if debug { log.Println(id, "is looking for", requestedPeer, "which does not exist") } protocol.WriteMessage(conn, protocol.ResponseNotFound) conn.Close() continue } // requestedPeer is the server, id is the client ses := newSession(requestedPeer, id, sessionLimiter, globalLimiter) go ses.Serve() clientInvitation := ses.GetClientInvitationMessage() serverInvitation := ses.GetServerInvitationMessage() if err := protocol.WriteMessage(conn, clientInvitation); err != nil { if debug { log.Printf("Error sending invitation from %s to client: %s", id, err) } conn.Close() continue } peerOutbox <- serverInvitation if debug { log.Println("Sent invitation from", id, "to", requestedPeer) } conn.Close() case protocol.Ping: if err := protocol.WriteMessage(conn, protocol.Pong{}); err != nil { if debug { log.Println("Error writing pong:", err) } conn.Close() continue } case protocol.Pong: // Nothing default: if debug { log.Printf("Unknown message %s: %T", id, message) } protocol.WriteMessage(conn, protocol.ResponseUnexpectedMessage) conn.Close() } case err := <-errors: if debug { log.Printf("Closing connection %s: %s", id, err) } close(outbox) // Potentially closing a second time. conn.Close() if joined { // Only delete the outbox if the client is joined, as it might be // a lookup request coming from the same client. outboxesMut.Lock() delete(outboxes, id) outboxesMut.Unlock() // Also, kill all sessions related to this node, as it probably // went offline. This is for the other end to realize the client // is no longer there faster. This also helps resolve // 'already connected' errors when one of the sides is // restarting, and connecting to the other peer before the other // peer even realised that the node has gone away. dropSessions(id) } return case <-pingTicker.C: if !joined { if debug { log.Println(id, "didn't join within", pingInterval) } conn.Close() continue } if err := protocol.WriteMessage(conn, protocol.Ping{}); err != nil { if debug { log.Println(id, err) } conn.Close() } if atomic.LoadInt32(&overLimit) > 0 && !hasSessions(id) { if debug { log.Println("Dropping", id, "as it has no sessions and we are over our limits") } protocol.WriteMessage(conn, protocol.RelayFull{}) conn.Close() limitCheckTimer.Reset(time.Second) } case <-timeoutTicker.C: // We should receive a error from the reader loop, which will cause // us to quit this loop. if debug { log.Printf("%s timed out", id) } conn.Close() case msg := <-outbox: if msg == nil { conn.Close() return } if debug { log.Printf("Sending message %T to %s", msg, id) } if err := protocol.WriteMessage(conn, msg); err != nil { if debug { log.Println(id, err) } conn.Close() } } } }
func (c *staticClient) Serve() { c.stop = make(chan struct{}) c.stopped = make(chan struct{}) defer close(c.stopped) if err := c.connect(); err != nil { l.Infof("Could not connect to relay %s: %s", c.uri, err) return } l.Debugln(c, "connected", c.conn.RemoteAddr()) if err := c.join(); err != nil { c.conn.Close() l.Infof("Could not join relay %s: %s", c.uri, err) return } if err := c.conn.SetDeadline(time.Time{}); err != nil { c.conn.Close() l.Infoln("Relay set deadline:", err) return } l.Infoln("Joined relay", c.uri) c.mut.Lock() c.connected = true c.mut.Unlock() messages := make(chan interface{}) errors := make(chan error, 1) go messageReader(c.conn, messages, errors) timeout := time.NewTimer(c.messageTimeout) for { select { case message := <-messages: timeout.Reset(c.messageTimeout) l.Debugf("%s received message %T", c, message) switch msg := message.(type) { case protocol.Ping: if err := protocol.WriteMessage(c.conn, protocol.Pong{}); err != nil { l.Infoln("Relay write:", err) c.disconnect() } else { l.Debugln(c, "sent pong") } case protocol.SessionInvitation: ip := net.IP(msg.Address) if len(ip) == 0 || ip.IsUnspecified() { msg.Address = c.conn.RemoteAddr().(*net.TCPAddr).IP[:] } c.invitations <- msg case protocol.RelayFull: l.Infof("Disconnected from relay %s due to it becoming full.", c.uri) c.disconnect() default: l.Infoln("Relay: protocol error: unexpected message %v", msg) c.disconnect() } case <-c.stop: l.Debugln(c, "stopping") c.disconnect() // We always exit via this branch of the select, to make sure the // the reader routine exits. case err := <-errors: close(errors) close(messages) c.mut.Lock() if c.connected { c.conn.Close() c.connected = false l.Infof("Disconnecting from relay %s due to error: %s", c.uri, err) } if c.closeInvitationsOnFinish { close(c.invitations) c.invitations = make(chan protocol.SessionInvitation) } c.mut.Unlock() return case <-timeout.C: l.Debugln(c, "timed out") c.disconnect() } } }