ing
This commit is contained in:
		
							parent
							
								
									df86e3cf66
								
							
						
					
					
						commit
						b88509516a
					
				@ -76,3 +76,11 @@
 | 
				
			|||||||
[prune]
 | 
					[prune]
 | 
				
			||||||
  go-tests = true
 | 
					  go-tests = true
 | 
				
			||||||
  unused-packages = true
 | 
					  unused-packages = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[constraint]]
 | 
				
			||||||
 | 
					  branch = "master"
 | 
				
			||||||
 | 
					  name = "github.com/grandcat/zeroconf"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[[constraint]]
 | 
				
			||||||
 | 
					  branch = "master"
 | 
				
			||||||
 | 
					  name = "github.com/huin/goupnp"
 | 
				
			||||||
 | 
				
			|||||||
@ -18,22 +18,28 @@ var (
 | 
				
			|||||||
		ExcludePatterns: []string{},
 | 
							ExcludePatterns: []string{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	z = &omd.Zone{
 | 
						z = &omd.Zone{
 | 
				
			||||||
		Network:    "192.168.1.0/24",
 | 
							Network:    "192.168.35.0/24",
 | 
				
			||||||
		MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
							MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
				
			||||||
		Address:    "192.168.1.201",
 | 
							// Address:    "192.168.1.201",
 | 
				
			||||||
		Iface:      "\\Device\\NPF_{1924FA2B-6927-4BA5-AF43-876C3F8853CE}",
 | 
							// Iface:      "\\Device\\NPF_{1924FA2B-6927-4BA5-AF43-876C3F8853CE}",
 | 
				
			||||||
		Mac:        "30:9C:23:15:A3:09",
 | 
							// Mac:        "30:9C:23:15:A3:09",
 | 
				
			||||||
 | 
							// Address: "192.168.1.101",
 | 
				
			||||||
 | 
							// Iface:   "enp3s0",
 | 
				
			||||||
 | 
							// Mac:     "44:8a:5b:f1:f1:f3",
 | 
				
			||||||
 | 
							Address: "192.168.35.234",
 | 
				
			||||||
 | 
							Iface:   "wlp5s0",
 | 
				
			||||||
 | 
							Mac:     "d0:7e:35:da:26:68",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dh = &omd.DiscoverHost{
 | 
						dh = &omd.DiscoverHost{
 | 
				
			||||||
		MetaIPType:     omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
							MetaIPType:     omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
				
			||||||
		FirstScanRange: "192.168.1.1",
 | 
							FirstScanRange: "192.168.35.1",
 | 
				
			||||||
		LastScanRange:  "192.168.1.254",
 | 
							LastScanRange:  "192.168.35.254",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	h = &omd.Host{
 | 
						h = &omd.Host{
 | 
				
			||||||
		Zone:       z,
 | 
							Zone:       z,
 | 
				
			||||||
		MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
							MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
				
			||||||
		Address:    "127.0.0.1",
 | 
							Address:    "192.168.35.80",
 | 
				
			||||||
		Mac:        "50:E5:49:46:93:28",
 | 
							Mac:        "6c:ad:f8:d3:f8:c6",
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	dp = &omd.DiscoverPort{
 | 
						dp = &omd.DiscoverPort{
 | 
				
			||||||
		FirstScanRange: 1,
 | 
							FirstScanRange: 1,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										266
									
								
								mdns/mdns.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										266
									
								
								mdns/mdns.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,266 @@
 | 
				
			|||||||
 | 
					package mdns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync/atomic"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
						omm "git.loafle.net/overflow/model/meta"
 | 
				
			||||||
 | 
						omu "git.loafle.net/overflow/model/util"
 | 
				
			||||||
 | 
						"git.loafle.net/overflow_scanner/probe/model"
 | 
				
			||||||
 | 
						"github.com/grandcat/zeroconf"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type serviceMeta struct {
 | 
				
			||||||
 | 
						name       string
 | 
				
			||||||
 | 
						cryptoType string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						serviceMetaMapping = map[string]*serviceMeta{
 | 
				
			||||||
 | 
							"http": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "HTTP",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"printer": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "PRINTER",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"pdl-datastream": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "PDL-DATASTREAM",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"ipp": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "IPP",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"webdav": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "WEBDAV",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"webdavs": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "WEBDAV",
 | 
				
			||||||
 | 
								cryptoType: "TLS",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"afpovertcp": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "AFP",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"smb": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "SMB",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"rfb": &serviceMeta{
 | 
				
			||||||
 | 
								name:       "RFB",
 | 
				
			||||||
 | 
								cryptoType: "NONE",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Scan(discovered model.Discovered) {
 | 
				
			||||||
 | 
						serviceEntries, err := browse("_services._dns-sd._udp", "local")
 | 
				
			||||||
 | 
						if nil != err {
 | 
				
			||||||
 | 
							log.Print("Cannot find service ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						metaIPTypeEnum := omm.ToMetaIPTypeEnum(discovered.Zone().MetaIPType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, serviceEntry := range serviceEntries {
 | 
				
			||||||
 | 
							name := removeDomainName(serviceEntry.Instance, serviceEntry.Domain)
 | 
				
			||||||
 | 
							entries, _err := browse(name, serviceEntry.Domain)
 | 
				
			||||||
 | 
							if nil != _err {
 | 
				
			||||||
 | 
								log.Print("Cannot find entry ", _err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						LOOP:
 | 
				
			||||||
 | 
							for _, entry := range entries {
 | 
				
			||||||
 | 
								log.Print("serviceEntry ", entry)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								name := entry.Instance   // HP\ LaserJet\ P1505n
 | 
				
			||||||
 | 
								service := entry.Service // _pdl-datastream._tcp
 | 
				
			||||||
 | 
								port := entry.Port       // 9100
 | 
				
			||||||
 | 
								meta := toMeta(entry.Text)
 | 
				
			||||||
 | 
								hostName := removeDomainName(entry.HostName, entry.Domain) // NPIFACA9B
 | 
				
			||||||
 | 
								serviceName, portType, cryptoType := parseService(service)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var metaPortType *omm.MetaPortType
 | 
				
			||||||
 | 
								switch portType {
 | 
				
			||||||
 | 
								case "tcp":
 | 
				
			||||||
 | 
									metaPortType = omm.ToMetaPortType(omm.MetaPortTypeEnumTCP)
 | 
				
			||||||
 | 
								case "udp":
 | 
				
			||||||
 | 
									metaPortType = omm.ToMetaPortType(omm.MetaPortTypeEnumUDP)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								metaCryptoType := omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumUNKNOWN)
 | 
				
			||||||
 | 
								switch cryptoType {
 | 
				
			||||||
 | 
								case "NONE":
 | 
				
			||||||
 | 
									metaCryptoType = omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumNONE)
 | 
				
			||||||
 | 
								case "TLS":
 | 
				
			||||||
 | 
									metaCryptoType = omm.ToMetaCryptoType(omm.MetaCryptoTypeEnumTLS)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch metaIPTypeEnum {
 | 
				
			||||||
 | 
								case omm.MetaIPTypeEnumV4:
 | 
				
			||||||
 | 
									for _, ipv4 := range entry.AddrIPv4 {
 | 
				
			||||||
 | 
										h := discovered.AddHost(&omd.Host{
 | 
				
			||||||
 | 
											MetaIPType:     omm.ToMetaIPType(metaIPTypeEnum),
 | 
				
			||||||
 | 
											Name:           hostName,
 | 
				
			||||||
 | 
											Address:        ipv4.String(),
 | 
				
			||||||
 | 
											Meta:           meta,
 | 
				
			||||||
 | 
											Zone:           discovered.Zone(),
 | 
				
			||||||
 | 
											DiscoveredDate: omu.NowPtr(),
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if 1 > port {
 | 
				
			||||||
 | 
											continue LOOP
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										p := discovered.AddPort(&omd.Port{
 | 
				
			||||||
 | 
											MetaPortType: metaPortType,
 | 
				
			||||||
 | 
											PortNumber:   json.Number(strconv.Itoa(port)),
 | 
				
			||||||
 | 
											Meta:         meta,
 | 
				
			||||||
 | 
											Host:         h,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										discovered.AddService(&omd.Service{
 | 
				
			||||||
 | 
											MetaCryptoType: metaCryptoType,
 | 
				
			||||||
 | 
											Key:            serviceName,
 | 
				
			||||||
 | 
											Name:           name,
 | 
				
			||||||
 | 
											Port:           p,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								case omm.MetaIPTypeEnumV6:
 | 
				
			||||||
 | 
									for _, ipv6 := range entry.AddrIPv6 {
 | 
				
			||||||
 | 
										h := discovered.AddHost(&omd.Host{
 | 
				
			||||||
 | 
											MetaIPType:     omm.ToMetaIPType(metaIPTypeEnum),
 | 
				
			||||||
 | 
											Name:           hostName,
 | 
				
			||||||
 | 
											Address:        ipv6.String(),
 | 
				
			||||||
 | 
											Meta:           meta,
 | 
				
			||||||
 | 
											Zone:           discovered.Zone(),
 | 
				
			||||||
 | 
											DiscoveredDate: omu.NowPtr(),
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if 1 > port {
 | 
				
			||||||
 | 
											continue LOOP
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										p := discovered.AddPort(&omd.Port{
 | 
				
			||||||
 | 
											MetaPortType: metaPortType,
 | 
				
			||||||
 | 
											PortNumber:   json.Number(strconv.Itoa(port)),
 | 
				
			||||||
 | 
											Meta:         meta,
 | 
				
			||||||
 | 
											Host:         h,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										discovered.AddService(&omd.Service{
 | 
				
			||||||
 | 
											MetaCryptoType: metaCryptoType,
 | 
				
			||||||
 | 
											Key:            serviceName,
 | 
				
			||||||
 | 
											Name:           name,
 | 
				
			||||||
 | 
											Port:           p,
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func removeDomainName(instance string, domain string) (service string) {
 | 
				
			||||||
 | 
						_instance := strings.TrimRight(instance, ".")
 | 
				
			||||||
 | 
						return strings.TrimRight(_instance, fmt.Sprintf(".%s", domain))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func toMeta(text []string) map[string]string {
 | 
				
			||||||
 | 
						if nil == text || 0 == len(text) {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						meta := make(map[string]string)
 | 
				
			||||||
 | 
						for _, v := range text {
 | 
				
			||||||
 | 
							ss := strings.SplitN(v, "=", 2)
 | 
				
			||||||
 | 
							if 2 != len(ss) {
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if nil != ss {
 | 
				
			||||||
 | 
								meta[ss[0]] = ss[1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return meta
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseService(service string) (name string, portType string, cryptoType string) {
 | 
				
			||||||
 | 
						ss := strings.SplitN(service, ".", 2)
 | 
				
			||||||
 | 
						if nil == ss {
 | 
				
			||||||
 | 
							return "UNKNOWN", "UNKNOWN", "UNKNOWN"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_name := strings.TrimLeft(ss[0], "_")
 | 
				
			||||||
 | 
						_portType := strings.TrimLeft(ss[1], "_")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m, ok := serviceMetaMapping[_name]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return "UNKNOWN", "UNKNOWN", "UNKNOWN"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						name = m.name
 | 
				
			||||||
 | 
						portType = _portType
 | 
				
			||||||
 | 
						cryptoType = m.cryptoType
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func browse(service string, domain string) ([]*zeroconf.ServiceEntry, error) {
 | 
				
			||||||
 | 
						resolver, err := zeroconf.NewResolver(nil)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Failed to initialize resolver %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						entryChan := make(chan *zeroconf.ServiceEntry)
 | 
				
			||||||
 | 
						timerStopped := make(chan struct{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						serviceEntries := make([]*zeroconf.ServiceEntry, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						go func(_entryChan <-chan *zeroconf.ServiceEntry) {
 | 
				
			||||||
 | 
							var delay atomic.Value
 | 
				
			||||||
 | 
							delay.Store(false)
 | 
				
			||||||
 | 
							ticker := time.NewTicker(time.Second * 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for {
 | 
				
			||||||
 | 
								select {
 | 
				
			||||||
 | 
								case entry, ok := <-_entryChan:
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									delay.Store(true)
 | 
				
			||||||
 | 
									serviceEntries = append(serviceEntries, entry)
 | 
				
			||||||
 | 
								case <-ticker.C:
 | 
				
			||||||
 | 
									if false == delay.Load().(bool) {
 | 
				
			||||||
 | 
										ticker.Stop()
 | 
				
			||||||
 | 
										timerStopped <- struct{}{}
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									delay.Store(false)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}(entryChan)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(30))
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
						err = resolver.Browse(ctx, service, domain, entryChan)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("Failed to browse %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case <-ctx.Done():
 | 
				
			||||||
 | 
						case <-timerStopped:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return serviceEntries, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										139
									
								
								mdns/mdns_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								mdns/mdns_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,139 @@
 | 
				
			|||||||
 | 
					package mdns
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
						omm "git.loafle.net/overflow/model/meta"
 | 
				
			||||||
 | 
						"git.loafle.net/overflow_scanner/probe/model"
 | 
				
			||||||
 | 
						"github.com/grandcat/zeroconf"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestScan(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							discovered model.Discovered
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "1",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									discovered: model.New(
 | 
				
			||||||
 | 
										&omd.Zone{
 | 
				
			||||||
 | 
											Network:    "192.168.1.0/24",
 | 
				
			||||||
 | 
											Iface:      "enp3s0",
 | 
				
			||||||
 | 
											MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
				
			||||||
 | 
											Address:    "192.168.1.101",
 | 
				
			||||||
 | 
											Mac:        "44:8a:5b:f1:f1:f3",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								Scan(tt.args.discovered)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_removeDomainName(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							instance string
 | 
				
			||||||
 | 
							domain   string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name        string
 | 
				
			||||||
 | 
							args        args
 | 
				
			||||||
 | 
							wantService string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if gotService := removeDomainName(tt.args.instance, tt.args.domain); gotService != tt.wantService {
 | 
				
			||||||
 | 
									t.Errorf("removeDomainName() = %v, want %v", gotService, tt.wantService)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_toMeta(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							text []string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
							want map[string]string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if got := toMeta(tt.args.text); !reflect.DeepEqual(got, tt.want) {
 | 
				
			||||||
 | 
									t.Errorf("toMeta() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_parseService(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							service string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name           string
 | 
				
			||||||
 | 
							args           args
 | 
				
			||||||
 | 
							wantName       string
 | 
				
			||||||
 | 
							wantPortType   string
 | 
				
			||||||
 | 
							wantCryptoType string
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								gotName, gotPortType, gotCryptoType := parseService(tt.args.service)
 | 
				
			||||||
 | 
								if gotName != tt.wantName {
 | 
				
			||||||
 | 
									t.Errorf("parseService() gotName = %v, want %v", gotName, tt.wantName)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if gotPortType != tt.wantPortType {
 | 
				
			||||||
 | 
									t.Errorf("parseService() gotPortType = %v, want %v", gotPortType, tt.wantPortType)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if gotCryptoType != tt.wantCryptoType {
 | 
				
			||||||
 | 
									t.Errorf("parseService() gotCryptoType = %v, want %v", gotCryptoType, tt.wantCryptoType)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Test_browse(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							service string
 | 
				
			||||||
 | 
							domain  string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name    string
 | 
				
			||||||
 | 
							args    args
 | 
				
			||||||
 | 
							want    []*zeroconf.ServiceEntry
 | 
				
			||||||
 | 
							wantErr bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								got, err := browse(tt.args.service, tt.args.domain)
 | 
				
			||||||
 | 
								if (err != nil) != tt.wantErr {
 | 
				
			||||||
 | 
									t.Errorf("browse() error = %v, wantErr %v", err, tt.wantErr)
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if !reflect.DeepEqual(got, tt.want) {
 | 
				
			||||||
 | 
									t.Errorf("browse() = %v, want %v", got, tt.want)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										178
									
								
								model/Discovered.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								model/Discovered.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,178 @@
 | 
				
			|||||||
 | 
					package model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Discovered interface {
 | 
				
			||||||
 | 
						Zone() *omd.Zone
 | 
				
			||||||
 | 
						AddHost(host *omd.Host) *omd.Host
 | 
				
			||||||
 | 
						AddPort(port *omd.Port) *omd.Port
 | 
				
			||||||
 | 
						AddService(service *omd.Service) *omd.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func New(zone *omd.Zone) Discovered {
 | 
				
			||||||
 | 
						return &discovered{
 | 
				
			||||||
 | 
							zone:     zone,
 | 
				
			||||||
 | 
							hosts:    make(map[string]*omd.Host),
 | 
				
			||||||
 | 
							ports:    make(map[*omd.Host]map[json.Number]map[string]*omd.Port),
 | 
				
			||||||
 | 
							services: make(map[*omd.Port]map[string]map[string]*omd.Service),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type discovered struct {
 | 
				
			||||||
 | 
						zone     *omd.Zone
 | 
				
			||||||
 | 
						hosts    map[string]*omd.Host
 | 
				
			||||||
 | 
						ports    map[*omd.Host]map[json.Number]map[string]*omd.Port
 | 
				
			||||||
 | 
						services map[*omd.Port]map[string]map[string]*omd.Service
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) Zone() *omd.Zone {
 | 
				
			||||||
 | 
						return d.zone
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) AddHost(host *omd.Host) *omd.Host {
 | 
				
			||||||
 | 
						h := d.findHost(host, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if "" == h.Mac && "" != host.Mac {
 | 
				
			||||||
 | 
							h.Mac = host.Mac
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h.Meta = d.appendMeta(h.Meta, host.Meta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) AddPort(port *omd.Port) *omd.Port {
 | 
				
			||||||
 | 
						p := d.findPort(port, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.Meta = d.appendMeta(p.Meta, port.Meta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) AddService(service *omd.Service) *omd.Service {
 | 
				
			||||||
 | 
						s := d.findService(service, true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.Meta = d.appendMeta(s.Meta, service.Meta)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) findHost(host *omd.Host, add bool) *omd.Host {
 | 
				
			||||||
 | 
						h, ok := d.hosts[host.Address]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							d.hosts[host.Address] = host
 | 
				
			||||||
 | 
							h = host
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return h
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) findPort(port *omd.Port, add bool) *omd.Port {
 | 
				
			||||||
 | 
						h := d.findHost(port.Host, false)
 | 
				
			||||||
 | 
						if nil == h {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						hostPorts, ok := d.ports[h]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							d.ports[h] = make(map[json.Number]map[string]*omd.Port)
 | 
				
			||||||
 | 
							hostPorts = d.ports[h]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ports, ok := hostPorts[port.PortNumber]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							hostPorts[port.PortNumber] = make(map[string]*omd.Port)
 | 
				
			||||||
 | 
							ports = hostPorts[port.PortNumber]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p, ok := ports[port.MetaPortType.Key]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ports[port.MetaPortType.Key] = port
 | 
				
			||||||
 | 
							p = ports[port.MetaPortType.Key]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return p
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) findService(service *omd.Service, add bool) *omd.Service {
 | 
				
			||||||
 | 
						p := d.findPort(service.Port, false)
 | 
				
			||||||
 | 
						if nil == p {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						portServices, ok := d.services[p]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							d.services[p] = make(map[string]map[string]*omd.Service)
 | 
				
			||||||
 | 
							portServices = d.services[p]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						services, ok := portServices[service.Key]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							portServices[service.Key] = make(map[string]*omd.Service)
 | 
				
			||||||
 | 
							services = portServices[service.Key]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s, ok := services[service.MetaCryptoType.Key]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							if !add {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							services[service.MetaCryptoType.Key] = service
 | 
				
			||||||
 | 
							s = services[service.MetaCryptoType.Key]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (d *discovered) appendMeta(oriMeta map[string]string, newMeta map[string]string) map[string]string {
 | 
				
			||||||
 | 
						if nil == newMeta {
 | 
				
			||||||
 | 
							return oriMeta
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if nil == oriMeta {
 | 
				
			||||||
 | 
							return newMeta
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOOP:
 | 
				
			||||||
 | 
						for k, v := range oriMeta {
 | 
				
			||||||
 | 
							_v, _ok := oriMeta[k]
 | 
				
			||||||
 | 
							if !_ok {
 | 
				
			||||||
 | 
								oriMeta[k] = v
 | 
				
			||||||
 | 
								continue LOOP
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if v == _v {
 | 
				
			||||||
 | 
								continue LOOP
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							oriMeta[k] = fmt.Sprintf("%s|||%s", _v, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return oriMeta
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,10 +1,13 @@
 | 
				
			|||||||
package service
 | 
					package service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	oa "git.loafle.net/overflow/annotation-go"
 | 
						oa "git.loafle.net/overflow/annotation-go"
 | 
				
			||||||
	od "git.loafle.net/overflow/di-go"
 | 
						od "git.loafle.net/overflow/di-go"
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
						"git.loafle.net/overflow_scanner/probe/model"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
@ -17,7 +20,9 @@ type DiscoveryService struct {
 | 
				
			|||||||
	oa.TypeAnnotation `annotation:"@Injectable('name': 'DiscoveryService') @Service()"`
 | 
						oa.TypeAnnotation `annotation:"@Injectable('name': 'DiscoveryService') @Service()"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *DiscoveryService) StartDiscover() error {
 | 
					func (s *DiscoveryService) StartDiscover(zone *omd.Zone) error {
 | 
				
			||||||
 | 
						d := model.New(zone)
 | 
				
			||||||
 | 
						log.Print(d)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										49
									
								
								upnp/upnp.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								upnp/upnp.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					package upnp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
						omu "git.loafle.net/overflow/model/util"
 | 
				
			||||||
 | 
						"git.loafle.net/overflow_scanner/probe/model"
 | 
				
			||||||
 | 
						"github.com/huin/goupnp"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						TargetRootDevice = "upnp:rootdevice"
 | 
				
			||||||
 | 
						TargetSSDPAll    = "ssdp:all"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Scan(discovered model.Discovered) {
 | 
				
			||||||
 | 
						devs, err := goupnp.DiscoverDevices(TargetRootDevice)
 | 
				
			||||||
 | 
						if nil != err {
 | 
				
			||||||
 | 
							fmt.Println("DeletePortMapping: ", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOOP:
 | 
				
			||||||
 | 
						for _, dev := range devs {
 | 
				
			||||||
 | 
							rd := dev.Root.Device
 | 
				
			||||||
 | 
							if !rd.PresentationURL.Ok {
 | 
				
			||||||
 | 
								continue LOOP
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							discovered.AddHost(&omd.Host{
 | 
				
			||||||
 | 
								MetaIPType: discovered.Zone().MetaIPType,
 | 
				
			||||||
 | 
								Name:       rd.FriendlyName,
 | 
				
			||||||
 | 
								Address:    rd.PresentationURL.URL.Host,
 | 
				
			||||||
 | 
								Meta: map[string]string{
 | 
				
			||||||
 | 
									"DeviceType":       rd.DeviceType,
 | 
				
			||||||
 | 
									"Manufacturer":     rd.Manufacturer,
 | 
				
			||||||
 | 
									"ManufacturerURL":  rd.ManufacturerURL.Str,
 | 
				
			||||||
 | 
									"ModelName":        rd.ModelName,
 | 
				
			||||||
 | 
									"ModelDescription": rd.ModelDescription,
 | 
				
			||||||
 | 
									"ModelNumber":      rd.ModelNumber,
 | 
				
			||||||
 | 
									"SerialNumber":     rd.SerialNumber,
 | 
				
			||||||
 | 
									"UDN":              rd.UDN,
 | 
				
			||||||
 | 
									"UPC":              rd.UPC,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Zone:           discovered.Zone(),
 | 
				
			||||||
 | 
								DiscoveredDate: omu.NowPtr(),
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								upnp/upnp_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								upnp/upnp_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
				
			|||||||
 | 
					package upnp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						omd "git.loafle.net/overflow/model/discovery"
 | 
				
			||||||
 | 
						omm "git.loafle.net/overflow/model/meta"
 | 
				
			||||||
 | 
						"git.loafle.net/overflow_scanner/probe/model"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestScan(t *testing.T) {
 | 
				
			||||||
 | 
						type args struct {
 | 
				
			||||||
 | 
							discovered model.Discovered
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							name string
 | 
				
			||||||
 | 
							args args
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							// TODO: Add test cases.
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "1",
 | 
				
			||||||
 | 
								args: args{
 | 
				
			||||||
 | 
									discovered: model.New(
 | 
				
			||||||
 | 
										&omd.Zone{
 | 
				
			||||||
 | 
											Network:    "192.168.1.0/24",
 | 
				
			||||||
 | 
											Iface:      "enp3s0",
 | 
				
			||||||
 | 
											MetaIPType: omm.ToMetaIPType(omm.MetaIPTypeEnumV4),
 | 
				
			||||||
 | 
											Address:    "192.168.1.101",
 | 
				
			||||||
 | 
											Mac:        "44:8a:5b:f1:f1:f3",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, tt := range tests {
 | 
				
			||||||
 | 
							t.Run(tt.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								Scan(tt.args.discovered)
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user