Tunnel discover/announce/open/session/close
This commit is contained in:
		| @@ -718,7 +718,7 @@ func (n *network) Connect() error { | |||||||
| 	) | 	) | ||||||
|  |  | ||||||
| 	// dial into ControlChannel to send route adverts | 	// dial into ControlChannel to send route adverts | ||||||
| 	ctrlClient, err := n.Tunnel.Dial(ControlChannel) | 	ctrlClient, err := n.Tunnel.Dial(ControlChannel, tunnel.DialMulticast()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| @@ -732,7 +732,7 @@ func (n *network) Connect() error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// dial into NetworkChannel to send network messages | 	// dial into NetworkChannel to send network messages | ||||||
| 	netClient, err := n.Tunnel.Dial(NetworkChannel) | 	netClient, err := n.Tunnel.Dial(NetworkChannel, tunnel.DialMulticast()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -87,6 +87,12 @@ func (t *tun) getSession(channel, session string) (*session, bool) { | |||||||
| 	return s, ok | 	return s, ok | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (t *tun) delSession(channel, session string) { | ||||||
|  | 	t.Lock() | ||||||
|  | 	delete(t.sessions, channel+session) | ||||||
|  | 	t.Unlock() | ||||||
|  | } | ||||||
|  |  | ||||||
| // newSession creates a new session and saves it | // newSession creates a new session and saves it | ||||||
| func (t *tun) newSession(channel, sessionId string) (*session, bool) { | func (t *tun) newSession(channel, sessionId string) (*session, bool) { | ||||||
| 	// new session | 	// new session | ||||||
| @@ -150,7 +156,9 @@ func (t *tun) monitor() { | |||||||
| 					log.Debugf("Tunnel failed to setup node link to %s: %v", node, err) | 					log.Debugf("Tunnel failed to setup node link to %s: %v", node, err) | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  | 				// set the link id to the node | ||||||
|  | 				// TODO: hash it | ||||||
|  | 				link.id = node | ||||||
| 				// save the link | 				// save the link | ||||||
| 				t.Lock() | 				t.Lock() | ||||||
| 				t.links[node] = link | 				t.links[node] = link | ||||||
| @@ -169,12 +177,15 @@ func (t *tun) process() { | |||||||
| 		case msg := <-t.send: | 		case msg := <-t.send: | ||||||
| 			newMsg := &transport.Message{ | 			newMsg := &transport.Message{ | ||||||
| 				Header: make(map[string]string), | 				Header: make(map[string]string), | ||||||
| 				Body:   msg.data.Body, |  | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// set the data | ||||||
|  | 			if msg.data != nil { | ||||||
| 				for k, v := range msg.data.Header { | 				for k, v := range msg.data.Header { | ||||||
| 					newMsg.Header[k] = v | 					newMsg.Header[k] = v | ||||||
| 				} | 				} | ||||||
|  | 				newMsg.Body = msg.data.Body | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// set message head | 			// set message head | ||||||
| 			newMsg.Header["Micro-Tunnel"] = msg.typ | 			newMsg.Header["Micro-Tunnel"] = msg.typ | ||||||
| @@ -195,7 +206,7 @@ func (t *tun) process() { | |||||||
| 			t.Lock() | 			t.Lock() | ||||||
|  |  | ||||||
| 			if len(t.links) == 0 { | 			if len(t.links) == 0 { | ||||||
| 				log.Debugf("No links to send to") | 				log.Debugf("No links to send message type: %s channel: %s", msg.typ, msg.channel) | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			var sent bool | 			var sent bool | ||||||
| @@ -232,25 +243,55 @@ func (t *tun) process() { | |||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				// check the multicast mappings | ||||||
|  | 				if msg.multicast { | ||||||
|  | 					link.RLock() | ||||||
|  | 					_, ok := link.channels[msg.channel] | ||||||
|  | 					link.RUnlock() | ||||||
|  | 					// channel mapping not found in link | ||||||
|  | 					if !ok { | ||||||
|  | 						continue | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				// send the message via the current link | 				// send the message via the current link | ||||||
| 				log.Debugf("Sending %+v to %s", newMsg, node) | 				log.Debugf("Sending %+v to %s", newMsg, node) | ||||||
|  |  | ||||||
| 				if errr := link.Send(newMsg); errr != nil { | 				if errr := link.Send(newMsg); errr != nil { | ||||||
| 					log.Debugf("Tunnel error sending %+v to %s: %v", newMsg, node, errr) | 					log.Debugf("Tunnel error sending %+v to %s: %v", newMsg, node, errr) | ||||||
| 					err = errors.New(errr.Error()) | 					err = errors.New(errr.Error()) | ||||||
|  | 					// kill the link | ||||||
|  | 					link.Close() | ||||||
|  | 					// delete the link | ||||||
| 					delete(t.links, node) | 					delete(t.links, node) | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				// is sent | 				// is sent | ||||||
| 				sent = true | 				sent = true | ||||||
|  |  | ||||||
|  | 				// keep sending broadcast messages | ||||||
|  | 				if msg.broadcast || msg.multicast { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// break on unicast | ||||||
|  | 				break | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			t.Unlock() | 			t.Unlock() | ||||||
|  |  | ||||||
|  | 			// set the error if not sent | ||||||
| 			var gerr error | 			var gerr error | ||||||
| 			if !sent { | 			if !sent { | ||||||
| 				gerr = err | 				gerr = err | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// skip if its not been set | ||||||
|  | 			if msg.errChan == nil { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
| 			// return error non blocking | 			// return error non blocking | ||||||
| 			select { | 			select { | ||||||
| 			case msg.errChan <- gerr: | 			case msg.errChan <- gerr: | ||||||
| @@ -262,14 +303,25 @@ func (t *tun) process() { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (t *tun) delLink(id string) { | ||||||
|  | 	t.Lock() | ||||||
|  | 	defer t.Unlock() | ||||||
|  | 	// get the link | ||||||
|  | 	link, ok := t.links[id] | ||||||
|  | 	if !ok { | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	// close and delete | ||||||
|  | 	link.Close() | ||||||
|  | 	delete(t.links, id) | ||||||
|  | } | ||||||
|  |  | ||||||
| // process incoming messages | // process incoming messages | ||||||
| func (t *tun) listen(link *link) { | func (t *tun) listen(link *link) { | ||||||
| 	// remove the link on exit | 	// remove the link on exit | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		log.Debugf("Tunnel deleting connection from %s", link.Remote()) | 		log.Debugf("Tunnel deleting connection from %s", link.Remote()) | ||||||
| 		t.Lock() | 		t.delLink(link.Remote()) | ||||||
| 		delete(t.links, link.Remote()) |  | ||||||
| 		t.Unlock() |  | ||||||
| 	}() | 	}() | ||||||
|  |  | ||||||
| 	// let us know if its a loopback | 	// let us know if its a loopback | ||||||
| @@ -301,6 +353,13 @@ func (t *tun) listen(link *link) { | |||||||
| 		// the session id | 		// the session id | ||||||
| 		sessionId := msg.Header["Micro-Tunnel-Session"] | 		sessionId := msg.Header["Micro-Tunnel-Session"] | ||||||
|  |  | ||||||
|  | 		// if its not connected throw away the link | ||||||
|  | 		// the first message we process needs to be connect | ||||||
|  | 		if !link.connected && mtype != "connect" { | ||||||
|  | 			log.Debugf("Tunnel link %s not connected", link.id) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		switch mtype { | 		switch mtype { | ||||||
| 		case "connect": | 		case "connect": | ||||||
| 			log.Debugf("Tunnel link %s received connect message", link.Remote()) | 			log.Debugf("Tunnel link %s received connect message", link.Remote()) | ||||||
| @@ -311,6 +370,8 @@ func (t *tun) listen(link *link) { | |||||||
| 				loopback = true | 				loopback = true | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
|  | 			// set to remote node | ||||||
|  | 			link.id = id | ||||||
| 			// set as connected | 			// set as connected | ||||||
| 			link.connected = true | 			link.connected = true | ||||||
|  |  | ||||||
| @@ -322,10 +383,31 @@ func (t *tun) listen(link *link) { | |||||||
| 			// nothing more to do | 			// nothing more to do | ||||||
| 			continue | 			continue | ||||||
| 		case "close": | 		case "close": | ||||||
| 			log.Debugf("Tunnel link %s closing connection", link.Remote()) |  | ||||||
| 			// TODO: handle the close message | 			// TODO: handle the close message | ||||||
| 			// maybe report io.EOF or kill the link | 			// maybe report io.EOF or kill the link | ||||||
|  |  | ||||||
|  | 			// close the link entirely | ||||||
|  | 			if len(channel) == 0 { | ||||||
|  | 				log.Debugf("Tunnel link %s received close message", link.Remote()) | ||||||
| 				return | 				return | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// the entire listener was closed so remove it from the mapping | ||||||
|  | 			if sessionId == "listener" { | ||||||
|  | 				link.Lock() | ||||||
|  | 				delete(link.channels, channel) | ||||||
|  | 				link.Unlock() | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// try get the dialing socket | ||||||
|  | 			s, exists := t.getSession(channel, sessionId) | ||||||
|  | 			if exists { | ||||||
|  | 				// close and continue | ||||||
|  | 				s.Close() | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// otherwise its a session mapping of sorts | ||||||
| 		case "keepalive": | 		case "keepalive": | ||||||
| 			log.Debugf("Tunnel link %s received keepalive", link.Remote()) | 			log.Debugf("Tunnel link %s received keepalive", link.Remote()) | ||||||
| 			t.Lock() | 			t.Lock() | ||||||
| @@ -333,18 +415,62 @@ func (t *tun) listen(link *link) { | |||||||
| 			link.lastKeepAlive = time.Now() | 			link.lastKeepAlive = time.Now() | ||||||
| 			t.Unlock() | 			t.Unlock() | ||||||
| 			continue | 			continue | ||||||
|  | 		// a new connection dialled outbound | ||||||
|  | 		case "open": | ||||||
|  | 			// we just let it pass through to be processed | ||||||
|  | 		// an accept returned by the listener | ||||||
|  | 		case "accept": | ||||||
|  |  | ||||||
|  | 		// a continued session | ||||||
| 		case "session": | 		case "session": | ||||||
| 			// process message | 			// process message | ||||||
| 			log.Debugf("Received %+v from %s", msg, link.Remote()) | 			log.Debugf("Received %+v from %s", msg, link.Remote()) | ||||||
| 		default: | 		// an announcement of a channel listener | ||||||
| 			// blackhole it | 		case "announce": | ||||||
|  | 			// update mapping in the link | ||||||
|  | 			link.Lock() | ||||||
|  | 			link.channels[channel] = time.Now() | ||||||
|  | 			link.Unlock() | ||||||
|  |  | ||||||
|  | 			// get the session that asked for the discovery | ||||||
|  | 			s, exists := t.getSession(channel, sessionId) | ||||||
|  | 			if exists { | ||||||
|  | 				// don't bother it's already discovered | ||||||
|  | 				if s.discovered { | ||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 		// if its not connected throw away the link | 				// send the announce back to the caller | ||||||
| 		if !link.connected { | 				s.recv <- &message{ | ||||||
| 			log.Debugf("Tunnel link %s not connected", link.id) | 					typ:     "announce", | ||||||
| 			return | 					tunnel:  id, | ||||||
|  | 					channel: channel, | ||||||
|  | 					session: sessionId, | ||||||
|  | 					link:    link.id, | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		case "discover": | ||||||
|  | 			// looking for existing mapping | ||||||
|  | 			_, exists := t.getSession(channel, "listener") | ||||||
|  | 			if exists { | ||||||
|  | 				log.Debugf("Tunnel sending announce for discovery of channel %s", channel) | ||||||
|  | 				// send back the announcement | ||||||
|  | 				link.Send(&transport.Message{ | ||||||
|  | 					Header: map[string]string{ | ||||||
|  | 						"Micro-Tunnel":         "announce", | ||||||
|  | 						"Micro-Tunnel-Id":      t.id, | ||||||
|  | 						"Micro-Tunnel-Channel": channel, | ||||||
|  | 						"Micro-Tunnel-Session": sessionId, | ||||||
|  | 						"Micro-Tunnel-Link":    link.id, | ||||||
|  | 						"Micro-Tunnel-Token":   t.token, | ||||||
|  | 					}, | ||||||
|  | 				}) | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		default: | ||||||
|  | 			// blackhole it | ||||||
|  | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// strip tunnel message header | 		// strip tunnel message header | ||||||
| @@ -368,8 +494,15 @@ func (t *tun) listen(link *link) { | |||||||
| 		// If its a loopback connection then we've enabled link direction | 		// If its a loopback connection then we've enabled link direction | ||||||
| 		// listening side is used for listening, the dialling side for dialling | 		// listening side is used for listening, the dialling side for dialling | ||||||
| 		switch { | 		switch { | ||||||
| 		case loopback: | 		case loopback, mtype == "open": | ||||||
| 			s, exists = t.getSession(channel, "listener") | 			s, exists = t.getSession(channel, "listener") | ||||||
|  | 		// only return accept to the session | ||||||
|  | 		case mtype == "accept": | ||||||
|  | 			log.Debugf("Received accept message for %s %s", channel, sessionId) | ||||||
|  | 			s, exists = t.getSession(channel, sessionId) | ||||||
|  | 			if exists && s.accepted { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
| 		default: | 		default: | ||||||
| 			// get the session based on the tunnel id and session | 			// get the session based on the tunnel id and session | ||||||
| 			// this could be something we dialed in which case | 			// this could be something we dialed in which case | ||||||
| @@ -383,7 +516,7 @@ func (t *tun) listen(link *link) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// bail if no session has been found | 		// bail if no session or listener has been found | ||||||
| 		if !exists { | 		if !exists { | ||||||
| 			log.Debugf("Tunnel skipping no session exists") | 			log.Debugf("Tunnel skipping no session exists") | ||||||
| 			// drop it, we don't care about | 			// drop it, we don't care about | ||||||
| @@ -391,8 +524,6 @@ func (t *tun) listen(link *link) { | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		log.Debugf("Tunnel using session %s %s", s.channel, s.session) |  | ||||||
|  |  | ||||||
| 		// is the session closed? | 		// is the session closed? | ||||||
| 		select { | 		select { | ||||||
| 		case <-s.closed: | 		case <-s.closed: | ||||||
| @@ -403,6 +534,8 @@ func (t *tun) listen(link *link) { | |||||||
| 			// process | 			// process | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		log.Debugf("Tunnel using channel %s session %s", s.channel, s.session) | ||||||
|  |  | ||||||
| 		// is the session new? | 		// is the session new? | ||||||
| 		select { | 		select { | ||||||
| 		// if its new the session is actually blocked waiting | 		// if its new the session is actually blocked waiting | ||||||
| @@ -462,9 +595,7 @@ func (t *tun) keepalive(link *link) { | |||||||
| 				}, | 				}, | ||||||
| 			}); err != nil { | 			}); err != nil { | ||||||
| 				log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err) | 				log.Debugf("Error sending keepalive to link %v: %v", link.Remote(), err) | ||||||
| 				t.Lock() | 				t.delLink(link.Remote()) | ||||||
| 				delete(t.links, link.Remote()) |  | ||||||
| 				t.Unlock() |  | ||||||
| 				return | 				return | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| @@ -482,6 +613,7 @@ func (t *tun) setupLink(node string) (*link, error) { | |||||||
| 	} | 	} | ||||||
| 	log.Debugf("Tunnel connected to %s", node) | 	log.Debugf("Tunnel connected to %s", node) | ||||||
|  |  | ||||||
|  | 	// send the first connect message | ||||||
| 	if err := c.Send(&transport.Message{ | 	if err := c.Send(&transport.Message{ | ||||||
| 		Header: map[string]string{ | 		Header: map[string]string{ | ||||||
| 			"Micro-Tunnel":       "connect", | 			"Micro-Tunnel":       "connect", | ||||||
| @@ -494,9 +626,11 @@ func (t *tun) setupLink(node string) (*link, error) { | |||||||
|  |  | ||||||
| 	// create a new link | 	// create a new link | ||||||
| 	link := newLink(c) | 	link := newLink(c) | ||||||
| 	link.connected = true | 	// set link id to remote side | ||||||
|  | 	link.id = c.Remote() | ||||||
| 	// we made the outbound connection | 	// we made the outbound connection | ||||||
| 	// and sent the connect message | 	// and sent the connect message | ||||||
|  | 	link.connected = true | ||||||
|  |  | ||||||
| 	// process incoming messages | 	// process incoming messages | ||||||
| 	go t.listen(link) | 	go t.listen(link) | ||||||
| @@ -554,7 +688,7 @@ func (t *tun) connect() error { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// save the link | 		// save the link | ||||||
| 		t.links[node] = link | 		t.links[link.Remote()] = link | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// process outbound messages to be sent | 	// process outbound messages to be sent | ||||||
| @@ -628,6 +762,8 @@ func (t *tun) Close() error { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	log.Debug("Tunnel closing") | ||||||
|  |  | ||||||
| 	select { | 	select { | ||||||
| 	case <-t.closed: | 	case <-t.closed: | ||||||
| 		return nil | 		return nil | ||||||
| @@ -651,7 +787,7 @@ func (t *tun) Close() error { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Dial an address | // Dial an address | ||||||
| func (t *tun) Dial(channel string) (Session, error) { | func (t *tun) Dial(channel string, opts ...DialOption) (Session, error) { | ||||||
| 	log.Debugf("Tunnel dialing %s", channel) | 	log.Debugf("Tunnel dialing %s", channel) | ||||||
| 	c, ok := t.newSession(channel, t.newSessionId()) | 	c, ok := t.newSession(channel, t.newSessionId()) | ||||||
| 	if !ok { | 	if !ok { | ||||||
| @@ -664,18 +800,87 @@ func (t *tun) Dial(channel string) (Session, error) { | |||||||
| 	// outbound session | 	// outbound session | ||||||
| 	c.outbound = true | 	c.outbound = true | ||||||
|  |  | ||||||
|  | 	// get opts | ||||||
|  | 	options := DialOptions{ | ||||||
|  | 		Timeout: DefaultDialTimeout, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, o := range opts { | ||||||
|  | 		o(&options) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// set the multicast option | ||||||
|  | 	c.multicast = options.Multicast | ||||||
|  | 	// set the dial timeout | ||||||
|  | 	c.timeout = options.Timeout | ||||||
|  |  | ||||||
|  | 	t.RLock() | ||||||
|  | 	for _, link := range t.links { | ||||||
|  | 		link.RLock() | ||||||
|  | 		_, ok := link.channels[channel] | ||||||
|  | 		link.RUnlock() | ||||||
|  |  | ||||||
|  | 		// we have at least one channel mapping | ||||||
|  | 		if ok { | ||||||
|  | 			c.discovered = true | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	t.RUnlock() | ||||||
|  |  | ||||||
|  | 	// shit fuck | ||||||
|  | 	if !c.discovered { | ||||||
|  | 		t.send <- &message{ | ||||||
|  | 			typ:       "discover", | ||||||
|  | 			tunnel:    t.id, | ||||||
|  | 			channel:   channel, | ||||||
|  | 			session:   c.session, | ||||||
|  | 			broadcast: true, | ||||||
|  | 			outbound:  true, | ||||||
|  | 			errChan:   c.errChan, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case err := <-c.errChan: | ||||||
|  | 			if err != nil { | ||||||
|  | 				return nil, err | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// wait for announce | ||||||
|  | 		select { | ||||||
|  | 		case msg := <-c.recv: | ||||||
|  | 			if msg.typ != "announce" { | ||||||
|  | 				return nil, errors.New("failed to discover channel") | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// try to open the session | ||||||
|  | 	err := c.Open() | ||||||
|  | 	if err != nil { | ||||||
|  | 		// delete the session | ||||||
|  | 		t.delSession(c.channel, c.session) | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return c, nil | 	return c, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Accept a connection on the address | // Accept a connection on the address | ||||||
| func (t *tun) Listen(channel string) (Listener, error) { | func (t *tun) Listen(channel string) (Listener, error) { | ||||||
| 	log.Debugf("Tunnel listening on %s", channel) | 	log.Debugf("Tunnel listening on %s", channel) | ||||||
|  |  | ||||||
| 	// create a new session by hashing the address | 	// create a new session by hashing the address | ||||||
| 	c, ok := t.newSession(channel, "listener") | 	c, ok := t.newSession(channel, "listener") | ||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, errors.New("already listening on " + channel) | 		return nil, errors.New("already listening on " + channel) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	delFunc := func() { | ||||||
|  | 		t.delSession(channel, "listener") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	// set remote. it will be replaced by the first message received | 	// set remote. it will be replaced by the first message received | ||||||
| 	c.remote = "remote" | 	c.remote = "remote" | ||||||
| 	// set local | 	// set local | ||||||
| @@ -691,6 +896,8 @@ func (t *tun) Listen(channel string) (Listener, error) { | |||||||
| 		tunClosed: t.closed, | 		tunClosed: t.closed, | ||||||
| 		// the listener session | 		// the listener session | ||||||
| 		session: c, | 		session: c, | ||||||
|  | 		// delete session | ||||||
|  | 		delFunc: delFunc, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// this kicks off the internal message processor | 	// this kicks off the internal message processor | ||||||
| @@ -699,6 +906,9 @@ func (t *tun) Listen(channel string) (Listener, error) { | |||||||
| 	// to the existign sessions | 	// to the existign sessions | ||||||
| 	go tl.process() | 	go tl.process() | ||||||
|  |  | ||||||
|  | 	// announces the listener channel to others | ||||||
|  | 	go tl.announce() | ||||||
|  |  | ||||||
| 	// return the listener | 	// return the listener | ||||||
| 	return tl, nil | 	return tl, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,9 +9,10 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| type link struct { | type link struct { | ||||||
|  | 	transport.Socket | ||||||
|  |  | ||||||
| 	sync.RWMutex | 	sync.RWMutex | ||||||
|  |  | ||||||
| 	transport.Socket |  | ||||||
| 	// unique id of this link e.g uuid | 	// unique id of this link e.g uuid | ||||||
| 	// which we define for ourselves | 	// which we define for ourselves | ||||||
| 	id string | 	id string | ||||||
| @@ -27,11 +28,67 @@ type link struct { | |||||||
| 	// the last time we received a keepalive | 	// the last time we received a keepalive | ||||||
| 	// on this link from the remote side | 	// on this link from the remote side | ||||||
| 	lastKeepAlive time.Time | 	lastKeepAlive time.Time | ||||||
|  | 	// channels keeps a mapping of channels and last seen | ||||||
|  | 	channels map[string]time.Time | ||||||
|  | 	// stop the link | ||||||
|  | 	closed chan bool | ||||||
| } | } | ||||||
|  |  | ||||||
| func newLink(s transport.Socket) *link { | func newLink(s transport.Socket) *link { | ||||||
| 	return &link{ | 	l := &link{ | ||||||
| 		Socket:   s, | 		Socket:   s, | ||||||
| 		id:       uuid.New().String(), | 		id:       uuid.New().String(), | ||||||
|  | 		channels: make(map[string]time.Time), | ||||||
|  | 		closed:   make(chan bool), | ||||||
|  | 	} | ||||||
|  | 	go l.run() | ||||||
|  | 	return l | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *link) run() { | ||||||
|  | 	t := time.NewTicker(time.Minute) | ||||||
|  | 	defer t.Stop() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-l.closed: | ||||||
|  | 			return | ||||||
|  | 		case <-t.C: | ||||||
|  | 			// drop any channel mappings older than 2 minutes | ||||||
|  | 			var kill []string | ||||||
|  | 			killTime := time.Minute * 2 | ||||||
|  |  | ||||||
|  | 			l.RLock() | ||||||
|  | 			for ch, t := range l.channels { | ||||||
|  | 				if d := time.Since(t); d > killTime { | ||||||
|  | 					kill = append(kill, ch) | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			l.RUnlock() | ||||||
|  |  | ||||||
|  | 			// if nothing to kill don't both with a wasted lock | ||||||
|  | 			if len(kill) == 0 { | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// kill the channels! | ||||||
|  | 			l.Lock() | ||||||
|  | 			for _, ch := range kill { | ||||||
|  | 				delete(l.channels, ch) | ||||||
|  | 			} | ||||||
|  | 			l.Unlock() | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (l *link) Close() { | ||||||
|  | 	l.Lock() | ||||||
|  | 	defer l.Unlock() | ||||||
|  |  | ||||||
|  | 	select { | ||||||
|  | 	case <-l.closed: | ||||||
|  | 		return | ||||||
|  | 	default: | ||||||
|  | 		close(l.closed) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package tunnel | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/util/log" | 	"github.com/micro/go-micro/util/log" | ||||||
| ) | ) | ||||||
| @@ -17,22 +18,77 @@ type tunListener struct { | |||||||
| 	tunClosed chan bool | 	tunClosed chan bool | ||||||
| 	// the listener session | 	// the listener session | ||||||
| 	session *session | 	session *session | ||||||
|  | 	// del func to kill listener | ||||||
|  | 	delFunc func() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // periodically announce self | ||||||
|  | func (t *tunListener) announce() { | ||||||
|  | 	tick := time.NewTicker(time.Minute) | ||||||
|  | 	defer tick.Stop() | ||||||
|  |  | ||||||
|  | 	announce := func() { | ||||||
|  | 		msg := &message{ | ||||||
|  | 			typ:       "announce", | ||||||
|  | 			tunnel:    t.session.tunnel, | ||||||
|  | 			channel:   t.session.channel, | ||||||
|  | 			session:   t.session.session, | ||||||
|  | 			outbound:  t.session.outbound, | ||||||
|  | 			loopback:  t.session.loopback, | ||||||
|  | 			multicast: t.session.multicast, | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		select { | ||||||
|  | 		case t.session.send <- msg: | ||||||
|  | 		case <-t.session.closed: | ||||||
|  | 			return | ||||||
|  | 		case <-t.closed: | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// first announcement | ||||||
|  | 	announce() | ||||||
|  |  | ||||||
|  | 	for { | ||||||
|  | 		select { | ||||||
|  | 		case <-tick.C: | ||||||
|  | 			announce() | ||||||
|  | 		case <-t.closed: | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (t *tunListener) process() { | func (t *tunListener) process() { | ||||||
| 	// our connection map for session | 	// our connection map for session | ||||||
| 	conns := make(map[string]*session) | 	conns := make(map[string]*session) | ||||||
|  |  | ||||||
|  | 	defer func() { | ||||||
|  | 		// close the sessions | ||||||
|  | 		for _, conn := range conns { | ||||||
|  | 			conn.Close() | ||||||
|  | 		} | ||||||
|  | 	}() | ||||||
|  |  | ||||||
| 	for { | 	for { | ||||||
| 		select { | 		select { | ||||||
| 		case <-t.closed: | 		case <-t.closed: | ||||||
| 			return | 			return | ||||||
|  | 		case <-t.tunClosed: | ||||||
|  | 			t.Close() | ||||||
|  | 			return | ||||||
| 		// receive a new message | 		// receive a new message | ||||||
| 		case m := <-t.session.recv: | 		case m := <-t.session.recv: | ||||||
| 			// get a session | 			// get a session | ||||||
| 			sess, ok := conns[m.session] | 			sess, ok := conns[m.session] | ||||||
| 			log.Debugf("Tunnel listener received channel %s session %s exists: %t", m.channel, m.session, ok) | 			log.Debugf("Tunnel listener received channel %s session %s exists: %t", m.channel, m.session, ok) | ||||||
| 			if !ok { | 			if !ok { | ||||||
|  | 				// only create new sessions on open message | ||||||
|  | 				if m.typ != "open" { | ||||||
|  | 					continue | ||||||
|  | 				} | ||||||
|  |  | ||||||
| 				// create a new session session | 				// create a new session session | ||||||
| 				sess = &session{ | 				sess = &session{ | ||||||
| 					// the id of the remote side | 					// the id of the remote side | ||||||
| @@ -45,6 +101,8 @@ func (t *tunListener) process() { | |||||||
| 					loopback: m.loopback, | 					loopback: m.loopback, | ||||||
| 					// the link the message was received on | 					// the link the message was received on | ||||||
| 					link: m.link, | 					link: m.link, | ||||||
|  | 					// set multicast | ||||||
|  | 					multicast: m.multicast, | ||||||
| 					// close chan | 					// close chan | ||||||
| 					closed: make(chan bool), | 					closed: make(chan bool), | ||||||
| 					// recv called by the acceptor | 					// recv called by the acceptor | ||||||
| @@ -60,12 +118,39 @@ func (t *tunListener) process() { | |||||||
| 				// save the session | 				// save the session | ||||||
| 				conns[m.session] = sess | 				conns[m.session] = sess | ||||||
|  |  | ||||||
| 				// send to accept chan |  | ||||||
| 				select { | 				select { | ||||||
| 				case <-t.closed: | 				case <-t.closed: | ||||||
| 					return | 					return | ||||||
|  | 				// send to accept chan | ||||||
| 				case t.accept <- sess: | 				case t.accept <- sess: | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
|  | 				// continue | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// an existing session was found | ||||||
|  |  | ||||||
|  | 			// received a close message | ||||||
|  | 			switch m.typ { | ||||||
|  | 			case "close": | ||||||
|  | 				select { | ||||||
|  | 				case <-sess.closed: | ||||||
|  | 					// no op | ||||||
|  | 					delete(conns, m.session) | ||||||
|  | 				default: | ||||||
|  | 					// close and delete session | ||||||
|  | 					close(sess.closed) | ||||||
|  | 					delete(conns, m.session) | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				// continue | ||||||
|  | 				continue | ||||||
|  | 			case "session": | ||||||
|  | 				// operate on this | ||||||
|  | 			default: | ||||||
|  | 				// non operational type | ||||||
|  | 				continue | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			// send this to the accept chan | 			// send this to the accept chan | ||||||
| @@ -89,6 +174,9 @@ func (t *tunListener) Close() error { | |||||||
| 	case <-t.closed: | 	case <-t.closed: | ||||||
| 		return nil | 		return nil | ||||||
| 	default: | 	default: | ||||||
|  | 		// close and delete | ||||||
|  | 		t.delFunc() | ||||||
|  | 		t.session.Close() | ||||||
| 		close(t.closed) | 		close(t.closed) | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| @@ -102,13 +190,17 @@ func (t *tunListener) Accept() (Session, error) { | |||||||
| 		return nil, io.EOF | 		return nil, io.EOF | ||||||
| 	case <-t.tunClosed: | 	case <-t.tunClosed: | ||||||
| 		// close the listener when the tunnel closes | 		// close the listener when the tunnel closes | ||||||
| 		t.Close() |  | ||||||
| 		return nil, io.EOF | 		return nil, io.EOF | ||||||
| 	// wait for a new connection | 	// wait for a new connection | ||||||
| 	case c, ok := <-t.accept: | 	case c, ok := <-t.accept: | ||||||
|  | 		// check if the accept chan is closed | ||||||
| 		if !ok { | 		if !ok { | ||||||
| 			return nil, io.EOF | 			return nil, io.EOF | ||||||
| 		} | 		} | ||||||
|  | 		// send back the accept | ||||||
|  | 		if err := c.Accept(); err != nil { | ||||||
|  | 			return nil, err | ||||||
|  | 		} | ||||||
| 		return c, nil | 		return c, nil | ||||||
| 	} | 	} | ||||||
| 	return nil, nil | 	return nil, nil | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| package tunnel | package tunnel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| 	"github.com/micro/go-micro/transport" | 	"github.com/micro/go-micro/transport" | ||||||
| 	"github.com/micro/go-micro/transport/quic" | 	"github.com/micro/go-micro/transport/quic" | ||||||
| @@ -29,6 +31,15 @@ type Options struct { | |||||||
| 	Transport transport.Transport | 	Transport transport.Transport | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type DialOption func(*DialOptions) | ||||||
|  |  | ||||||
|  | type DialOptions struct { | ||||||
|  | 	// specify a multicast connection | ||||||
|  | 	Multicast bool | ||||||
|  | 	// the dial timeout | ||||||
|  | 	Timeout time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
| // The tunnel id | // The tunnel id | ||||||
| func Id(id string) Option { | func Id(id string) Option { | ||||||
| 	return func(o *Options) { | 	return func(o *Options) { | ||||||
| @@ -73,3 +84,18 @@ func DefaultOptions() Options { | |||||||
| 		Transport: quic.NewTransport(), | 		Transport: quic.NewTransport(), | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Dial options | ||||||
|  |  | ||||||
|  | // Dial multicast sets the multicast option to send only to those mapped | ||||||
|  | func DialMulticast() DialOption { | ||||||
|  | 	return func(o *DialOptions) { | ||||||
|  | 		o.Multicast = true | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func DialTimeout(t time.Duration) DialOption { | ||||||
|  | 	return func(o *DialOptions) { | ||||||
|  | 		o.Timeout = t | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package tunnel | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"io" | 	"io" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/transport" | 	"github.com/micro/go-micro/transport" | ||||||
| 	"github.com/micro/go-micro/util/log" | 	"github.com/micro/go-micro/util/log" | ||||||
| @@ -28,10 +29,20 @@ type session struct { | |||||||
| 	recv chan *message | 	recv chan *message | ||||||
| 	// wait until we have a connection | 	// wait until we have a connection | ||||||
| 	wait chan bool | 	wait chan bool | ||||||
|  | 	// if the discovery worked | ||||||
|  | 	discovered bool | ||||||
|  | 	// if the session was accepted | ||||||
|  | 	accepted bool | ||||||
| 	// outbound marks the session as outbound dialled connection | 	// outbound marks the session as outbound dialled connection | ||||||
| 	outbound bool | 	outbound bool | ||||||
| 	// lookback marks the session as a loopback on the inbound | 	// lookback marks the session as a loopback on the inbound | ||||||
| 	loopback bool | 	loopback bool | ||||||
|  | 	// if the session is multicast | ||||||
|  | 	multicast bool | ||||||
|  | 	// if the session is broadcast | ||||||
|  | 	broadcast bool | ||||||
|  | 	// the timeout | ||||||
|  | 	timeout time.Duration | ||||||
| 	// the link on which this message was received | 	// the link on which this message was received | ||||||
| 	link string | 	link string | ||||||
| 	// the error response | 	// the error response | ||||||
| @@ -52,6 +63,10 @@ type message struct { | |||||||
| 	outbound bool | 	outbound bool | ||||||
| 	// loopback marks the message intended for loopback | 	// loopback marks the message intended for loopback | ||||||
| 	loopback bool | 	loopback bool | ||||||
|  | 	// whether to send as multicast | ||||||
|  | 	multicast bool | ||||||
|  | 	// broadcast sets the broadcast type | ||||||
|  | 	broadcast bool | ||||||
| 	// the link to send the message on | 	// the link to send the message on | ||||||
| 	link string | 	link string | ||||||
| 	// transport data | 	// transport data | ||||||
| @@ -76,10 +91,97 @@ func (s *session) Channel() string { | |||||||
| 	return s.channel | 	return s.channel | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Open will fire the open message for the session | ||||||
|  | func (s *session) Open() error { | ||||||
|  | 	msg := &message{ | ||||||
|  | 		typ:       "open", | ||||||
|  | 		tunnel:    s.tunnel, | ||||||
|  | 		channel:   s.channel, | ||||||
|  | 		session:   s.session, | ||||||
|  | 		outbound:  s.outbound, | ||||||
|  | 		loopback:  s.loopback, | ||||||
|  | 		multicast: s.multicast, | ||||||
|  | 		link:      s.link, | ||||||
|  | 		errChan:   s.errChan, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// send open message | ||||||
|  | 	s.send <- msg | ||||||
|  |  | ||||||
|  | 	// wait for an error response for send | ||||||
|  | 	select { | ||||||
|  | 	case err := <-msg.errChan: | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	case <-s.closed: | ||||||
|  | 		return io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// we don't wait on multicast | ||||||
|  | 	if s.multicast { | ||||||
|  | 		s.accepted = true | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// now wait for the accept | ||||||
|  | 	select { | ||||||
|  | 	case msg = <-s.recv: | ||||||
|  | 		if msg.typ != "accept" { | ||||||
|  | 			log.Debugf("Received non accept message in Open %s", msg.typ) | ||||||
|  | 			return errors.New("failed to connect") | ||||||
|  | 		} | ||||||
|  | 		// set to accepted | ||||||
|  | 		s.accepted = true | ||||||
|  | 		// set link | ||||||
|  | 		s.link = msg.link | ||||||
|  | 	case <-time.After(s.timeout): | ||||||
|  | 		return ErrDialTimeout | ||||||
|  | 	case <-s.closed: | ||||||
|  | 		return io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *session) Accept() error { | ||||||
|  | 	msg := &message{ | ||||||
|  | 		typ:       "accept", | ||||||
|  | 		tunnel:    s.tunnel, | ||||||
|  | 		channel:   s.channel, | ||||||
|  | 		session:   s.session, | ||||||
|  | 		outbound:  s.outbound, | ||||||
|  | 		loopback:  s.loopback, | ||||||
|  | 		multicast: s.multicast, | ||||||
|  | 		link:      s.link, | ||||||
|  | 		errChan:   s.errChan, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// send the accept message | ||||||
|  | 	select { | ||||||
|  | 	case <-s.closed: | ||||||
|  | 		return io.EOF | ||||||
|  | 	case s.send <- msg: | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// wait for send response | ||||||
|  | 	select { | ||||||
|  | 	case err := <-s.errChan: | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	case <-s.closed: | ||||||
|  | 		return io.EOF | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func (s *session) Send(m *transport.Message) error { | func (s *session) Send(m *transport.Message) error { | ||||||
| 	select { | 	select { | ||||||
| 	case <-s.closed: | 	case <-s.closed: | ||||||
| 		return errors.New("session is closed") | 		return io.EOF | ||||||
| 	default: | 	default: | ||||||
| 		// no op | 		// no op | ||||||
| 	} | 	} | ||||||
| @@ -102,6 +204,7 @@ func (s *session) Send(m *transport.Message) error { | |||||||
| 		session:   s.session, | 		session:   s.session, | ||||||
| 		outbound:  s.outbound, | 		outbound:  s.outbound, | ||||||
| 		loopback:  s.loopback, | 		loopback:  s.loopback, | ||||||
|  | 		multicast: s.multicast, | ||||||
| 		data:      data, | 		data:      data, | ||||||
| 		// specify the link on which to send this | 		// specify the link on which to send this | ||||||
| 		// it will be blank for dialled sessions | 		// it will be blank for dialled sessions | ||||||
| @@ -109,6 +212,12 @@ func (s *session) Send(m *transport.Message) error { | |||||||
| 		// error chan | 		// error chan | ||||||
| 		errChan: s.errChan, | 		errChan: s.errChan, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	// if not multicast then set link | ||||||
|  | 	if !s.multicast { | ||||||
|  | 		msg.link = s.link | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	log.Debugf("Appending %+v to send backlog", msg) | 	log.Debugf("Appending %+v to send backlog", msg) | ||||||
| 	s.send <- msg | 	s.send <- msg | ||||||
|  |  | ||||||
| @@ -154,6 +263,25 @@ func (s *session) Close() error { | |||||||
| 		// no op | 		// no op | ||||||
| 	default: | 	default: | ||||||
| 		close(s.closed) | 		close(s.closed) | ||||||
|  |  | ||||||
|  | 		// append to backlog | ||||||
|  | 		msg := &message{ | ||||||
|  | 			typ:       "close", | ||||||
|  | 			tunnel:    s.tunnel, | ||||||
|  | 			channel:   s.channel, | ||||||
|  | 			session:   s.session, | ||||||
|  | 			outbound:  s.outbound, | ||||||
|  | 			loopback:  s.loopback, | ||||||
|  | 			multicast: s.multicast, | ||||||
|  | 			link:      s.link, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// send the close message | ||||||
|  | 		select { | ||||||
|  | 		case s.send <- msg: | ||||||
|  | 		default: | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,6 +2,9 @@ | |||||||
| package tunnel | package tunnel | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/micro/go-micro/transport" | 	"github.com/micro/go-micro/transport" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -18,7 +21,7 @@ type Tunnel interface { | |||||||
| 	// Close closes the tunnel | 	// Close closes the tunnel | ||||||
| 	Close() error | 	Close() error | ||||||
| 	// Connect to a channel | 	// Connect to a channel | ||||||
| 	Dial(channel string) (Session, error) | 	Dial(channel string, opts ...DialOption) (Session, error) | ||||||
| 	// Accept connections on a channel | 	// Accept connections on a channel | ||||||
| 	Listen(channel string) (Listener, error) | 	Listen(channel string) (Listener, error) | ||||||
| 	// Name of the tunnel implementation | 	// Name of the tunnel implementation | ||||||
| @@ -42,6 +45,12 @@ type Session interface { | |||||||
| 	transport.Socket | 	transport.Socket | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	ErrDialTimeout = errors.New("dial timeout") | ||||||
|  |  | ||||||
|  | 	DefaultDialTimeout = time.Second * 5 | ||||||
|  | ) | ||||||
|  |  | ||||||
| // NewTunnel creates a new tunnel | // NewTunnel creates a new tunnel | ||||||
| func NewTunnel(opts ...Option) Tunnel { | func NewTunnel(opts ...Option) Tunnel { | ||||||
| 	return newTunnel(opts...) | 	return newTunnel(opts...) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user