Register for your free account! | Forgot your password?

Go Back   elitepvpers › Popular Games › Silkroad Online › SRO Coding Corner
You last visited: Today at 00:07

  • Please register to post and access all features, it's quick, easy and FREE!

Advertisement



[Dev] vSRO Filter Architecture & Packet Handling

Discussion on [Dev] vSRO Filter Architecture & Packet Handling within the SRO Coding Corner forum part of the Silkroad Online category.

Reply
 
Old   #1
 
elite*gold: 0
Join Date: Oct 2015
Posts: 7
Received Thanks: 1
[Dev] vSRO Filter Architecture & Packet Handling

Hello epvp community,

I've been studying Silkroad Online server architecture—specifically vSRO—and I am currently diving deep into the mechanics of custom security filters (similar in concept to early stFilter, bFilter, or modern proxy solutions).

Rather than looking for ready-made source code or leaked binaries, my primary goal is **educational and architectural understanding**. I want to properly learn how high-performance filters handle the pipeline between the client and `SR_ShardManager` / `SR_Client`.

### Core Topics I'm Focusing On:
1. **Network Layer & Proxy Architecture:**
- Best practices for socket handling (IOCP in C++ vs. modern .NET async pipelines).
- Minimizing latency during real-time packet inspection.
2. **Security & Protocol Handling:**
- Silkroad packet structure, handshakes, and Blowfish/Security token handling.
- Mitigating common exploit packets, packet spam, and malformed opcode injections.
3. **Client-Side Verification:**
- Establishing reliable heartbeats and basic integrity checks between the launcher/client hook and the proxy filter.

### What I Bring & What I'm Looking For:
I am actively coding and analyzing open implementations (such as SilkroadDoc/SilkroadSecurity libraries), but I’d love to connect with veterans who have built or maintained production filters.

If anyone is open to discussing architectural trade-offs, pointing out common design pitfalls, or sharing recommended reading/repos for network filtering in vSRO, I’d greatly appreciate your insights.

Feel free to share your thoughts here or reach out via PM / Discord if you're open to technical discussions.

Thanks for your time and respect for the dev scene!
MeDoSam is offline  
Thanks
1 User
Old   #2
 
Hamxy's Avatar
 
elite*gold: 0
Join Date: Apr 2022
Posts: 20
Received Thanks: 8
How much progress have you made on your project? It would be great if you could show a few visuals of your work. I’m just curious. I’ve been developing my own project for years, too.
Hamxy is offline  
Old   #3
 
elite*gold: 0
Join Date: May 2021
Posts: 119
Received Thanks: 26
hi bro
your approach is good, especially because you are focusing on understanding the architecture instead of looking for ready-made source code.

the main thing i would recommend is to think of the filter as a protocol-aware layer between the client and the server, rather than just a tcp relay.

a useful architecture would look like this:

client
|
v
security filter / proxy
|
socket layer
session manager
packet framing
security layer
opcode validation
packet validation
rate limiting
heartbeat logging
|
v
gateway / agent / shard services

the first important part is the network layer.

on windows, iocp is a good choice when you want to handle many connections efficiently. the basic idea is to have asynchronous read and write operations associated with an iocp completion port instead of creating one blocking thread for every connection.

a simplified structure could be:

cpp
struct io_operation
{
overlapped overlapped{};
enum class type
{
read,
write,
accept
};

type operation_type;
wsabuf buffer;
};

the worker threads can then wait for completed operations:
cpp
dword bytes_transferred = 0;
ulong_ptr completion_key = 0;
lpoverlapped overlapped = nullptr;

bool result =
getqueuedcompletionstatus(
iocp,
&bytes_transferred,
&completion_key,
&overlapped,
infinite);


the important point is that the socket layer should not contain packet parsing or security logic. keep those responsibilities separated.

for example:

text
tcp socket
↓
receive buffer
↓
packet decoder
↓
security layer
↓
opcode parser
↓
validator
↓
forwarder


another important point is tcp framing.

you cannot assume that one recv call equals one silkroad packet.

for example, one packet may arrive in multiple tcp reads:

text
read 1
[packet part 1]

read 2:
[packet part 2]


or several packets may arrive together:

text
[packet a][packet b][packet c]


therefore the proxy needs a persistent receive buffer and a packet decoder that extracts complete frames from it.

a simple example:

cpp
class packet_buffer
{
public:

void append(
const uint8_t* data,
size_t size)
{
buffer.insert(
buffer.end(),
data,
data + size);
}

private:

std::vector<uint8_t> buffer;
};


after the framing layer, i would keep the silkroad security implementation separate.

for example:

cpp
class isecurity_layer
{
public:

virtual ~isecurity_layer() = default;

virtual bool process_incoming(
std::span<const uint8_t> input,
std::vector<uint8_t>& output) = 0;

virtual bool process_outgoing(
std::span<const uint8_t> input,
std::vector<uint8_t>& output) = 0;
};


then the silkroad-specific security implementation can implement this interface:

cpp
class silkroad_security :
public isecurity_layer
{
public:

bool process_incoming(
std::span<const uint8_t> input,
std::vector<uint8_t>& output) override
{
// security decoding
return true;
}

bool process_outgoing(
std::span<const uint8_t> input,
std::vector<uint8_t>& output) override
{
// security encoding
return true;
}
};


this separation is useful because the networking code does not need to know how the security algorithm works.

the next important part is the session state.

i would not allow every opcode at every stage of the connection.

for example:

cpp
enum class session_state
{
connected,
handshake,
authenticated,
in_game
};


then the filter can decide whether an opcode is valid for the current state:

cpp
bool is_opcode_allowed(
session_state state,
uint16_t opcode)
{
switch (state)
{
case session_state::connected:
return false;

case session_state::handshake:
return true;

case session_state::authenticated:
return true;

case session_state::in_game:
return true;
}

return false;
}


the exact opcode rules should come from the protocol analysis for the specific version being used.

i would also keep an opcode registry instead of putting opcode checks everywhere:

cpp
struct opcode_rule
{
uint16_t opcode;
uint16_t min_size;
uint16_t max_size;
bool client_to_server;
};


then:

cpp
bool validate_packet(
const opcode_rule& rule,
uint16_t size,
bool client_to_server)
{
if (size < rule.min_size)
return false;

if (size > rule.max_size)
return false;

if (rule.client_to_server != client_to_server)
return false;

return true;
}


this allows the filter to check more than just the opcode.

for example:

text
opcode
packet size
direction
session state
field ranges
packet frequency
sequence
security state


rate limiting is another important part.

instead of applying one global limit, i would normally keep limits per opcode or per packet category:

cpp
struct rate_limiter
{
uint64_t window_start = 0;
uint32_t packets = 0;

bool allow(
uint32_t maximum,
uint64_t now)
{
if (now - window_start >= 1000)
{
window_start = now;
packets = 0;
}

if (packets >= maximum)
return false;

++packets;
return true;
}
};


the filter can then detect abnormal packet rates without mixing that logic with the socket implementation.

for malformed packets, i would also avoid immediately treating every unexpected packet as a ban condition.

a better model is to keep a security state or violation counter:

cpp
struct security_score
{
uint32_t violations = 0;

void add_violation()
{
++violations;
}

bool should_disconnect() const
{
return violations >= 3;
}
};


this makes it easier to deal with protocol errors without creating unnecessary false positives.

for logging, i would record at least:

text
timestamp
session id
direction
opcode
packet size
session state
action
reason


for example:
text
session=1042
direction=c2s
opcode=0x1234
size=48
state=authenticated
action=forward


and for a rejected packet:

text
session=1042
direction=c2s
opcode=0x1234
size=4096
maximum=128
action=drop
reason=invalid_size


this becomes especially useful when analyzing disconnects, login errors, or unexpected server behavior.

for the client-side verification part, i would use a session-based challenge rather than relying only on a simple ping/pong.

the general flow can be:

text
client
|
| hello
v
filter
|
| challenge / nonce
v
client
|
| response
v
filter
|
v
session established


the response should be tied to the current session and challenge so that an old response cannot simply be reused.

a heartbeat can then be maintained independently:

cpp
struct heartbeat
{
uint32_t sequence;
uint64_t timestamp;
};


and the session can store:

cpp
struct client_security_context
{
uint64_t last_heartbeat;
uint32_t last_sequence;
bool authenticated;
};


the important architectural rule is to keep these components independent:

text
network
↓
session
↓
security
↓
framing
↓
opcode parsing
↓
validation
↓
rate limiting
↓
forwarding


for a vSRO-based project, i would also avoid assuming that every component communicates directly with the client. the exact path should be confirmed from the server configuration, packet captures, binaries, and the behavior of the specific client/server version.

for example, depending on the architecture, the path may be closer to:

text
client
↓
filter
↓
gateway / agent
↓
shard services


rather than directly connecting the client to every backend service.

i would therefore build the project incrementally:

text
1. tcp proxy
2. connection/session manager
3. packet framing
4. security layer
5. opcode registry
6. packet validation
7. state machine
8. rate limiting
9. heartbeat
10. client integrity checks
11. logging and packet capture
12. performance optimization with iocp


this approach makes debugging much easier because every layer has a clear responsibility.

the most important thing is not to start by writing a large filter and guessing the protocol. first establish the packet flow and security state for the exact version, then implement each layer around the observed protocol behavior.

that also makes it easier to compare different implementations such as stfilter-style filters, bfilter-style architectures, and proxy-based designs without depending on leaked source code or prebuilt binaries.

if you get stuck → on anything → ↓
←←←←←←←←←← ← ↓
↓
↓
don't hesitate to ask.
romio100 is offline  
Old   #4
 
B1Q's Avatar
 
elite*gold: 350
Join Date: Aug 2015
Posts: 2,046
Received Thanks: 1,203
start an emulator project brother you will learn 10x more
B1Q is offline  
Reply


Similar Threads Similar Threads
[Release] Z-Filter | Packet Filter | vSro Files | Anti Exploit | Beta
10/28/2021 - SRO PServer Guides & Releases - 69 Replies
Z-FILTER http://i.epvpimg.com/0U0idab.pnghttp://i.epvpimg. com/eJk0dab.png http://i.epvpimg.com/txrydab.png http://i.epvpimg.com/0w5dcab.png http://i.epvpimg.com/MGqGgab.png
[Question]New way of packet handling?
01/26/2011 - CO2 Private Server - 16 Replies
I have been thinking, and to me it seems that a lot of the packet handlers (especially LOTF) are pretty large. Would it take up more resources to do something more dynamic such as the following? This is just an example obviously. But the main concept is there. The void would be your PacketID, the Arg1 would be your data. And it simply calls the method immediately rather then going through a case? class Program { static void Main(string args) { string...



All times are GMT +1. The time now is 00:07.


Powered by vBulletin®
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
SEO by vBSEO ©2011, Crawlability, Inc.

Support | Contact Us | FAQ | Advertising | Privacy Policy | Terms of Service | Abuse
Copyright ©2026 elitepvpers All Rights Reserved.