github.com/nxtrace/NTrace-core@v1.3.1-0.20240513132635-39169291e8c9/cmd/cmd.go (about)

     1  package cmd
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"github.com/fatih/color"
     7  	"log"
     8  	"net"
     9  	"os"
    10  	"os/signal"
    11  	"runtime"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/akamensky/argparse"
    16  	"github.com/nxtrace/NTrace-core/config"
    17  	fastTrace "github.com/nxtrace/NTrace-core/fast_trace"
    18  	"github.com/nxtrace/NTrace-core/ipgeo"
    19  	"github.com/nxtrace/NTrace-core/printer"
    20  	"github.com/nxtrace/NTrace-core/reporter"
    21  	"github.com/nxtrace/NTrace-core/trace"
    22  	"github.com/nxtrace/NTrace-core/tracelog"
    23  	"github.com/nxtrace/NTrace-core/tracemap"
    24  	"github.com/nxtrace/NTrace-core/util"
    25  	"github.com/nxtrace/NTrace-core/wshandle"
    26  	"github.com/syndtr/gocapability/capability"
    27  )
    28  
    29  func Excute() {
    30  	parser := argparse.NewParser("nexttrace", "An open source visual route tracking CLI tool")
    31  	// Create string flag
    32  	ipv4Only := parser.Flag("4", "ipv4", &argparse.Options{Help: "Use IPv4 only"})
    33  	ipv6Only := parser.Flag("6", "ipv6", &argparse.Options{Help: "Use IPv6 only"})
    34  	tcp := parser.Flag("T", "tcp", &argparse.Options{Help: "Use TCP SYN for tracerouting (default port is 80)"})
    35  	udp := parser.Flag("U", "udp", &argparse.Options{Help: "Use UDP SYN for tracerouting (default port is 53)"})
    36  	fast_trace := parser.Flag("F", "fast-trace", &argparse.Options{Help: "One-Key Fast Trace to China ISPs"})
    37  	port := parser.Int("p", "port", &argparse.Options{Help: "Set the destination port to use. It is either initial udp port value for \"default\"" +
    38  		"method (incremented by each probe, default is 33434), or initial seq for \"icmp\" (incremented as well, default from 1), or some constant" +
    39  		"destination port for other methods (with default of 80 for \"tcp\", 53 for \"udp\", etc.)"})
    40  	numMeasurements := parser.Int("q", "queries", &argparse.Options{Default: 3, Help: "Set the number of probes per each hop"})
    41  	parallelRequests := parser.Int("", "parallel-requests", &argparse.Options{Default: 18, Help: "Set ParallelRequests number. It should be 1 when there is a multi-routing"})
    42  	maxHops := parser.Int("m", "max-hops", &argparse.Options{Default: 30, Help: "Set the max number of hops (max TTL to be reached)"})
    43  	dataOrigin := parser.Selector("d", "data-provider", []string{"Ip2region", "ip2region", "IP.SB", "ip.sb", "IPInfo", "ipinfo", "IPInsight", "ipinsight", "IPAPI.com", "ip-api.com", "IPInfoLocal", "ipinfolocal", "chunzhen", "LeoMoeAPI", "leomoeapi", "disable-geoip"}, &argparse.Options{Default: "LeoMoeAPI",
    44  		Help: "Choose IP Geograph Data Provider [IP.SB, IPInfo, IPInsight, IP-API.com, Ip2region, IPInfoLocal, CHUNZHEN, disable-geoip]"})
    45  	powProvider := parser.Selector("", "pow-provider", []string{"api.nxtrace.org", "sakura"}, &argparse.Options{Default: "api.nxtrace.org",
    46  		Help: "Choose PoW Provider [api.nxtrace.org, sakura] For China mainland users, please use sakura"})
    47  	noRdns := parser.Flag("n", "no-rdns", &argparse.Options{Help: "Do not resolve IP addresses to their domain names"})
    48  	alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"})
    49  	routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"})
    50  	report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"})
    51  	dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"})
    52  	output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"})
    53  	tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"})
    54  	rawPrint := parser.Flag("", "raw", &argparse.Options{Help: "An Output Easy to Parse"})
    55  	jsonPrint := parser.Flag("j", "json", &argparse.Options{Help: "Output trace results as JSON"})
    56  	classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"})
    57  	beginHop := parser.Int("f", "first", &argparse.Options{Default: 1, Help: "Start from the first_ttl hop (instead from 1)"})
    58  	disableMaptrace := parser.Flag("M", "map", &argparse.Options{Help: "Disable Print Trace Map"})
    59  	disableMPLS := parser.Flag("e", "disable-mpls", &argparse.Options{Help: "Disable MPLS"})
    60  	ver := parser.Flag("v", "version", &argparse.Options{Help: "Print version info and exit"})
    61  	srcAddr := parser.String("s", "source", &argparse.Options{Help: "Use source src_addr for outgoing packets"})
    62  	srcDev := parser.String("D", "dev", &argparse.Options{Help: "Use the following Network Devices as the source address in outgoing packets"})
    63  	//router := parser.Flag("R", "route", &argparse.Options{Help: "Show Routing Table [Provided By BGP.Tools]"})
    64  	packetInterval := parser.Int("z", "send-time", &argparse.Options{Default: 100, Help: "Set how many [milliseconds] between sending each packet.. Useful when some routers use rate-limit for ICMP messages"})
    65  	ttlInterval := parser.Int("i", "ttl-time", &argparse.Options{Default: 500, Help: "Set how many [milliseconds] between sending packets groups by TTL. Useful when some routers use rate-limit for ICMP messages"})
    66  	timeout := parser.Int("", "timeout", &argparse.Options{Default: 1000, Help: "The number of [milliseconds] to keep probe sockets open before giving up on the connection."})
    67  	packetSize := parser.Int("", "psize", &argparse.Options{Default: 52, Help: "Set the packet size (payload size)"})
    68  	str := parser.StringPositional(&argparse.Options{Help: "IP Address or domain name"})
    69  	dot := parser.Selector("", "dot-server", []string{"dnssb", "aliyun", "dnspod", "google", "cloudflare"}, &argparse.Options{
    70  		Help: "Use DoT Server for DNS Parse [dnssb, aliyun, dnspod, google, cloudflare]"})
    71  	lang := parser.Selector("g", "language", []string{"en", "cn"}, &argparse.Options{Default: "cn",
    72  		Help: "Choose the language for displaying [en, cn]"})
    73  	file := parser.String("", "file", &argparse.Options{Help: "Read IP Address or domain name from file"})
    74  	nocolor := parser.Flag("C", "nocolor", &argparse.Options{Help: "Disable Colorful Output"})
    75  
    76  	err := parser.Parse(os.Args)
    77  	if err != nil {
    78  		// In case of error print error and print usage
    79  		// This can also be done by passing -h or --help flags
    80  		fmt.Print(parser.Usage(err))
    81  		return
    82  	}
    83  
    84  	if *nocolor {
    85  		color.NoColor = true
    86  	} else {
    87  		color.NoColor = false
    88  	}
    89  
    90  	if !*jsonPrint {
    91  		printer.Version()
    92  	}
    93  
    94  	if *ver {
    95  		printer.CopyRight()
    96  		os.Exit(0)
    97  	}
    98  
    99  	domain := *str
   100  
   101  	if *port == 0 {
   102  		*port = 80
   103  	}
   104  
   105  	if *fast_trace || *file != "" {
   106  		var paramsFastTrace = fastTrace.ParamsFastTrace{
   107  			SrcDev:         *srcDev,
   108  			SrcAddr:        *srcAddr,
   109  			BeginHop:       *beginHop,
   110  			MaxHops:        *maxHops,
   111  			RDns:           !*noRdns,
   112  			AlwaysWaitRDNS: *alwaysRdns,
   113  			Lang:           *lang,
   114  			PktSize:        *packetSize,
   115  			Timeout:        time.Duration(*timeout) * time.Millisecond,
   116  			File:           *file,
   117  		}
   118  
   119  		fastTrace.FastTest(*tcp, *output, paramsFastTrace)
   120  		if *output {
   121  			fmt.Println("您的追踪日志已经存放在 /tmp/trace.log 中")
   122  		}
   123  
   124  		os.Exit(0)
   125  	}
   126  
   127  	// DOMAIN处理开始
   128  	if domain == "" {
   129  		fmt.Print(parser.Usage(err))
   130  		return
   131  	}
   132  
   133  	if strings.Contains(domain, "/") {
   134  		parts := strings.Split(domain, "/")
   135  		if len(parts) < 3 {
   136  			fmt.Println("Invalid input")
   137  			return
   138  		}
   139  		domain = parts[2]
   140  	}
   141  
   142  	if strings.Contains(domain, "]") {
   143  		domain = strings.Split(strings.Split(domain, "]")[0], "[")[1]
   144  	} else if strings.Contains(domain, ":") {
   145  		if strings.Count(domain, ":") == 1 {
   146  			domain = strings.Split(domain, ":")[0]
   147  		}
   148  	}
   149  	// DOMAIN处理结束
   150  
   151  	capabilitiesCheck()
   152  	// return
   153  
   154  	var ip net.IP
   155  
   156  	if runtime.GOOS == "windows" && (*tcp || *udp) {
   157  		fmt.Println("NextTrace 基于 Windows 的路由跟踪还在早期开发阶段,目前还存在诸多问题,TCP/UDP SYN 包请求可能不能正常运行")
   158  	}
   159  
   160  	if *dn42 {
   161  		// 初始化配置
   162  		config.InitConfig()
   163  		*dataOrigin = "DN42"
   164  		*disableMaptrace = true
   165  	}
   166  
   167  	/**
   168  	 * 此处若使用goroutine同时运行ws的建立与nslookup,
   169  	 * 会导致第一跳的IP信息无法获取,原因不明。
   170  	 */
   171  	//var wg sync.WaitGroup
   172  	//wg.Add(2)
   173  	//
   174  	//go func() {
   175  	//	defer wg.Done()
   176  	if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" {
   177  		val, ok := os.LookupEnv("NEXTTRACE_DATAPROVIDER")
   178  		if strings.ToUpper(*powProvider) != "API.NXTRACE.ORG" {
   179  			util.PowProviderParam = *powProvider
   180  		}
   181  		if ok {
   182  			*dataOrigin = val
   183  		} else {
   184  			w := wshandle.New()
   185  			w.Interrupt = make(chan os.Signal, 1)
   186  			signal.Notify(w.Interrupt, os.Interrupt)
   187  			defer func() {
   188  				w.Conn.Close()
   189  			}()
   190  		}
   191  	}
   192  	//}()
   193  	//
   194  	//go func() {
   195  	//	defer wg.Done()
   196  	if *udp {
   197  		if *ipv6Only {
   198  			fmt.Println("[Info] IPv6 UDP Traceroute is not supported right now.")
   199  			os.Exit(0)
   200  		}
   201  		ip, err = util.DomainLookUp(domain, "4", *dot, *jsonPrint)
   202  	} else {
   203  		if *ipv6Only {
   204  			ip, err = util.DomainLookUp(domain, "6", *dot, *jsonPrint)
   205  		} else if *ipv4Only {
   206  			ip, err = util.DomainLookUp(domain, "4", *dot, *jsonPrint)
   207  		} else {
   208  			ip, err = util.DomainLookUp(domain, "all", *dot, *jsonPrint)
   209  		}
   210  	}
   211  	if err != nil {
   212  		fmt.Println(err)
   213  		os.Exit(1)
   214  	}
   215  	//}()
   216  	//
   217  	//wg.Wait()
   218  
   219  	if *srcDev != "" {
   220  		dev, _ := net.InterfaceByName(*srcDev)
   221  		if addrs, err := dev.Addrs(); err == nil {
   222  			for _, addr := range addrs {
   223  				if (addr.(*net.IPNet).IP.To4() == nil) == (ip.To4() == nil) {
   224  					*srcAddr = addr.(*net.IPNet).IP.String()
   225  					// 检查是否是内网IP
   226  					if !(net.ParseIP(*srcAddr).IsPrivate() ||
   227  						net.ParseIP(*srcAddr).IsLoopback() ||
   228  						net.ParseIP(*srcAddr).IsLinkLocalUnicast() ||
   229  						net.ParseIP(*srcAddr).IsLinkLocalMulticast()) {
   230  						// 若不是则跳出
   231  						break
   232  					}
   233  				}
   234  			}
   235  		}
   236  	}
   237  
   238  	if !*jsonPrint {
   239  		printer.PrintTraceRouteNav(ip, domain, *dataOrigin, *maxHops, *packetSize)
   240  	}
   241  
   242  	var m trace.Method
   243  
   244  	switch {
   245  	case *tcp:
   246  		m = trace.TCPTrace
   247  	case *udp:
   248  		m = trace.UDPTrace
   249  	default:
   250  		m = trace.ICMPTrace
   251  	}
   252  
   253  	if !*tcp && *port == 80 {
   254  		*port = 53
   255  	}
   256  
   257  	util.DestIP = ip.String()
   258  	var conf = trace.Config{
   259  		DN42:             *dn42,
   260  		SrcAddr:          *srcAddr,
   261  		BeginHop:         *beginHop,
   262  		DestIP:           ip,
   263  		DestPort:         *port,
   264  		MaxHops:          *maxHops,
   265  		PacketInterval:   *packetInterval,
   266  		TTLInterval:      *ttlInterval,
   267  		NumMeasurements:  *numMeasurements,
   268  		ParallelRequests: *parallelRequests,
   269  		Lang:             *lang,
   270  		RDns:             !*noRdns,
   271  		AlwaysWaitRDNS:   *alwaysRdns,
   272  		IPGeoSource:      ipgeo.GetSource(*dataOrigin),
   273  		Timeout:          time.Duration(*timeout) * time.Millisecond,
   274  		PktSize:          *packetSize,
   275  	}
   276  
   277  	// 暂时弃用
   278  	router := new(bool)
   279  	*router = false
   280  	if !*tablePrint {
   281  		if *classicPrint {
   282  			conf.RealtimePrinter = printer.ClassicPrinter
   283  		} else if *rawPrint {
   284  			conf.RealtimePrinter = printer.EasyPrinter
   285  		} else {
   286  			if *output {
   287  				conf.RealtimePrinter = tracelog.RealtimePrinter
   288  			} else if *router {
   289  				conf.RealtimePrinter = printer.RealtimePrinterWithRouter
   290  				fmt.Println("路由表数据源由 BGP.Tools 提供,在此特表感谢")
   291  			} else {
   292  				conf.RealtimePrinter = printer.RealtimePrinter
   293  			}
   294  		}
   295  	} else {
   296  		if !*report {
   297  			conf.AsyncPrinter = printer.TracerouteTablePrinter
   298  		}
   299  	}
   300  
   301  	if *jsonPrint {
   302  		conf.RealtimePrinter = nil
   303  		conf.AsyncPrinter = nil
   304  	}
   305  
   306  	if util.Uninterrupted != "" && *rawPrint {
   307  		for {
   308  			_, err := trace.Traceroute(m, conf)
   309  			if err != nil {
   310  				fmt.Println(err)
   311  			}
   312  		}
   313  	}
   314  
   315  	if *disableMPLS {
   316  		util.DisableMPLS = "1"
   317  	}
   318  
   319  	res, err := trace.Traceroute(m, conf)
   320  
   321  	if err != nil {
   322  		log.Fatalln(err)
   323  	}
   324  
   325  	if *tablePrint {
   326  		printer.TracerouteTablePrinter(res)
   327  	}
   328  
   329  	if *routePath {
   330  		r := reporter.New(res, ip.String())
   331  		r.Print()
   332  	}
   333  
   334  	r, err := json.Marshal(res)
   335  	if err != nil {
   336  		fmt.Println(err)
   337  		return
   338  	}
   339  	if !*disableMaptrace &&
   340  		(util.StringInSlice(strings.ToUpper(*dataOrigin), []string{"LEOMOEAPI", "IPINFO", "IPINFO", "IP-API.COM", "IPAPI.COM"})) {
   341  		url, err := tracemap.GetMapUrl(string(r))
   342  		if err != nil {
   343  			log.Fatalln(err)
   344  		}
   345  		res.TraceMapUrl = url
   346  		if !*jsonPrint {
   347  			tracemap.PrintMapUrl(url)
   348  		}
   349  	}
   350  	r, err = json.Marshal(res)
   351  	if err != nil {
   352  		fmt.Println(err)
   353  		return
   354  	}
   355  	if *jsonPrint {
   356  		fmt.Println(string(r))
   357  	}
   358  }
   359  
   360  func capabilitiesCheck() {
   361  
   362  	// Windows 判断放在前面,防止遇到一些奇奇怪怪的问题
   363  	if runtime.GOOS == "windows" {
   364  		// Running on Windows, skip checking capabilities
   365  		return
   366  	}
   367  
   368  	uid := os.Getuid()
   369  	if uid == 0 {
   370  		// Running as root, skip checking capabilities
   371  		return
   372  	}
   373  
   374  	/***
   375  	* 检查当前进程是否有两个关键的权限
   376  	==== 看不到我 ====
   377  	* 没办法啦
   378  	* 自己之前承诺的坑补全篇
   379  	* 被迫填坑系列 qwq
   380  	==== 看不到我 ====
   381  	***/
   382  
   383  	// NewPid 已经被废弃了,这里改用 NewPid2 方法
   384  	caps, err := capability.NewPid2(0)
   385  	if err != nil {
   386  		// 判断是否为macOS
   387  		if runtime.GOOS == "darwin" {
   388  			// macOS下报错有问题
   389  		} else {
   390  			fmt.Println(err)
   391  		}
   392  		return
   393  	}
   394  
   395  	// load 获取全部的 caps 信息
   396  	err = caps.Load()
   397  	if err != nil {
   398  		fmt.Println(err)
   399  		return
   400  	}
   401  
   402  	// 判断一下权限有木有
   403  	if caps.Get(capability.EFFECTIVE, capability.CAP_NET_RAW) && caps.Get(capability.EFFECTIVE, capability.CAP_NET_ADMIN) {
   404  		// 有权限啦
   405  		return
   406  	} else {
   407  		// 没权限啦
   408  		fmt.Println("您正在以普通用户权限运行 NextTrace,但 NextTrace 未被赋予监听网络套接字的ICMP消息包、修改IP头信息(TTL)等路由跟踪所需的权限")
   409  		fmt.Println("请使用管理员用户执行 `sudo setcap cap_net_raw,cap_net_admin+eip ${your_nexttrace_path}/nexttrace` 命令,赋予相关权限后再运行~")
   410  		fmt.Println("什么?为什么 ping 普通用户执行不要 root 权限?因为这些工具在管理员安装时就已经被赋予了一些必要的权限,具体请使用 `getcap /usr/bin/ping` 查看")
   411  	}
   412  }