How Routing Rules Are Processed and Applied in v2rayN for V2Ray and sing-box Cores
v2rayN processes routing rules by loading a RoutingItem from SQLite, converting its JSON RuleSet into core-specific configurations via GenRouting methods in V2rayRoutingService or SingboxRoutingService, and appending the rules to the final config.json before starting the core process.
Understanding how routing rules are processed and applied in v2rayN is essential for customizing traffic splitting between the V2Ray and sing-box cores. The application stores routing policies in a SQLite database, then transforms these abstract rules into concrete JSON configurations that each proxy core understands. This article examines the complete pipeline from data storage to runtime application, referencing the actual source code in the 2dust/v2rayN repository.
How v2rayN Stores and Loads Routing Rules
v2rayN persists routing configurations in the RoutingItem model, which maps to a SQLite table. Each record contains a RuleSet field storing a JSON array of RulesItem objects that define domain, IP, port, and protocol matching criteria.
When a user starts or reloads a profile, the AppManager invokes ConfigHandler.GetDefaultRouting to retrieve the active routing item from the database. This method deserializes the JSON RuleSet into List<RulesItem> and returns the complete RoutingItem object.
// From ConfigHandler.cs – GetDefaultRouting (line ~1988)
public static RoutingItem GetDefaultRouting(Config config)
{
var routingItem = SQLiteHelper.Instance.Table<RoutingItem>()
.FirstOrDefault(t => t.Id == config.RoutingBasicItem.RoutingIndexId);
if (routingItem != null && routingItem.RuleSet.IsNotEmpty())
{
routingItem.Rules = JsonUtils.Deserialize<List<RulesItem>>(routingItem.RuleSet);
}
return routingItem ?? new RoutingItem();
}
The loaded RoutingItem is then packaged into a CoreConfigContext object by CoreConfigHandler.BuildContext. This context carries the routing configuration—along with the selected profile, DNS items, and proxy mappings—to the core-specific generators.
The Routing Rule Processing Pipeline
The transformation from abstract RulesItem objects to executable core configuration follows a standardized workflow, regardless of whether the target is V2Ray or sing-box. The CoreConfigHandler.BuildCoreConfig method selects the appropriate service—CoreConfigV2rayService or CoreConfigSingboxService—based on Config.CoreInfo.CoreType.
Step 1: Initialize the Generator
Each core service implements a GenRouting method that constructs the routing section of the final JSON configuration. For V2Ray, this populates _coreConfig.routing; for sing-box, it populates _coreConfig.route.
Step 2: Merge Global and User Rules
The generator first applies global basic routing settings from _config.RoutingBasicItem, then processes the user routing item carried in the CoreConfigContext. If the routing item defines a DomainStrategy, it overrides the global setting in _coreConfig.routing.domainStrategy (V2Ray) or route.default_domain_resolver.strategy (sing-box).
Step 3: Convert Individual Rules
Each enabled RulesItem in the RuleSet array is transformed into a core-specific rule object:
- V2Ray: Converted to
RulesItem4Raywithtype = "field"and optionaldomain,ip,port,network,inboundTag,protocol, andbalancerTagfields. - sing-box: Converted to
Rule4Sboxobjects that separate domain rules (geosite,domain_regex,domain_suffix,domain_keyword) from IP rules (geoip,ip_cidr), process rules (process_name,process_path), and port rules.
Step 4: Handle Balancers and Final Rules
For V2Ray, if balancers are defined, the generator injects balancer tags into matching rules and populates the balancers section. Both generators append a final fallback rule that directs unmatched traffic to the proxy tag (or a specific outbound), ensuring no traffic leaks outside the configured routes.
// V2Ray final rule construction (from V2rayRoutingService.cs)
private void BuildFinalRule()
{
var finalRule = new RulesItem4Ray
{
type = "field",
outboundTag = Global.ProxyTag,
port = "0-65535"
};
_coreConfig.routing.rules.Add(finalRule);
}
Core-Specific Implementation Differences
While the high-level workflow is consistent, the internal representations diverge significantly between V2Ray and sing-box to accommodate each core's native configuration schema.
Domain and IP Matching
V2Ray uses a unified RulesItem4Ray with arrays for domain and ip that can contain GeoSite/GeoIP tags (e.g., geosite:cn), full domains, or CIDR ranges. sing-box splits these into distinct rule types: domain rules use geosite, domain_suffix, domain_keyword, or domain_regex, while IP rules use geoip or ip_cidr.
Process-Based Routing
Process name matching is handled differently due to core capabilities:
- V2Ray: Only supported when using the Xray core. The
V2rayRoutingServicechecks the core type and clears theprocessfield for standard V2Ray. - sing-box: Fully supports process matching via
process_nameandprocess_pathfields, converting eachRulesItem.Processentry into the appropriate sing-box rule attribute.
DNS and Resolver Integration
V2Ray does not support per-rule DNS settings in the routing section; DNS rules (RuleType == DNS) are filtered out during generation. sing-box explicitly adds DNS hijack rules (action = "hijack-dns") and can insert resolve rules when custom domain resolution is required, leveraging sing-box's default_domain_resolver strategy.
Balancer Architecture
V2Ray's routing supports explicit load balancers defined in a separate balancers array, with rules referencing them via balancerTag. sing-box handles load balancing internally through its outbound selection mechanism, so v2rayN's sing-box generator does not produce a separate balancer section; instead, it relies on the core's built-in routing logic.
Programmatically Creating and Applying Routing Rules
Developers can bypass the UI to create routing items and trigger configuration generation using the same internal APIs that v2rayN uses.
Creating a Routing Item
The following example demonstrates creating a routing item that blocks advertising domains and routes private IPs to direct connection:
using System;
using System.Collections.Generic;
using System.Text.Json;
using ServiceLib.Models;
using ServiceLib.Handler;
// Define rules that block ads and bypass private networks
var rules = new List<RulesItem>
{
new RulesItem
{
Enabled = true,
RuleType = ERuleType.Domain,
OutboundTag = "block",
Domain = new[] { "geosite:category-ads-all", "geosite:google-ads" }
},
new RulesItem
{
Enabled = true,
RuleType = ERuleType.Ip,
OutboundTag = "direct",
Ip = new[] { "geoip:private", "192.168.0.0/16", "10.0.0.0/8" }
}
};
// Create the routing item
var routingItem = new RoutingItem
{
Id = Guid.NewGuid().ToString(),
Remarks = "Custom Ad Block and Private Bypass",
RuleSet = JsonSerializer.Serialize(rules),
DomainStrategy = Global.IPIfNonMatch,
IsActive = true,
Sort = 1
};
// Persist to SQLite database
var config = await ConfigHandler.LoadConfig();
await ConfigHandler.SaveRoutingItem(config, routingItem);
Generating Core Configuration
Once the routing item is saved, you can generate the core-specific configuration file that incorporates these rules:
using ServiceLib.Services.CoreConfig;
using ServiceLib.Handler;
using ServiceLib.Manager;
// Load current configuration and core information
var config = await ConfigHandler.LoadConfig();
var coreInfo = CoreInfoManager.Instance.GetCoreInfo()
.First(ci => ci.CoreType == ECoreType.v2ray); // or ECoreType.sing_box
// Build the context that carries the routing item
var context = await CoreConfigHandler.BuildContext(config, coreInfo);
// Instantiate the appropriate generator
ICoreConfigService generator = coreInfo.CoreType switch
{
ECoreType.v2ray => new CoreConfigV2rayService(context),
ECoreType.sing_box => new CoreConfigSingboxService(context),
_ => throw new NotSupportedException($"Core type {coreInfo.CoreType} not supported")
};
// Generate the configuration file (writes to config.json)
await generator.BuildConfigFileAsync();
This code path mirrors the execution flow triggered by the Start button in MainWindowViewModel.StartCmd, allowing automated testing or custom tooling around v2rayN's routing engine.
Summary
- v2rayN stores routing policies in SQLite as
RoutingItemobjects containing a JSONRuleSetarray ofRulesItemdefinitions. - The routing pipeline loads the active routing item via
ConfigHandler.GetDefaultRouting, packages it into aCoreConfigContext, and delegates to core-specific generators. - V2Ray and sing-box use distinct internal representations: V2Ray employs unified
fieldtype rules withdomainandiparrays, while sing-box separates concerns into distinct rule types withgeosite,geoip,domain_suffix, andprocess_namefields. - Process-based routing is supported in sing-box natively, but only available for V2Ray when using the Xray core.
- Balancers are implemented as separate
balancerssections in V2Ray, while sing-box handles load balancing internally without explicit balancer tags. - Developers can programmatically create and apply routing rules using
ConfigHandler.SaveRoutingItemand theCoreConfigV2rayServiceorCoreConfigSingboxServicegenerators.
Frequently Asked Questions
How does v2rayN store custom routing rules internally?
v2rayN persists routing configurations in a SQLite database using the RoutingItem model defined in ServiceLib/Models/RoutingItem.cs. Each routing item contains a RuleSet field that stores a serialized JSON array of RulesItem objects. When the application loads a configuration, ConfigHandler.GetDefaultRouting deserializes this JSON into a List<RulesItem> that the core generators consume.
What is the difference between V2Ray and sing-box routing rule formats?
V2Ray uses a unified rule structure with type = "field" and optional arrays for domain, ip, port, and protocol within a single RulesItem4Ray object. sing-box, conversely, separates matching criteria into distinct rule types: domain rules use geosite, domain_suffix, domain_keyword, or domain_regex; IP rules use geoip or ip_cidr; and process rules use process_name or process_path. This structural difference is handled internally by V2rayRoutingService.GenRoutingUserRule and SingboxRoutingService.GenRoutingUserRule.
Can I use process-based routing rules with the standard V2Ray core?
Process-based routing is only supported when using the Xray core variant, not the standard V2Ray core. In V2rayRoutingService.cs, the generator checks the core type and clears the process field for non-Xray cores to prevent configuration errors. If you require process-level traffic splitting, switch to the Xray core or use sing-box, which natively supports process_name and process_path matching without core-specific limitations.
How are routing balancers implemented differently between V2Ray and sing-box?
V2Ray implements balancers as a separate balancers array in the routing configuration, where each balancer defines multiple outbound tags and a selection strategy. Routing rules then reference these balancers via a balancerTag field instead of a direct outboundTag. sing-box does not expose a separate balancer section; instead, load balancing is handled internally by the core through outbound selection mechanisms. When v2rayN generates sing-box configurations, it maps the intended load balancing behavior directly to the outbound field without creating explicit balancer objects.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →