How v2rayN Handles Profile Groups and Implements Server Switching

v2rayN stores every server definition as a ProfileItem, implements profile groups through the ProtocolExtra property containing GroupType metadata and comma-separated child IDs, and executes server switching via a reactive event pipeline that updates the global configuration index and triggers an application reload.

v2rayN manages proxy configurations through a sophisticated grouping system that allows users to organize servers hierarchically. The application stores each server or group as a ProfileItem object, with specialized logic in GroupProfileManager handling the validation and expansion of nested group structures. Understanding how v2rayN handles profile groups and implements server switching reveals a clean separation between UI interactions and core configuration management in the 2dust/v2rayN repository.

Understanding v2rayN Profile Groups

The ProfileItem Foundation

Every server definition in v2rayN is stored as a ProfileItem. This base structure contains connection parameters, protocol settings, and identification fields that uniquely define a single proxy endpoint or container.

Group Metadata in ProtocolExtra

A profile group is technically a ProfileItem whose ProtocolExtra property contains specific metadata:

  • GroupType: Identifies this item as a container rather than a concrete server
  • ChildItems: A comma-separated string of profile IDs representing the group's members

This design allows v2rayN to treat groups and individual servers polymorphically while maintaining the ability to distinguish containers from endpoints.

GroupProfileManager: Core Group Logic

The GroupProfileManager class in v2rayN/ServiceLib/Manager/GroupProfileManager.cs centralizes all group-related operations, implementing three critical responsibilities:

Cyclic Reference Detection

To prevent infinite recursion during group expansion, the HasCycle method (lines 5-15) implements a depth-first traversal with a visited set and current path stack. If the method encounters an ID already present in the traversal stack, it reports a cyclic reference error, safeguarding the application against malformed group definitions.

Child Profile Resolution

The GetChildProfileItemsByProtocolExtra method (lines 72-90) flattens a group into concrete server items. This method:

  1. Parses the comma-separated ChildItems string from ProtocolExtra
  2. Retrieves full ProfileItem objects from AppManager
  3. Preserves the ordering defined by the group
  4. Returns a flat List<ProfileItem> ready for core configuration

Descendant Collection

For statistics and bulk operations, GetAllChildProfileItems (lines 46-68) performs a depth-first walk through nested group structures. The method recurses only when encountering child items where ConfigType.IsGroupType() returns true, ensuring complete traversal of arbitrarily deep hierarchies.

The v2rayN Server Switching Pipeline

v2rayN implements server switching through a reactive event-driven pipeline spanning multiple ViewModels:

  1. UI Selection: In StatusBarViewModel.cs (lines 38-44), the status bar's server drop-down binds to StatusBarViewModel.SelectedServer using ReactiveUI bindings. When a user selects a different server from the ComboBox, the binding triggers ServerSelectedChanged.

  2. Event Publication: The selection handler publishes AppEvents.SetDefaultServerRequested with the selected server's ID (lines 30-45). This decouples the UI from configuration logic.

  3. Event Handling: ProfilesViewModel subscribes to this event (lines 68-72). Upon receiving the event, it invokes SetDefaultServer(string) to process the request.

  4. Configuration Update: The SetDefaultServer method (lines 303-325) validates the ID, loads the corresponding ProfileItem via AppManager, and updates the global configuration's _config.IndexId through ConfigHandler.SetDefaultServerIndex. After persisting the configuration, the method refreshes the UI and forces an application reload, causing the core process to restart with the new server selection.

Group Selection and Expansion

When a user selects a group rather than an individual server, the pipeline remains identical through step 4. However, the downstream CoreConfigHandler calls GroupProfileManager.GetChildProfileItems to expand the group before constructing the core configuration.

This expansion:

  • Validates the group structure using HasCycle
  • Resolves all child profiles recursively
  • Flattens the hierarchy into a concrete server list
  • Passes the merged list to the core process

The core effectively "switches" to a group by receiving the expanded member list rather than a single server definition.

Practical Implementation Examples

Resolving Group Children Programmatically

When activating a group from code:

// Inside CoreConfigHandler or components needing actual servers
var profile = await AppManager.Instance.GetProfileItem(selectedId);
if (profile.ConfigType.IsGroupType())
{
    // Expand the group recursively with cycle checking
    var (children, _) = await GroupProfileManager.GetChildProfileItems(profile);
    // children now contains a flat List<ProfileItem>
}

Triggering Server Switches via Events

Components can programmatically switch servers using the same event pipeline as the UI:

public async Task SwitchTo(string indexId)
{
    // Publish the event that ProfilesViewModel listens for
    AppEvents.SetDefaultServerRequested.Publish(indexId);
    // ProfilesViewModel handles config update and core restart
}

Creating Groups in Code

To create a new profile group programmatically:

var group = new ProfileItem
{
    IndexId = Guid.NewGuid().ToString(),
    ConfigType = EConfigType.Xray,  // Must be a group-compatible type
    Remarks = "Production Servers",
    ProtocolExtra = new ProtocolExtraItem
    {
        GroupType = "Xray",
        ChildItems = "server-id-1,server-id-2,server-id-3",  // Comma-separated IDs
        Filter = null  // Optional sub-filter for dynamic children
    }
};
await ConfigHandler.AddProfile(_config, group);

Summary

  • ProfileItem objects store both individual servers and groups, with groups distinguished by ProtocolExtra metadata containing GroupType and comma-separated ChildItems.
  • GroupProfileManager in ServiceLib/Manager/GroupProfileManager.cs handles cycle detection via HasCycle, child resolution via GetChildProfileItemsByProtocolExtra, and descendant collection via GetAllChildProfileItems.
  • Server switching follows a reactive pipeline: StatusBarViewModel detects selection changes, publishes SetDefaultServerRequested, and ProfilesViewModel updates _config.IndexId through ConfigHandler before reloading the application.
  • Group expansion occurs downstream in CoreConfigHandler, which flattens selected groups into concrete server lists before core configuration generation.
  • Cyclic references are detected early through depth-first traversal with path tracking, preventing infinite recursion in nested group structures.

Frequently Asked Questions

How does v2rayN distinguish between a server and a group?

v2rayN stores both as ProfileItem objects. A group is identified by its ProtocolExtra property containing a GroupType value and a ChildItems string with comma-separated member IDs. Individual servers lack this metadata. The ConfigType.IsGroupType() method provides runtime type checking to differentiate containers from concrete endpoints.

What prevents circular group references in v2rayN?

The GroupProfileManager.HasCycle method implements cycle detection using depth-first search with a visited set and recursion stack. Located at lines 5-15 of GroupProfileManager.cs, this method tracks the current traversal path and reports an error if it encounters an ID already present in the stack, blocking self-referential or mutually recursive group definitions before they cause stack overflow errors.

How does selecting a group differ from selecting a server in the UI?

The UI publishes the same SetDefaultServerRequested event for both selections. However, when CoreConfigHandler processes a group ID, it calls GroupProfileManager.GetChildProfileItems to expand the group into its constituent servers. The core receives the flattened list rather than a single endpoint, effectively treating the selection as a composite configuration.

Where is the active server index stored in v2rayN?

The active server ID is stored in _config.IndexId, persisted through ConfigHandler.SetDefaultServerIndex. When ProfilesViewModel.SetDefaultServer (lines 303-325) executes, it updates this field, saves the configuration to disk, and triggers an application reload to initialize the core process with the new server or expanded group configuration.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →