mirror of
https://github.com/YGGverse/yps.git
synced 2026-03-31 17:05:30 +00:00
initial commit
This commit is contained in:
parent
36556f4f3c
commit
1392fee475
10 changed files with 348 additions and 0 deletions
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
custom: https://yggverse.github.io/#donate
|
||||||
27
.github/workflows/linux.yml
vendored
Normal file
27
.github/workflows/linux.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
name: Linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
RUSTFLAGS: -Dwarnings
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Run rustfmt
|
||||||
|
run: cargo fmt --all -- --check
|
||||||
|
- name: Run clippy
|
||||||
|
run: cargo clippy --all-targets
|
||||||
|
- name: Build
|
||||||
|
run: cargo build --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test --verbose
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/target
|
||||||
|
Cargo.lock
|
||||||
16
Cargo.toml
Normal file
16
Cargo.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "yps"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "YPS - Yggdrasil Port Scanner"
|
||||||
|
keywords = ["yggdrasil", "tcp", "udp", "search", "crawler"]
|
||||||
|
categories = ["network-programming"]
|
||||||
|
repository = "https://github.com/YGGverse/yps"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0"
|
||||||
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
43
README.md
Normal file
43
README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# YPS - Yggdrasil Port Scanner
|
||||||
|
|
||||||
|

|
||||||
|
[](https://deps.rs/repo/github/yggverse/yps)
|
||||||
|
[](https://crates.io/crates/yps)
|
||||||
|
|
||||||
|
A simple crawler tool to find open ports for specific services,
|
||||||
|
such as web servers or Bitcoin nodes, to connect with [Yggdrasil](https://yggdrasil-network.github.io/).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
* `git clone https://github.com/yggverse/yps.git && cd yps`
|
||||||
|
* `cargo build --release`
|
||||||
|
* `sudo install target/release/yps /usr/local/bin/yps`
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
sudo yps --tcp -p 80 -p 443
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
-d, --debug
|
||||||
|
Print debug information
|
||||||
|
-s, --socket <SOCKET>
|
||||||
|
Yggdrasil socket path (unix only) [default: /var/run/yggdrasil.sock]
|
||||||
|
-p, --port <PORT>
|
||||||
|
Port(s) to scan
|
||||||
|
-u, --udp <UDP>
|
||||||
|
Crawl UDP ports (requires `[host]:port` binding as the value)
|
||||||
|
-t, --tcp
|
||||||
|
Crawl TCP ports
|
||||||
|
-l, --latency <LATENCY>
|
||||||
|
Pause in seconds before make new request
|
||||||
|
--index-capacity <INDEX_CAPACITY>
|
||||||
|
Optimize memory usage by providing expecting index capacity, according to the current network size [default: 1000]
|
||||||
|
-h, --help
|
||||||
|
Print help
|
||||||
|
-V, --version
|
||||||
|
Print version
|
||||||
|
```
|
||||||
34
src/config.rs
Normal file
34
src/config.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
use clap::Parser;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Print debug information
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
pub debug: bool,
|
||||||
|
|
||||||
|
/// Yggdrasil socket path (unix only)
|
||||||
|
#[arg(short, long, default_value_t = String::from("/var/run/yggdrasil.sock"))]
|
||||||
|
pub socket: String,
|
||||||
|
|
||||||
|
/// Port(s) to scan
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub port: Vec<u16>,
|
||||||
|
|
||||||
|
/// Crawl UDP ports (requires `[host]:port` binding as the value)
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub udp: Option<String>,
|
||||||
|
|
||||||
|
/// Crawl TCP ports
|
||||||
|
#[arg(short, long, default_value_t = false)]
|
||||||
|
pub tcp: bool,
|
||||||
|
|
||||||
|
/// Pause in seconds before make new request
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub latency: Option<u64>,
|
||||||
|
|
||||||
|
/// Optimize memory usage by providing expecting index capacity,
|
||||||
|
/// according to the current network size
|
||||||
|
#[arg(long, default_value_t = 1000)]
|
||||||
|
pub index_capacity: usize,
|
||||||
|
}
|
||||||
101
src/main.rs
Normal file
101
src/main.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
mod config;
|
||||||
|
mod udp;
|
||||||
|
mod yggdrasil;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use config::Config;
|
||||||
|
use std::{
|
||||||
|
net::{IpAddr, SocketAddr, TcpStream},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use udp::Udp;
|
||||||
|
use yggdrasil::Yggdrasil;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
use clap::Parser;
|
||||||
|
let config = Config::parse();
|
||||||
|
if config.port.is_empty() {
|
||||||
|
panic!("at least one port must be specified for scan!")
|
||||||
|
}
|
||||||
|
if !config.tcp && config.udp.is_none() {
|
||||||
|
panic!("at least one TCP or UDP protocol is required for scan!")
|
||||||
|
}
|
||||||
|
let latency = config.latency.map(Duration::from_secs); // parse once
|
||||||
|
let mut ygg = Yggdrasil::init(&config.socket)?;
|
||||||
|
let mut tcp: Option<Vec<SocketAddr>> = if config.tcp {
|
||||||
|
Some(Vec::with_capacity(config.index_capacity))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut udp = config.udp.as_ref().map(|bind| {
|
||||||
|
let server = Udp::init(bind).unwrap();
|
||||||
|
let index: Vec<SocketAddr> = Vec::with_capacity(config.index_capacity);
|
||||||
|
(index, server)
|
||||||
|
});
|
||||||
|
// get initial peers to crawl
|
||||||
|
if config.debug {
|
||||||
|
println!("get initial peers to crawl...");
|
||||||
|
}
|
||||||
|
let p = ygg.peers()?;
|
||||||
|
if p.status != "success" {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
// start crawler
|
||||||
|
for peer in p.response.unwrap().peers {
|
||||||
|
crawl(peer.key, latency, &config, &mut ygg, &mut tcp, &mut udp)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn crawl(
|
||||||
|
key: String,
|
||||||
|
latency: Option<Duration>,
|
||||||
|
config: &Config,
|
||||||
|
ygg: &mut Yggdrasil,
|
||||||
|
tcp: &mut Option<Vec<SocketAddr>>,
|
||||||
|
udp: &mut Option<(Vec<SocketAddr>, Udp)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
if config.debug {
|
||||||
|
println!("get peers for `{}`...", &key);
|
||||||
|
}
|
||||||
|
let p = ygg.remote_peers(key)?;
|
||||||
|
if p.status != "success" {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
for (host, peers) in p.response.unwrap() {
|
||||||
|
for port in &config.port {
|
||||||
|
let address = SocketAddr::new(IpAddr::V6(host), *port);
|
||||||
|
if let Some(index) = tcp {
|
||||||
|
if !index.contains(&address) {
|
||||||
|
let url = format!("tcp://{address}");
|
||||||
|
if config.debug {
|
||||||
|
println!("try `{url}`...");
|
||||||
|
}
|
||||||
|
if TcpStream::connect_timeout(&address, Duration::from_secs(1)).is_ok() {
|
||||||
|
println!("{url}");
|
||||||
|
index.push(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some((index, server)) = udp {
|
||||||
|
let url = format!("udp://{address}");
|
||||||
|
if !index.contains(&address) {
|
||||||
|
if config.debug {
|
||||||
|
println!("try `{url}`...");
|
||||||
|
}
|
||||||
|
if server.check(address) {
|
||||||
|
println!("{url}");
|
||||||
|
index.push(address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k in peers.keys {
|
||||||
|
if let Some(l) = latency {
|
||||||
|
std::thread::sleep(l);
|
||||||
|
}
|
||||||
|
crawl(k, latency, config, ygg, tcp, udp)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
33
src/udp.rs
Normal file
33
src/udp.rs
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::{
|
||||||
|
net::{SocketAddr, SocketAddrV6, UdpSocket},
|
||||||
|
str::FromStr,
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Simple UDP server to test peers connection
|
||||||
|
pub struct Udp(UdpSocket);
|
||||||
|
|
||||||
|
impl Udp {
|
||||||
|
pub fn init(bind: &str) -> Result<Self> {
|
||||||
|
const T: u64 = 1; // @TODO optional
|
||||||
|
|
||||||
|
let this = UdpSocket::bind(SocketAddrV6::from_str(bind)?)?;
|
||||||
|
this.set_write_timeout(Some(Duration::from_secs(T)))?;
|
||||||
|
this.set_read_timeout(Some(Duration::from_secs(T)))?;
|
||||||
|
Ok(Self(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(&self, remote: SocketAddr) -> bool {
|
||||||
|
if self.0.connect(remote).is_err() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
match self.0.send_to(b"test", remote) {
|
||||||
|
Ok(_) => {
|
||||||
|
let mut b = [0; 1];
|
||||||
|
self.0.recv_from(&mut b).is_ok() // @TODO
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/yggdrasil.rs
Normal file
50
src/yggdrasil.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
mod api;
|
||||||
|
use api::*;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
io::{Read, Write},
|
||||||
|
net::Ipv6Addr,
|
||||||
|
os::unix::net::UnixStream,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Yggdrasil(UnixStream);
|
||||||
|
|
||||||
|
impl Yggdrasil {
|
||||||
|
pub fn init(path: &str) -> Result<Self> {
|
||||||
|
Ok(Self(UnixStream::connect(path)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peers(&mut self) -> Result<Response<LocalPeers>> {
|
||||||
|
let r: Response<LocalPeers> = serde_json::from_slice(
|
||||||
|
&self.request(b"{\"keepalive\":true,\"request\":\"getpeers\"}")?,
|
||||||
|
)?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remote_peers(
|
||||||
|
&mut self,
|
||||||
|
public_key: String,
|
||||||
|
) -> Result<Response<HashMap<Ipv6Addr, RemotePeers>>> {
|
||||||
|
let r: Response<HashMap<Ipv6Addr, RemotePeers>> = serde_json::from_slice(&self.request(
|
||||||
|
format!("{{\"keepalive\":true,\"request\":\"debug_remotegetpeers\",\"arguments\":{{\"key\":\"{public_key}\"}}}}")
|
||||||
|
.as_bytes(),
|
||||||
|
)?)?;
|
||||||
|
Ok(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request(&mut self, bytes: &[u8]) -> Result<Vec<u8>> {
|
||||||
|
const L: usize = 1024; // chunk len
|
||||||
|
self.0.write_all(bytes)?;
|
||||||
|
let mut r = Vec::with_capacity(L);
|
||||||
|
loop {
|
||||||
|
let mut b = vec![0; L];
|
||||||
|
let l = self.0.read(&mut b)?; // can't `read_to_end` as no EOF
|
||||||
|
r.extend(&b[..l]);
|
||||||
|
if l < L {
|
||||||
|
return Ok(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/yggdrasil/api.rs
Normal file
41
src/yggdrasil/api.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct Response<T> {
|
||||||
|
pub response: Option<T>,
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct LocalPeers {
|
||||||
|
pub peers: Vec<LocalPeer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct LocalPeer {
|
||||||
|
pub key: String,
|
||||||
|
// pub address: std::net::Ipv6Addr,
|
||||||
|
// pub bytes_recvd: Option<u64>,
|
||||||
|
// pub bytes_sent: Option<u64>,
|
||||||
|
// pub inbound: bool,
|
||||||
|
// #[serde(default, deserialize_with = "time")]
|
||||||
|
// pub last_error_time: Option<Duration>,
|
||||||
|
// pub last_error: Option<String>,
|
||||||
|
// #[serde(default, deserialize_with = "time")]
|
||||||
|
// pub latency: Option<Duration>,
|
||||||
|
// pub port: u64,
|
||||||
|
// pub priority: Option<u64>,
|
||||||
|
// pub remote: Option<String>,
|
||||||
|
// pub up: bool,
|
||||||
|
// pub uptime: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct RemotePeers {
|
||||||
|
pub keys: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// use std::time::Duration;
|
||||||
|
// fn time<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<Duration>, D::Error> {
|
||||||
|
// u64::deserialize(d).map(|t| Some(Duration::from_nanos(t)))
|
||||||
|
// }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue