mirror of
https://github.com/YGGverse/yps.git
synced 2026-03-31 08:55:28 +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