Compare commits
18
Commits
3.6.0测试版1
...
61868fe93e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61868fe93e
|
||
|
|
9cea5ed493
|
||
|
|
e0296e0ccd
|
||
|
|
9cea74943c
|
||
|
|
35c430114e
|
||
|
|
6c7017bc75
|
||
|
|
041cc31c5f
|
||
|
|
cc811eddfe
|
||
|
|
984031c6e2
|
||
|
|
75ff49a9d1
|
||
|
|
d6ac0da75d
|
||
|
|
455139dfe9
|
||
|
|
f0efa6d041 | ||
|
|
314f90b5f3 | ||
|
|
053450429c | ||
|
|
8dcdb08755 | ||
|
|
ba61d3b5ae | ||
|
|
74e4360d71 |
@@ -0,0 +1,277 @@
|
|||||||
|
---
|
||||||
|
name: shadcn
|
||||||
|
description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI, including chat interfaces. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
|
||||||
|
user-invocable: false
|
||||||
|
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
||||||
|
---
|
||||||
|
|
||||||
|
# shadcn/ui
|
||||||
|
|
||||||
|
A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
## Current Project Context
|
||||||
|
|
||||||
|
```json
|
||||||
|
!`npx shadcn@latest info --json`
|
||||||
|
```
|
||||||
|
|
||||||
|
The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
|
||||||
|
|
||||||
|
## Principles
|
||||||
|
|
||||||
|
1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
|
||||||
|
2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
|
||||||
|
3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
|
||||||
|
4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
|
||||||
|
|
||||||
|
## Critical Rules
|
||||||
|
|
||||||
|
These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
|
||||||
|
|
||||||
|
### Styling & Tailwind → [styling.md](./rules/styling.md)
|
||||||
|
|
||||||
|
- **`className` for layout, not styling.** Never override component colors or typography.
|
||||||
|
- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
|
||||||
|
- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
|
||||||
|
- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
|
||||||
|
- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
|
||||||
|
- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
|
||||||
|
|
||||||
|
### Forms & Inputs → [forms.md](./rules/forms.md)
|
||||||
|
|
||||||
|
- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
|
||||||
|
- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
|
||||||
|
- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
|
||||||
|
- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
|
||||||
|
- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
|
||||||
|
- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
|
||||||
|
|
||||||
|
### Component Structure → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
|
||||||
|
- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
|
||||||
|
- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||||
|
- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
|
||||||
|
- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
|
||||||
|
- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
|
||||||
|
- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
|
||||||
|
|
||||||
|
### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
|
||||||
|
|
||||||
|
- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
|
||||||
|
- **Callouts use `Alert`.** Don't build custom styled divs.
|
||||||
|
- **Empty states use `Empty`.** Don't build custom empty state markup.
|
||||||
|
- **Toast follows the project base.** Use `toast` from the `toast` component for
|
||||||
|
Base UI projects. Use `toast()` from `sonner` for Radix and React Aria
|
||||||
|
projects.
|
||||||
|
- **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
|
||||||
|
- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
|
||||||
|
- **Use `Badge`** instead of custom styled spans.
|
||||||
|
|
||||||
|
### Icons → [icons.md](./rules/icons.md)
|
||||||
|
|
||||||
|
- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
|
||||||
|
- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
|
||||||
|
- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
|
||||||
|
|
||||||
|
### Chat & Messaging → [chat.md](./rules/chat.md)
|
||||||
|
|
||||||
|
- **Chat UI composes the chat primitives.** Conversations use `MessageScroller`, rows use `Message`, surfaces use `Bubble`. Never hand-rolled bubble `div`s or a raw scroll container.
|
||||||
|
- **`MessageScroller` owns scroll behavior.** Streaming follow, anchoring, and jump-to-latest (`MessageScrollerButton`) are built in. Don't write a `useStickToBottom`/`ResizeObserver` hook.
|
||||||
|
- **Attachments use `Attachment`; system notes and dividers use `Marker`.** Not `Item` cards or `Separator` + a label.
|
||||||
|
|
||||||
|
### CLI
|
||||||
|
|
||||||
|
- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode <code>`, `preset url <code>`, or `preset open <code>`. For project-aware preset detection, use `npx shadcn@latest preset resolve`.
|
||||||
|
- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Form layout: FieldGroup + Field, not div + Label.
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
|
||||||
|
// Validation: data-invalid on Field, aria-invalid on the control.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel>Email</FieldLabel>
|
||||||
|
<Input aria-invalid />
|
||||||
|
<FieldDescription>Invalid email.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Icons in buttons: data-icon, no sizing classes.
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
// Spacing: gap-*, not space-y-*.
|
||||||
|
<div className="flex flex-col gap-4"> // correct
|
||||||
|
<div className="space-y-4"> // wrong
|
||||||
|
|
||||||
|
// Equal dimensions: size-*, not w-* h-*.
|
||||||
|
<Avatar className="size-10"> // correct
|
||||||
|
<Avatar className="w-10 h-10"> // wrong
|
||||||
|
|
||||||
|
// Status colors: Badge variants or semantic tokens, not raw colors.
|
||||||
|
<Badge variant="secondary">+20.1%</Badge> // correct
|
||||||
|
<span className="text-emerald-600">+20.1%</span> // wrong
|
||||||
|
```
|
||||||
|
|
||||||
|
## Component Selection
|
||||||
|
|
||||||
|
| Need | Use |
|
||||||
|
| -------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||||
|
| Button/action | `Button` with appropriate variant |
|
||||||
|
| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
|
||||||
|
| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
|
||||||
|
| Data display | `Table`, `Card`, `Badge`, `Avatar` |
|
||||||
|
| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
|
||||||
|
| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
|
||||||
|
| Feedback | `toast` (Base UI), `sonner` (Radix/Aria), `Alert`, `Progress`, `Skeleton`, `Spinner` |
|
||||||
|
| Command palette | `Command` inside `Dialog` |
|
||||||
|
| Charts | `Chart` (wraps Recharts) |
|
||||||
|
| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
|
||||||
|
| Empty states | `Empty` |
|
||||||
|
| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
|
||||||
|
| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
|
||||||
|
| Chat / conversation UI | `MessageScroller`, `Message`, `Bubble`, `Attachment`, `Marker` |
|
||||||
|
|
||||||
|
## Key Fields
|
||||||
|
|
||||||
|
The injected project context contains these key fields:
|
||||||
|
|
||||||
|
- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
|
||||||
|
- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
|
||||||
|
- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
|
||||||
|
- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
|
||||||
|
- **`style`** → component visual treatment (e.g. `nova`, `vega`).
|
||||||
|
- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
|
||||||
|
- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
|
||||||
|
- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
|
||||||
|
- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
|
||||||
|
- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
|
||||||
|
- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information.
|
||||||
|
|
||||||
|
See [cli.md — `info` command](./cli.md) for the full field reference.
|
||||||
|
|
||||||
|
## Component Docs, Examples, and Usage
|
||||||
|
|
||||||
|
Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
```
|
||||||
|
|
||||||
|
**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
|
||||||
|
2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
|
||||||
|
3. **Find components** — `npx shadcn@latest search`.
|
||||||
|
4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
|
||||||
|
5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
|
||||||
|
6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
|
||||||
|
7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
|
||||||
|
8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, `owner/repo`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
|
||||||
|
9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
|
||||||
|
- **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values.
|
||||||
|
- **Inspect incoming preset**: `npx shadcn@latest preset decode <code>`. Use `preset url <code>` or `preset open <code>` to share or open the preset builder.
|
||||||
|
- **Overwrite**: `npx shadcn@latest apply <code>`. Overwrites detected components, fonts, and CSS variables.
|
||||||
|
- **Partial**: `npx shadcn@latest apply <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
|
||||||
|
- **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
|
||||||
|
- **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
|
||||||
|
- **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||||
|
|
||||||
|
## Updating Components
|
||||||
|
|
||||||
|
When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
|
||||||
|
|
||||||
|
1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
|
||||||
|
2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
|
||||||
|
3. Decide per file based on the diff:
|
||||||
|
- No local changes → safe to overwrite.
|
||||||
|
- Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
|
||||||
|
- User says "just update everything" → use `--overwrite`, but confirm first.
|
||||||
|
4. **Never use `--overwrite` without the user's explicit approval.**
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create a new project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova
|
||||||
|
npx shadcn@latest init --name my-app --preset a2r6bw --template vite
|
||||||
|
|
||||||
|
# Create a monorepo project.
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --monorepo
|
||||||
|
npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
|
||||||
|
|
||||||
|
# Initialize existing project.
|
||||||
|
npx shadcn@latest init --preset base-nova
|
||||||
|
npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
|
||||||
|
|
||||||
|
# Apply a preset to an existing project.
|
||||||
|
npx shadcn@latest apply a2r6bw
|
||||||
|
npx shadcn@latest apply a2r6bw --only theme
|
||||||
|
npx shadcn@latest apply a2r6bw --only font
|
||||||
|
npx shadcn@latest apply a2r6bw --only theme,font
|
||||||
|
|
||||||
|
# Inspect preset codes and project preset state.
|
||||||
|
npx shadcn@latest preset decode a2r6bw
|
||||||
|
npx shadcn@latest preset url a2r6bw
|
||||||
|
npx shadcn@latest preset open a2r6bw
|
||||||
|
npx shadcn@latest preset resolve
|
||||||
|
npx shadcn@latest preset resolve --json
|
||||||
|
|
||||||
|
# Add components.
|
||||||
|
npx shadcn@latest add button card dialog
|
||||||
|
npx shadcn@latest add @magicui/shimmer-button
|
||||||
|
npx shadcn@latest add owner/repo/item
|
||||||
|
npx shadcn@latest add --all
|
||||||
|
|
||||||
|
# Preview changes before adding/updating.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
npx shadcn@latest add @acme/form --view button.tsx
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
|
||||||
|
# Search registries.
|
||||||
|
npx shadcn@latest search @shadcn -q "sidebar"
|
||||||
|
npx shadcn@latest search @tailark -q "stats"
|
||||||
|
npx shadcn@latest search owner/repo -q "login"
|
||||||
|
npx shadcn@latest search # all configured registries
|
||||||
|
npx shadcn@latest search @shadcn -q "menu" -t ui # filter by item type
|
||||||
|
|
||||||
|
# Get component docs and example URLs.
|
||||||
|
npx shadcn@latest docs button dialog select
|
||||||
|
|
||||||
|
# View registry item details (for items not yet installed).
|
||||||
|
npx shadcn@latest view @shadcn/button
|
||||||
|
npx shadcn@latest view owner/repo/item
|
||||||
|
```
|
||||||
|
|
||||||
|
**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
|
||||||
|
**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
|
||||||
|
**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).
|
||||||
|
|
||||||
|
## Detailed References
|
||||||
|
|
||||||
|
- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
|
||||||
|
- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
|
||||||
|
- [rules/chat.md](./rules/chat.md) — MessageScroller, Message, Bubble, Attachment, Marker; streaming, anchoring, jump-to-latest
|
||||||
|
- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
|
||||||
|
- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
|
||||||
|
- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
|
||||||
|
- [cli.md](./cli.md) — Commands, flags, presets, templates
|
||||||
|
- [registry.md](./registry.md) — Authoring source registries, `include`, item definitions, dependencies, GitHub registry rules
|
||||||
|
- [customization.md](./customization.md) — Theming, CSS variables, extending components
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "shadcn/ui"
|
||||||
|
short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
|
||||||
|
icon_small: "./assets/shadcn-small.png"
|
||||||
|
icon_large: "./assets/shadcn.png"
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,290 @@
|
|||||||
|
# shadcn CLI Reference
|
||||||
|
|
||||||
|
Configuration is read from `components.json`.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
|
||||||
|
|
||||||
|
> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
|
||||||
|
- Templates: next, vite, start, react-router, astro
|
||||||
|
- Presets: named, code, URL formats and fields
|
||||||
|
- Switching presets
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `init` — Initialize or create a project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest init [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------------- | ----- | --------------------------------------------------------- | ------- |
|
||||||
|
| `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
|
||||||
|
| `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `true` |
|
||||||
|
| `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
|
||||||
|
| `--force` | `-f` | Force overwrite existing configuration | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--name <name>` | `-n` | Name for new project | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--rtl` | | Enable RTL support | — |
|
||||||
|
| `--reinstall` | | Re-install existing UI components | `false` |
|
||||||
|
| `--monorepo` | | Scaffold a monorepo project | — |
|
||||||
|
| `--no-monorepo` | | Skip the monorepo prompt | — |
|
||||||
|
|
||||||
|
`npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
|
||||||
|
|
||||||
|
### `apply` — Apply a preset to an existing project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest apply [preset] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------------- | ----- | ------------------------------------------ | ------- |
|
||||||
|
| `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
|
||||||
|
`[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
|
||||||
|
If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
|
||||||
|
|
||||||
|
### `add` — Add components
|
||||||
|
|
||||||
|
> **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add [components...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Accepts component names, registry-prefixed names (`@magicui/shimmer-button`),
|
||||||
|
GitHub item addresses (`owner/repo/item`), URLs, or local paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||||
|
| `--yes` | `-y` | Skip confirmation prompt | `false` |
|
||||||
|
| `--overwrite` | `-o` | Overwrite existing files | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
| `--all` | `-a` | Add all available components | `false` |
|
||||||
|
| `--path <path>` | `-p` | Target path for the component | — |
|
||||||
|
| `--silent` | `-s` | Mute output | `false` |
|
||||||
|
| `--dry-run` | | Preview all changes without writing files | `false` |
|
||||||
|
| `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
| `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
|
||||||
|
|
||||||
|
#### Dry-Run Mode
|
||||||
|
|
||||||
|
Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Preview all changes.
|
||||||
|
npx shadcn@latest add button --dry-run
|
||||||
|
|
||||||
|
# Show diffs for all files (top 5).
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
|
||||||
|
# Show the diff for a specific file.
|
||||||
|
npx shadcn@latest add button --diff button.tsx
|
||||||
|
|
||||||
|
# Show contents for all files (top 5).
|
||||||
|
npx shadcn@latest add button --view
|
||||||
|
|
||||||
|
# Show the full content of a specific file.
|
||||||
|
npx shadcn@latest add button --view button.tsx
|
||||||
|
|
||||||
|
# Works with URLs too.
|
||||||
|
npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
|
||||||
|
|
||||||
|
# Works with public GitHub registries too.
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
|
||||||
|
# CSS diffs.
|
||||||
|
npx shadcn@latest add button --diff globals.css
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use dry-run:**
|
||||||
|
|
||||||
|
- When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
|
||||||
|
- Before overwriting existing components — use `--diff` to preview the changes first.
|
||||||
|
- When the user wants to inspect component source code without installing — use `--view`.
|
||||||
|
- When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
|
||||||
|
- When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
|
||||||
|
|
||||||
|
> **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
|
||||||
|
|
||||||
|
#### Smart Merge from Upstream
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
|
||||||
|
|
||||||
|
### `search` — Search registries
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest search [registries...] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Fuzzy search across registries. Also aliased as `npx shadcn@latest list`.
|
||||||
|
Supports namespaces (`@acme`), public GitHub registry sources (`owner/repo`),
|
||||||
|
and registry catalog URLs. Without `-q`, lists all items. When no registries are
|
||||||
|
passed, searches every registry configured in `components.json`.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------------- | ----- | ------------------------------------------------- | ------- |
|
||||||
|
| `--query <query>` | `-q` | Search query | — |
|
||||||
|
| `--type <type>` | `-t` | Filter by item type (e.g. `ui`, `block`, `hook`); comma-separated | — |
|
||||||
|
| `--limit <number>` | `-l` | Max items to display | `100` |
|
||||||
|
| `--offset <number>` | `-o` | Items to skip | `0` |
|
||||||
|
| `--json` | | Output as JSON | `false` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
### `view` — View item details
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest view <items...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays item info including file contents. Examples:
|
||||||
|
`npx shadcn@latest view @shadcn/button`,
|
||||||
|
`npx shadcn@latest view owner/repo/item`.
|
||||||
|
|
||||||
|
### `docs` — Get component documentation URLs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest docs <components...> [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
|
||||||
|
|
||||||
|
Example output for `npx shadcn@latest docs input button`:
|
||||||
|
|
||||||
|
```
|
||||||
|
base radix
|
||||||
|
|
||||||
|
input
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/input
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/input-example.tsx
|
||||||
|
|
||||||
|
button
|
||||||
|
docs https://ui.shadcn.com/docs/components/radix/button
|
||||||
|
examples https://raw.githubusercontent.com/.../examples/button-example.tsx
|
||||||
|
```
|
||||||
|
|
||||||
|
Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
|
||||||
|
|
||||||
|
### `diff` — Check for updates
|
||||||
|
|
||||||
|
Do not use this command. Use `npx shadcn@latest add --diff` instead.
|
||||||
|
|
||||||
|
### `info` — Project information
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest info [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ------------- | ----- | ----------------- | ------- |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
**Project Info fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------ |
|
||||||
|
| `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
|
||||||
|
| `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
|
||||||
|
| `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
|
||||||
|
| `isRSC` | `boolean` | Whether React Server Components are enabled |
|
||||||
|
| `isTsx` | `boolean` | Whether the project uses TypeScript |
|
||||||
|
| `tailwindVersion` | `string` | `"v3"` or `"v4"` |
|
||||||
|
| `tailwindConfigFile` | `string` | Path to the Tailwind config file |
|
||||||
|
| `tailwindCssFile` | `string` | Path to the global CSS file |
|
||||||
|
| `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
|
||||||
|
| `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
|
||||||
|
|
||||||
|
**Components.json fields:**
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
| -------------------- | --------- | ------------------------------------------------------------------------------------------ |
|
||||||
|
| `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
|
||||||
|
| `style` | `string` | Visual style (e.g. `nova`, `vega`) |
|
||||||
|
| `rsc` | `boolean` | RSC flag from config |
|
||||||
|
| `tsx` | `boolean` | TypeScript flag |
|
||||||
|
| `tailwind.config` | `string` | Tailwind config path |
|
||||||
|
| `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
|
||||||
|
| `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
|
||||||
|
| `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
|
||||||
|
| `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
|
||||||
|
| `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
|
||||||
|
| `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
|
||||||
|
| `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
|
||||||
|
| `resolvedPaths` | `object` | Absolute file-system paths for each alias |
|
||||||
|
| `registries` | `object` | Configured custom registries |
|
||||||
|
|
||||||
|
**Links fields:**
|
||||||
|
|
||||||
|
The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
|
||||||
|
|
||||||
|
### `build` — Build a custom registry
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest build [registry] [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
|
||||||
|
|
||||||
|
For authoring rules, `include`, item definitions, `registryDependencies`, and
|
||||||
|
GitHub registry behavior, see [registry.md](./registry.md).
|
||||||
|
|
||||||
|
| Flag | Short | Description | Default |
|
||||||
|
| ----------------- | ----- | ----------------- | ------------ |
|
||||||
|
| `--output <path>` | `-o` | Output directory | `./public/r` |
|
||||||
|
| `--cwd <cwd>` | `-c` | Working directory | current |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
| Value | Framework | Monorepo support |
|
||||||
|
| -------------- | -------------- | ---------------- |
|
||||||
|
| `next` | Next.js | Yes |
|
||||||
|
| `vite` | Vite | Yes |
|
||||||
|
| `start` | TanStack Start | Yes |
|
||||||
|
| `react-router` | React Router | Yes |
|
||||||
|
| `astro` | Astro | Yes |
|
||||||
|
| `laravel` | Laravel | No |
|
||||||
|
|
||||||
|
All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Presets
|
||||||
|
|
||||||
|
Three ways to specify a preset via `--preset`:
|
||||||
|
|
||||||
|
1. **Named:** `--preset nova` or `--preset lyra`
|
||||||
|
2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
|
||||||
|
3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
|
||||||
|
|
||||||
|
> **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
|
||||||
|
> Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
|
||||||
|
|
||||||
|
## Switching Presets
|
||||||
|
|
||||||
|
Ask the user first: **overwrite**, **merge**, or **skip** existing components?
|
||||||
|
|
||||||
|
- **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
|
||||||
|
- **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
|
||||||
|
- **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
|
||||||
|
|
||||||
|
Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# Customization & Theming
|
||||||
|
|
||||||
|
Components reference semantic CSS variable tokens. Change the variables to change every component.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- How it works (CSS variables → Tailwind utilities → components)
|
||||||
|
- Color variables and OKLCH format
|
||||||
|
- Dark mode setup
|
||||||
|
- Changing the theme (presets, CSS variables)
|
||||||
|
- Adding custom colors (Tailwind v3 and v4)
|
||||||
|
- Border radius
|
||||||
|
- Customizing components (variants, className, wrappers)
|
||||||
|
- Checking for updates
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
|
||||||
|
2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
|
||||||
|
3. Components use these utilities — changing a variable changes all components that reference it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Variables
|
||||||
|
|
||||||
|
Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
|
||||||
|
|
||||||
|
| Variable | Purpose |
|
||||||
|
| -------------------------------------------- | -------------------------------- |
|
||||||
|
| `--background` / `--foreground` | Page background and default text |
|
||||||
|
| `--card` / `--card-foreground` | Card surfaces |
|
||||||
|
| `--primary` / `--primary-foreground` | Primary buttons and actions |
|
||||||
|
| `--secondary` / `--secondary-foreground` | Secondary actions |
|
||||||
|
| `--muted` / `--muted-foreground` | Muted/disabled states |
|
||||||
|
| `--accent` / `--accent-foreground` | Hover and accent states |
|
||||||
|
| `--destructive` / `--destructive-foreground` | Error and destructive actions |
|
||||||
|
| `--border` | Default border color |
|
||||||
|
| `--input` | Form input borders |
|
||||||
|
| `--ring` | Focus ring color |
|
||||||
|
| `--chart-1` through `--chart-5` | Chart/data visualization |
|
||||||
|
| `--sidebar-*` | Sidebar-specific colors |
|
||||||
|
| `--surface` / `--surface-foreground` | Secondary surface |
|
||||||
|
|
||||||
|
Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dark Mode
|
||||||
|
|
||||||
|
Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ThemeProvider } from "next-themes"
|
||||||
|
|
||||||
|
<ThemeProvider attribute="class" defaultTheme="system" enableSystem>
|
||||||
|
{children}
|
||||||
|
</ThemeProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changing the Theme
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Apply a preset code from ui.shadcn.com.
|
||||||
|
npx shadcn@latest apply --preset a2r6bw
|
||||||
|
|
||||||
|
# Positional shorthand also works.
|
||||||
|
npx shadcn@latest apply a2r6bw
|
||||||
|
|
||||||
|
# Switch to a named preset and overwrite existing components.
|
||||||
|
npx shadcn@latest apply --preset nova
|
||||||
|
|
||||||
|
# Preserve existing components instead.
|
||||||
|
npx shadcn@latest init --preset nova --force --no-reinstall
|
||||||
|
|
||||||
|
# Use a custom theme URL.
|
||||||
|
npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
|
||||||
|
```
|
||||||
|
|
||||||
|
Or edit CSS variables directly in `globals.css`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Adding Custom Colors
|
||||||
|
|
||||||
|
Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 1. Define in the global CSS file. */
|
||||||
|
:root {
|
||||||
|
--warning: oklch(0.84 0.16 84);
|
||||||
|
--warning-foreground: oklch(0.28 0.07 46);
|
||||||
|
}
|
||||||
|
.dark {
|
||||||
|
--warning: oklch(0.41 0.11 46);
|
||||||
|
--warning-foreground: oklch(0.99 0.02 95);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```css
|
||||||
|
/* 2a. Register with Tailwind v4 (@theme inline). */
|
||||||
|
@theme inline {
|
||||||
|
--color-warning: var(--warning);
|
||||||
|
--color-warning-foreground: var(--warning-foreground);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// 2b. Register with Tailwind v3 (tailwind.config.js).
|
||||||
|
module.exports = {
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
warning: "oklch(var(--warning) / <alpha-value>)",
|
||||||
|
"warning-foreground":
|
||||||
|
"oklch(var(--warning-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// 3. Use in components.
|
||||||
|
<div className="bg-warning text-warning-foreground">Warning</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Border Radius
|
||||||
|
|
||||||
|
`--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Customizing Components
|
||||||
|
|
||||||
|
See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
|
||||||
|
|
||||||
|
Prefer these approaches in order:
|
||||||
|
|
||||||
|
### 1. Built-in variants
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
Click
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Tailwind classes via `className`
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="mx-auto max-w-md">...</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Add a new variant
|
||||||
|
|
||||||
|
Edit the component source to add a variant via `cva`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// components/ui/button.tsx
|
||||||
|
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Wrapper components
|
||||||
|
|
||||||
|
Compose shadcn/ui primitives into higher-level components:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
export function ConfirmDialog({ title, description, onConfirm, children }) {
|
||||||
|
return (
|
||||||
|
<AlertDialog>
|
||||||
|
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{title}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>{description}</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checking for Updates
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --diff
|
||||||
|
```
|
||||||
|
|
||||||
|
To preview exactly what would change before updating, use `--dry-run` and `--diff`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest add button --dry-run # see all affected files
|
||||||
|
npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
|
||||||
|
```
|
||||||
|
|
||||||
|
See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"skill_name": "shadcn",
|
||||||
|
"evals": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
|
||||||
|
"expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses FieldGroup and Field components for form layout instead of raw div with space-y",
|
||||||
|
"Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
|
||||||
|
"Uses data-invalid on Field and aria-invalid on the input control for validation states",
|
||||||
|
"Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
|
||||||
|
"No manual dark: color overrides"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
|
||||||
|
"expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Includes DialogTitle for accessibility (visible or with sr-only class)",
|
||||||
|
"Avatar component includes AvatarFallback",
|
||||||
|
"Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
|
||||||
|
"No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
|
||||||
|
"Uses tabler icons (@tabler/icons-react) instead of lucide-react",
|
||||||
|
"Uses asChild for custom triggers (radix preset)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
|
||||||
|
"expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
|
||||||
|
"Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
|
||||||
|
"Uses Badge component for percentage change instead of custom styled spans",
|
||||||
|
"Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
|
||||||
|
"Uses gap-* instead of space-y-* or space-x-* for spacing",
|
||||||
|
"Uses size-* when width and height are equal instead of separate w-* h-*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Build a chat conversation view: a scrollable thread of messages from two different people, each with an avatar, sender name, timestamp, and message bubble. A couple of messages include an image attachment and a PDF file attachment, and there's a 'Today' divider separating the days.",
|
||||||
|
"expected_output": "A React component composing MessageScroller, Message, Bubble, Attachment, and Marker from the registry instead of hand-rolled bubble/divider/attachment markup.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses MessageScroller (MessageScrollerProvider, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem) for the scrollable thread instead of a raw overflow-y-auto div or ScrollArea",
|
||||||
|
"Wraps each row in MessageScrollerItem inside MessageScrollerContent",
|
||||||
|
"Uses Message with MessageAvatar/MessageContent/MessageHeader for row layout instead of custom flex divs",
|
||||||
|
"Uses Bubble + BubbleContent for the message surface instead of a styled div with bg-muted/bg-primary",
|
||||||
|
"Uses Attachment (AttachmentMedia, AttachmentContent, AttachmentTitle, AttachmentDescription) for the file and image attachments instead of Item or a custom card",
|
||||||
|
"Uses Marker (variant=\"separator\") for the 'Today' divider instead of Separator plus a centered label",
|
||||||
|
"Uses semantic color tokens and gap-* spacing; no raw colors like bg-emerald-500 and no space-y-*",
|
||||||
|
"Includes \"use client\" when the component uses state or event handlers (isRSC)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"prompt": "Using shadcn/ui (base-nova preset, lucide icons), build a streaming AI chat UI. The assistant's reply streams in while it generates, the view auto-scrolls to follow the latest content but stops following if the user scrolls up to read earlier messages, a 'jump to latest' button appears when the user has scrolled away from the bottom, and a subtle 'thinking…' shimmer shows while the model is generating.",
|
||||||
|
"expected_output": "A React component that delegates scroll/anchor behavior to MessageScroller and uses MessageScrollerButton for jump-to-latest and the shimmer utility for the thinking indicator — no hand-rolled scroll logic or custom shimmer keyframes.",
|
||||||
|
"files": [],
|
||||||
|
"expectations": [
|
||||||
|
"Uses MessageScroller with MessageScrollerProvider (autoScroll) and scrollAnchor on message items for the stick-to-bottom/follow behavior instead of a custom useStickToBottom hook or ResizeObserver/scrollTop wiring",
|
||||||
|
"Uses MessageScrollerButton for the jump-to-latest control instead of a hand-built conditional button driven by manual scroll-position state",
|
||||||
|
"Uses the shimmer utility class for the 'thinking…' indicator instead of a custom @keyframes or bg-clip-text gradient animation",
|
||||||
|
"Wraps each message row in MessageScrollerItem inside MessageScrollerContent",
|
||||||
|
"Uses Message + Bubble + BubbleContent for the conversation rows instead of hand-rolled bubble divs",
|
||||||
|
"Uses semantic color tokens and gap-* spacing; includes \"use client\" (isRSC)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# shadcn MCP Server
|
||||||
|
|
||||||
|
The CLI includes an MCP server that lets AI assistants search, browse, view, and install items from registries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
shadcn mcp # start the MCP server (stdio)
|
||||||
|
shadcn mcp init # write config for your editor
|
||||||
|
```
|
||||||
|
|
||||||
|
Editor config files:
|
||||||
|
|
||||||
|
| Editor | Config file |
|
||||||
|
| ----------- | ------------------------------- |
|
||||||
|
| Claude Code | `.mcp.json` |
|
||||||
|
| Cursor | `.cursor/mcp.json` |
|
||||||
|
| VS Code | `.vscode/mcp.json` |
|
||||||
|
| OpenCode | `opencode.json` |
|
||||||
|
| Codex | `~/.codex/config.toml` (manual) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
> **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
|
||||||
|
|
||||||
|
### `shadcn:get_project_registries`
|
||||||
|
|
||||||
|
Returns registry names from `components.json`. Errors if no `components.json` exists.
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
### `shadcn:list_items_in_registries`
|
||||||
|
|
||||||
|
Lists all items from one or more registries. Registries can be configured
|
||||||
|
namespaces such as `@acme`, public GitHub sources such as `owner/repo`, or
|
||||||
|
registry catalog URLs. Omit `registries` to list from every registry configured
|
||||||
|
in `components.json`.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:search_items_in_registries`
|
||||||
|
|
||||||
|
Fuzzy search across registries. Registries can be configured namespaces, public
|
||||||
|
GitHub sources, or registry catalog URLs. Omit `registries` to search every
|
||||||
|
registry configured in `components.json` — e.g. "find me a hero" across all
|
||||||
|
configured registries.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `query` (string), `types` (string[], optional — e.g. `["ui", "block"]`), `limit` (number, optional, defaults to 100), `offset` (number, optional)
|
||||||
|
|
||||||
|
### `shadcn:view_items_in_registries`
|
||||||
|
|
||||||
|
View item details including full file contents.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g.
|
||||||
|
`["@shadcn/button", "@shadcn/card", "owner/repo/item"]`
|
||||||
|
|
||||||
|
### `shadcn:get_item_examples_from_registries`
|
||||||
|
|
||||||
|
Find usage examples and demos with source code. Omit `registries` to search
|
||||||
|
every registry configured in `components.json`.
|
||||||
|
|
||||||
|
**Input:** `registries` (string[], optional — omit for all configured), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
|
||||||
|
|
||||||
|
### `shadcn:get_add_command_for_items`
|
||||||
|
|
||||||
|
Returns the CLI install command.
|
||||||
|
|
||||||
|
**Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
|
||||||
|
|
||||||
|
### `shadcn:get_audit_checklist`
|
||||||
|
|
||||||
|
Returns a checklist for verifying components (imports, deps, lint, TypeScript).
|
||||||
|
|
||||||
|
**Input:** none
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuring Registries
|
||||||
|
|
||||||
|
Namespaced and authenticated registries are set in `components.json`. The
|
||||||
|
`@shadcn` registry is always built-in. Public GitHub registries can also be used
|
||||||
|
directly as `owner/repo` registry sources when the repository has a root
|
||||||
|
`registry.json`; they do not need `components.json` configuration.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"registries": {
|
||||||
|
"@acme": "https://acme.com/r/{name}.json",
|
||||||
|
"@private": {
|
||||||
|
"url": "https://private.com/r/{name}.json",
|
||||||
|
"headers": { "Authorization": "Bearer ${MY_TOKEN}" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Names must start with `@`.
|
||||||
|
- URLs must contain `{name}`.
|
||||||
|
- `${VAR}` references are resolved from environment variables.
|
||||||
|
|
||||||
|
Community registry index: `https://ui.shadcn.com/r/registries.json`
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
# Registry Authoring and Addresses
|
||||||
|
|
||||||
|
Use this reference when the user wants to create, fix, publish, or reason about
|
||||||
|
a shadcn registry.
|
||||||
|
|
||||||
|
## Mental Model
|
||||||
|
|
||||||
|
A registry has two forms:
|
||||||
|
|
||||||
|
- **Source registry**: an authored `registry.json` in a project or repository.
|
||||||
|
It may use `include` and file paths that point at source files.
|
||||||
|
- **Built registry**: generated JSON files served to CLI consumers, usually
|
||||||
|
from `public/r`. Use `npx shadcn@latest build` to create this form.
|
||||||
|
|
||||||
|
The CLI installer consumes registry item payloads. A source registry is a way to
|
||||||
|
author those payloads from real files.
|
||||||
|
|
||||||
|
Registry items are not limited to React components. They can distribute
|
||||||
|
components, hooks, utilities, design tokens, pages, config files, docs, rules,
|
||||||
|
workflows, templates, MCP files, and other project files.
|
||||||
|
|
||||||
|
## Root `registry.json`
|
||||||
|
|
||||||
|
The root registry file should define registry metadata and either `items` or
|
||||||
|
`include`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||||
|
"name": "acme",
|
||||||
|
"homepage": "https://acme.com",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "absolute-url",
|
||||||
|
"type": "registry:lib",
|
||||||
|
"title": "Absolute URL",
|
||||||
|
"description": "A utility to turn any path into an absolute URL.",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "lib/absolute-url.ts",
|
||||||
|
"type": "registry:lib"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Root registry rules:
|
||||||
|
|
||||||
|
- Root `registry.json` must include `name` and `homepage`.
|
||||||
|
- `items` is an array of registry item definitions.
|
||||||
|
- `include` may be used to split the source registry into multiple files.
|
||||||
|
- Included registry files may omit `name` and `homepage`.
|
||||||
|
|
||||||
|
## Include
|
||||||
|
|
||||||
|
Use `include` to keep large registries modular.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema/registry.json",
|
||||||
|
"name": "acme",
|
||||||
|
"homepage": "https://acme.com",
|
||||||
|
"include": ["registry/ui/registry.json", "registry/blocks/registry.json"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Include rules:
|
||||||
|
|
||||||
|
- Include paths are relative to the `registry.json` that declares them.
|
||||||
|
- Include paths must explicitly point to a `registry.json` file.
|
||||||
|
- Do not use remote URLs, absolute paths, or parent traversal (`..`).
|
||||||
|
- Item file paths are relative to the registry file that declares the item.
|
||||||
|
- Duplicate item names fail across the resolved registry.
|
||||||
|
|
||||||
|
Example included file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"name": "button",
|
||||||
|
"type": "registry:ui",
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "button.tsx",
|
||||||
|
"type": "registry:ui"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If this file is at `registry/ui/registry.json`, then `button.tsx` is read from
|
||||||
|
`registry/ui/button.tsx`, and the built item path is emitted relative to the
|
||||||
|
root registry.
|
||||||
|
|
||||||
|
## Item Definitions
|
||||||
|
|
||||||
|
Common item fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "login-form",
|
||||||
|
"type": "registry:block",
|
||||||
|
"title": "Login Form",
|
||||||
|
"description": "A login form with email and password fields.",
|
||||||
|
"dependencies": ["zod"],
|
||||||
|
"registryDependencies": ["button", "input", "label"],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "blocks/login-form.tsx",
|
||||||
|
"type": "registry:block"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cssVars": {
|
||||||
|
"light": {
|
||||||
|
"brand": "oklch(0.62 0.18 250)"
|
||||||
|
},
|
||||||
|
"dark": {
|
||||||
|
"brand": "oklch(0.72 0.16 250)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Important fields:
|
||||||
|
|
||||||
|
- `name`: the installable item name. It is not necessarily a file path.
|
||||||
|
- `type`: one of the registry item types, such as `registry:ui`,
|
||||||
|
`registry:block`, `registry:lib`, `registry:hook`, `registry:file`,
|
||||||
|
`registry:page`, `registry:theme`, `registry:style`, `registry:font`, or
|
||||||
|
`registry:item`.
|
||||||
|
- `files`: source files copied or generated by the item.
|
||||||
|
- `dependencies`: npm runtime dependencies.
|
||||||
|
- `devDependencies`: npm development dependencies.
|
||||||
|
- `registryDependencies`: other registry items required by this item.
|
||||||
|
- `cssVars`, `css`, `tailwind`, `envVars`, and `docs`: optional install-time
|
||||||
|
additions.
|
||||||
|
|
||||||
|
File rules:
|
||||||
|
|
||||||
|
- File paths are relative to the declaring `registry.json`.
|
||||||
|
- `registry:file` and `registry:page` files require a `target`.
|
||||||
|
- Do not use remote file URLs in source registry file paths.
|
||||||
|
- Keep source files copy-pasteable: no hidden app-only imports.
|
||||||
|
|
||||||
|
## Registry Dependencies
|
||||||
|
|
||||||
|
`registryDependencies` entries are item addresses, not file paths.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "login-form",
|
||||||
|
"type": "registry:block",
|
||||||
|
"registryDependencies": ["button", "@acme/input", "acme/ui/card#v1.2.0"],
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "blocks/login-form.tsx",
|
||||||
|
"type": "registry:block"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependency rules:
|
||||||
|
|
||||||
|
- Bare names such as `"button"` mean official shadcn items.
|
||||||
|
- Bare names never mean same-registry or same-repository items.
|
||||||
|
- Namespaced dependencies use `@namespace/item-name`.
|
||||||
|
- GitHub dependencies use `owner/repo/item-name`.
|
||||||
|
- Pin GitHub dependencies with `owner/repo/item-name#ref` when needed.
|
||||||
|
- Refs are not inherited. If `owner/repo/foo#v2` depends on `bar` from the same
|
||||||
|
repo at `v2`, write `owner/repo/bar#v2`.
|
||||||
|
- Do not use relative dependencies such as `"./bar"`.
|
||||||
|
|
||||||
|
## Address Schemes
|
||||||
|
|
||||||
|
When reasoning about a registry item string, classify it first.
|
||||||
|
|
||||||
|
| Address | Scheme | Meaning |
|
||||||
|
| ----------------------------------- | --------- | ------------------------------------------------------------ |
|
||||||
|
| `button` | shadcn | Official shadcn item named `button`. |
|
||||||
|
| `@acme/button` | namespace | Item `button` from configured registry `@acme`. |
|
||||||
|
| `@acme/ui/button` | namespace | Item `ui/button` from configured registry `@acme`. |
|
||||||
|
| `https://example.com/r/button.json` | url | Built registry item JSON at that URL. |
|
||||||
|
| `./button.json` | file | Built registry item JSON on disk. |
|
||||||
|
| `acme/ui/button` | github | Item `button` from GitHub repo `acme/ui`. |
|
||||||
|
| `acme/ui/forms/login#main` | github | Item `forms/login` from GitHub repo `acme/ui` at ref `main`. |
|
||||||
|
|
||||||
|
For namespace and GitHub addresses, slashful item names are allowed and are item
|
||||||
|
names, not file paths. Addresses ending in `.json` keep file-address
|
||||||
|
precedence, so `acme/ui/data/schema.json` is treated as a file path, not a
|
||||||
|
GitHub item address.
|
||||||
|
|
||||||
|
## GitHub Registries
|
||||||
|
|
||||||
|
A public GitHub repository can act as a source registry when it has a root
|
||||||
|
`registry.json`.
|
||||||
|
|
||||||
|
```txt
|
||||||
|
owner/repo/item-name[#ref]
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- The first two path segments are GitHub owner and repo.
|
||||||
|
- All remaining path segments are the registry item name.
|
||||||
|
- The source entrypoint is always root `registry.json`.
|
||||||
|
- GitHub registries are source registries consumed directly by the CLI. They do
|
||||||
|
not require `shadcn build` or generated item JSON files.
|
||||||
|
- `include` follows the same source-registry rules as local registries.
|
||||||
|
- Currently, GitHub addresses support public `github.com` repositories only.
|
||||||
|
- Private repos and GitHub Enterprise require explicit product decisions.
|
||||||
|
|
||||||
|
When implementing GitHub registry fetching, resolve refs to a commit SHA before
|
||||||
|
reading source files. Do not read moving refs directly from
|
||||||
|
`raw.githubusercontent.com`, because branch-like refs can be cached for several
|
||||||
|
minutes.
|
||||||
|
|
||||||
|
Preferred flow:
|
||||||
|
|
||||||
|
```txt
|
||||||
|
owner/repo[#ref]
|
||||||
|
-> resolve ref with git ls-remote
|
||||||
|
-> commit SHA
|
||||||
|
-> read https://raw.githubusercontent.com/{owner}/{repo}/{sha}/registry.json
|
||||||
|
-> read includes and item files from the same SHA
|
||||||
|
```
|
||||||
|
|
||||||
|
This keeps a command on one consistent repository snapshot.
|
||||||
|
|
||||||
|
Full 40-character commit SHAs are already stable and can be used directly.
|
||||||
|
Branches, tags, and short refs require Git so the CLI can resolve them to a
|
||||||
|
commit SHA first.
|
||||||
|
|
||||||
|
## Build and Verify
|
||||||
|
|
||||||
|
Use the CLI to build source registries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest build
|
||||||
|
npx shadcn@latest build registry.json --output public/r
|
||||||
|
```
|
||||||
|
|
||||||
|
Use CLI commands to inspect the result:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest list @acme
|
||||||
|
npx shadcn@latest search @acme -q "login"
|
||||||
|
npx shadcn@latest view @acme/login-form
|
||||||
|
npx shadcn@latest add @acme/login-form --dry-run
|
||||||
|
npx shadcn@latest registry validate ./registry.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Use GitHub addresses directly for public GitHub registries:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx shadcn@latest list owner/repo
|
||||||
|
npx shadcn@latest search owner/repo -q "login"
|
||||||
|
npx shadcn@latest view owner/repo/item
|
||||||
|
npx shadcn@latest add owner/repo/item --dry-run
|
||||||
|
npx shadcn@latest registry validate owner/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
When working on registry implementation in the shadcn/ui codebase:
|
||||||
|
|
||||||
|
- Keep address parsing pure and testable.
|
||||||
|
- Do not add side effects to validators.
|
||||||
|
- Preserve existing behavior for official shadcn, namespace, URL, and file
|
||||||
|
schemes.
|
||||||
|
- Add tests for address parsing, source loading, dependency resolution, list,
|
||||||
|
search, view, and add paths.
|
||||||
|
- Prefer small source-reader abstractions over a plugin system until there are
|
||||||
|
multiple real providers.
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
# Base vs Radix
|
||||||
|
|
||||||
|
API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Composition: asChild vs render
|
||||||
|
- Button / trigger as non-button element
|
||||||
|
- Select (items prop, placeholder, positioning, multiple, object values)
|
||||||
|
- ToggleGroup (type vs multiple)
|
||||||
|
- Slider (scalar vs array)
|
||||||
|
- Accordion (type and defaultValue)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Composition: asChild (radix) vs render (base)
|
||||||
|
|
||||||
|
Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger>
|
||||||
|
<div>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</div>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Button>Open</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogTrigger render={<Button />}>Open</DialogTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button / trigger as non-button element (base only)
|
||||||
|
|
||||||
|
When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
|
||||||
|
|
||||||
|
**Incorrect (base):** missing `nativeButton={false}`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />}>Read the docs</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button render={<a href="/docs" />} nativeButton={false}>
|
||||||
|
Read the docs
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button asChild>
|
||||||
|
<a href="/docs">Read the docs</a>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Same for triggers whose `render` is not a `Button`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
|
||||||
|
Pick date
|
||||||
|
</PopoverTrigger>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select
|
||||||
|
|
||||||
|
**items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger><SelectValue placeholder="Select a fruit" /></SelectTrigger>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const items = [
|
||||||
|
{ label: "Select a fruit", value: null },
|
||||||
|
{ label: "Apple", value: "apple" },
|
||||||
|
{ label: "Banana", value: "banana" },
|
||||||
|
]
|
||||||
|
|
||||||
|
<Select items={items}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select a fruit" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
|
||||||
|
|
||||||
|
**Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
<SelectContent alignItemWithTrigger={false} side="bottom">
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
<SelectContent position="popper">
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Select — multiple selection and object values (base only)
|
||||||
|
|
||||||
|
Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
|
||||||
|
|
||||||
|
**Correct (base — multiple selection):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select items={items} multiple defaultValue={[]}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>
|
||||||
|
{(value: string[]) => value.length === 0 ? "Select fruits" : `${value.length} selected`}
|
||||||
|
</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base — object values):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue>{(value) => value.name}</SelectValue>
|
||||||
|
</SelectTrigger>
|
||||||
|
...
|
||||||
|
</Select>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ToggleGroup
|
||||||
|
|
||||||
|
Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<ToggleGroup type="single" defaultValue="daily">
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single (no prop needed), defaultValue is always an array.
|
||||||
|
<ToggleGroup defaultValue={["daily"]} spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup multiple>
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Single, defaultValue is a string.
|
||||||
|
<ToggleGroup type="single" defaultValue="daily" spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
// Multi-selection.
|
||||||
|
<ToggleGroup type="multiple">
|
||||||
|
<ToggleGroupItem value="bold">Bold</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="italic">Italic</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Controlled single value:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base — wrap/unwrap arrays.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
|
||||||
|
|
||||||
|
// radix — plain string.
|
||||||
|
const [value, setValue] = React.useState("normal")
|
||||||
|
<ToggleGroup type="single" value={value} onValueChange={setValue}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Slider
|
||||||
|
|
||||||
|
Base accepts a plain number for a single thumb. Radix always requires an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={50} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Slider defaultValue={[50]} max={100} step={1} />
|
||||||
|
```
|
||||||
|
|
||||||
|
Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// base.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={(v) => setValue(v as number[])} />
|
||||||
|
|
||||||
|
// radix.
|
||||||
|
const [value, setValue] = React.useState([0.3, 0.7])
|
||||||
|
<Slider value={value} onValueChange={setValue} />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Accordion
|
||||||
|
|
||||||
|
Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
|
||||||
|
|
||||||
|
**Incorrect (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (base):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion defaultValue={["item-1"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
|
||||||
|
// Multi-select.
|
||||||
|
<Accordion multiple defaultValue={["item-1", "item-2"]}>
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
<AccordionItem value="item-2">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct (radix):**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Accordion type="single" collapsible defaultValue="item-1">
|
||||||
|
<AccordionItem value="item-1">...</AccordionItem>
|
||||||
|
</Accordion>
|
||||||
|
```
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
# Chat & Messaging
|
||||||
|
|
||||||
|
Components for conversation and chat UI. Compose these instead of hand-rolling
|
||||||
|
bubbles, scroll containers, dividers, or attachment cards.
|
||||||
|
|
||||||
|
Install: `npx shadcn@latest add message-scroller message bubble attachment marker`
|
||||||
|
|
||||||
|
The same component names and props ship for both `base` and `radix`; only
|
||||||
|
composition differs (`render` vs `asChild`). See [base-vs-radix.md](./base-vs-radix.md).
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Scrollable threads use MessageScroller
|
||||||
|
- Message rows use Message
|
||||||
|
- Message surfaces use Bubble
|
||||||
|
- Attachments use Attachment
|
||||||
|
- System notes and dividers use Marker
|
||||||
|
- Streaming, anchoring, and jump-to-latest are built in
|
||||||
|
- Escape hatch: the scroller hooks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scrollable threads use MessageScroller
|
||||||
|
|
||||||
|
A conversation that scrolls, follows new messages, restores position, or jumps
|
||||||
|
to a message uses `MessageScroller`. Don't build a raw overflow container with
|
||||||
|
manual scroll wiring, and don't reach for `ScrollArea`.
|
||||||
|
|
||||||
|
The parts nest in a fixed order. Every direct child of the content is wrapped in
|
||||||
|
a `MessageScrollerItem` so the scroller can measure, anchor, preserve position,
|
||||||
|
track visibility, and jump to it. `MessageScrollerButton` sits inside
|
||||||
|
`MessageScroller`, after the viewport.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Hand-rolled scroll container with manual stick-to-bottom logic.
|
||||||
|
<div ref={scrollRef} onScroll={handleScroll} className="flex-1 overflow-y-auto">
|
||||||
|
<div className="flex flex-col gap-6 p-4">
|
||||||
|
{messages.map((m) => (
|
||||||
|
<ChatMessage key={m.id} message={m} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<MessageScrollerProvider autoScroll>
|
||||||
|
<MessageScroller>
|
||||||
|
<MessageScrollerViewport>
|
||||||
|
<MessageScrollerContent>
|
||||||
|
{messages.map((message) => (
|
||||||
|
<MessageScrollerItem
|
||||||
|
key={message.id}
|
||||||
|
messageId={message.id}
|
||||||
|
scrollAnchor={message.role === "user"}
|
||||||
|
>
|
||||||
|
<Message align={message.role === "user" ? "end" : "start"}>
|
||||||
|
{/* ...message content... */}
|
||||||
|
</Message>
|
||||||
|
</MessageScrollerItem>
|
||||||
|
))}
|
||||||
|
</MessageScrollerContent>
|
||||||
|
</MessageScrollerViewport>
|
||||||
|
<MessageScrollerButton />
|
||||||
|
</MessageScroller>
|
||||||
|
</MessageScrollerProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message rows use Message
|
||||||
|
|
||||||
|
`Message` lays out a single row: avatar, header, content, footer, with
|
||||||
|
alignment. Group consecutive rows from one sender with `MessageGroup`. Don't
|
||||||
|
rebuild the row from flex divs.
|
||||||
|
|
||||||
|
`align="end"` is the current user's side; `align="start"` is everyone else.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Message align="start">
|
||||||
|
<MessageAvatar>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src={sender.avatar} alt={sender.name} />
|
||||||
|
<AvatarFallback>{initials}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</MessageAvatar>
|
||||||
|
<MessageContent>
|
||||||
|
<MessageHeader>{sender.name}</MessageHeader>
|
||||||
|
<Bubble>
|
||||||
|
<BubbleContent>{text}</BubbleContent>
|
||||||
|
</Bubble>
|
||||||
|
<MessageFooter>{time}</MessageFooter>
|
||||||
|
</MessageContent>
|
||||||
|
</Message>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Message surfaces use Bubble
|
||||||
|
|
||||||
|
The colored message surface is `Bubble` + `BubbleContent`, never a styled `div`
|
||||||
|
with `bg-muted` / `bg-primary` and hand-managed corners.
|
||||||
|
|
||||||
|
- `variant`: `default`, `secondary`, `muted`, `tinted`, `outline`, `ghost`, `destructive`.
|
||||||
|
- `align`: `start` or `end` (matches the `Message` side).
|
||||||
|
|
||||||
|
`BubbleReactions` renders the reaction cluster. `side` (`top` | `bottom`) and
|
||||||
|
`align` (`start` | `end`) position it against the bubble. Don't lay reactions out
|
||||||
|
with absolutely-positioned `Badge`s.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="w-fit rounded-2xl bg-primary px-3 py-2 text-primary-foreground">
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Bubble variant="default" align="end">
|
||||||
|
<BubbleContent>{text}</BubbleContent>
|
||||||
|
<BubbleReactions side="bottom" align="end">
|
||||||
|
<Badge variant="secondary">👍 2</Badge>
|
||||||
|
</BubbleReactions>
|
||||||
|
</Bubble>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Attachments use Attachment
|
||||||
|
|
||||||
|
File and image attachments use `Attachment`, not `Item` or a custom card. It
|
||||||
|
carries upload state, so wire `state` to the real status rather than rendering a
|
||||||
|
separate spinner.
|
||||||
|
|
||||||
|
- `state`: `idle`, `uploading`, `processing`, `error`, `done`. `uploading` and
|
||||||
|
`processing` apply the `shimmer` animation to the title automatically.
|
||||||
|
- `size`: `default`, `sm`, `xs`. `orientation`: `horizontal`, `vertical`.
|
||||||
|
- Use `AttachmentGroup` to lay out several attachments in a scrolling row.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Attachment state="done">
|
||||||
|
<AttachmentMedia variant="icon">
|
||||||
|
<FileTextIcon />
|
||||||
|
</AttachmentMedia>
|
||||||
|
<AttachmentContent>
|
||||||
|
<AttachmentTitle>homepage-feedback.pdf</AttachmentTitle>
|
||||||
|
<AttachmentDescription>PDF · 2.4 MB</AttachmentDescription>
|
||||||
|
</AttachmentContent>
|
||||||
|
<AttachmentActions>
|
||||||
|
<AttachmentAction>
|
||||||
|
<DownloadIcon />
|
||||||
|
</AttachmentAction>
|
||||||
|
</AttachmentActions>
|
||||||
|
</Attachment>
|
||||||
|
```
|
||||||
|
|
||||||
|
For an image, use `<AttachmentMedia variant="image">` with an `img` child.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System notes and dividers use Marker
|
||||||
|
|
||||||
|
Status lines ("Sarah joined the conversation"), date dividers ("Today"), and
|
||||||
|
labeled separators are `Marker`, not a `Separator` plus a centered span.
|
||||||
|
|
||||||
|
- `variant`: `default` (plain row), `separator` (centered label with rules on
|
||||||
|
each side), `border` (bottom-bordered row).
|
||||||
|
- `MarkerIcon` holds a leading icon; `MarkerContent` holds the label.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="flex items-center gap-3 py-2">
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
<span className="text-xs text-muted-foreground">Today</span>
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Marker variant="separator">
|
||||||
|
<MarkerContent>Today</MarkerContent>
|
||||||
|
</Marker>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Streaming, anchoring, and jump-to-latest are built in
|
||||||
|
|
||||||
|
`MessageScroller` handles the behavior that chat UIs usually reinvent. Don't
|
||||||
|
write a `useStickToBottom` hook, a `ResizeObserver`, or manual `scrollTop` math.
|
||||||
|
|
||||||
|
- **Follow the live edge while streaming.** `MessageScrollerProvider` with
|
||||||
|
`autoScroll` keeps the view pinned to new content and yields the moment the
|
||||||
|
user scrolls up. Streaming token updates that grow the last message are
|
||||||
|
followed automatically.
|
||||||
|
- **Anchor a turn.** `scrollAnchor` on a `MessageScrollerItem` marks the row to
|
||||||
|
hold in view (typically the user's message that started the turn).
|
||||||
|
- **Jump to latest.** `MessageScrollerButton` appears when the user scrolls away
|
||||||
|
and scrolls back on click. `direction="end"` (default) or `direction="start"`.
|
||||||
|
It is a self-managing control, so don't gate it behind your own scroll-position
|
||||||
|
state.
|
||||||
|
|
||||||
|
For a "thinking…" indicator while the model generates, apply the `shimmer`
|
||||||
|
utility to text. Don't author a custom keyframe animation. See
|
||||||
|
[styling.md](./styling.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Escape hatch: the scroller hooks
|
||||||
|
|
||||||
|
For behavior the parts don't expose, read state from the hooks rather than
|
||||||
|
re-implementing the scroller: `useMessageScroller`,
|
||||||
|
`useMessageScrollerVisibility`, and `useMessageScrollerScrollable`. They come
|
||||||
|
from the auto-installed `@shadcn/react` dependency, so there's nothing extra to
|
||||||
|
install. Reach for them only when composition can't express what you need.
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
# Component Composition
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Items always inside their Group component
|
||||||
|
- Callouts use Alert
|
||||||
|
- Empty states use Empty component
|
||||||
|
- Toast notifications follow the project base
|
||||||
|
- Choosing between overlay components
|
||||||
|
- Dialog, Sheet, and Drawer always need a Title
|
||||||
|
- Card structure
|
||||||
|
- Button has no isPending or isLoading prop
|
||||||
|
- TabsTrigger must be inside TabsList
|
||||||
|
- Avatar always needs AvatarFallback
|
||||||
|
- Use Separator instead of raw hr or border divs
|
||||||
|
- Use Skeleton for loading placeholders
|
||||||
|
- Use Badge instead of custom styled spans
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Items always inside their Group component
|
||||||
|
|
||||||
|
Never render items directly inside the content container.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value="apple">Apple</SelectItem>
|
||||||
|
<SelectItem value="banana">Banana</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
This applies to all group-based components:
|
||||||
|
|
||||||
|
| Item | Group |
|
||||||
|
|------|-------|
|
||||||
|
| `SelectItem`, `SelectLabel` | `SelectGroup` |
|
||||||
|
| `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
|
||||||
|
| `MenubarItem` | `MenubarGroup` |
|
||||||
|
| `ContextMenuItem` | `ContextMenuGroup` |
|
||||||
|
| `CommandItem` | `CommandGroup` |
|
||||||
|
| `MessageScrollerItem` | `MessageScrollerContent` |
|
||||||
|
| `Message` (consecutive, same sender) | `MessageGroup` |
|
||||||
|
| `Bubble` (stacked) | `BubbleGroup` |
|
||||||
|
| `Attachment` (in a row) | `AttachmentGroup` |
|
||||||
|
|
||||||
|
Chat components nest in a fixed order (`MessageScrollerProvider` → `MessageScroller` → `MessageScrollerViewport` → `MessageScrollerContent` → `MessageScrollerItem`). See [chat.md](./chat.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Callouts use Alert
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Alert>
|
||||||
|
<AlertTitle>Warning</AlertTitle>
|
||||||
|
<AlertDescription>Something needs attention.</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Empty states use Empty component
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Empty>
|
||||||
|
<EmptyHeader>
|
||||||
|
<EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
|
||||||
|
<EmptyTitle>No projects yet</EmptyTitle>
|
||||||
|
<EmptyDescription>Get started by creating a new project.</EmptyDescription>
|
||||||
|
</EmptyHeader>
|
||||||
|
<EmptyContent>
|
||||||
|
<Button>Create Project</Button>
|
||||||
|
</EmptyContent>
|
||||||
|
</Empty>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Toast notifications follow the project base
|
||||||
|
|
||||||
|
For Base UI projects, use the `toast` component:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { toast } from "@/components/ui/toast"
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
title: "Changes saved.",
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For Radix and React Aria projects, use Sonner:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
|
toast.success("Changes saved.")
|
||||||
|
toast.error("Something went wrong.")
|
||||||
|
toast("File deleted.", {
|
||||||
|
action: { label: "Undo", onClick: () => undoDelete() },
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Choosing between overlay components
|
||||||
|
|
||||||
|
| Use case | Component |
|
||||||
|
|----------|-----------|
|
||||||
|
| Focused task that requires input | `Dialog` |
|
||||||
|
| Destructive action confirmation | `AlertDialog` |
|
||||||
|
| Side panel with details or filters | `Sheet` |
|
||||||
|
| Mobile-first bottom panel | `Drawer` |
|
||||||
|
| Quick info on hover | `HoverCard` |
|
||||||
|
| Small contextual content on click | `Popover` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dialog, Sheet, and Drawer always need a Title
|
||||||
|
|
||||||
|
`DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Edit Profile</DialogTitle>
|
||||||
|
<DialogDescription>Update your profile.</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
...
|
||||||
|
</DialogContent>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Card structure
|
||||||
|
|
||||||
|
Use full composition — don't dump everything into `CardContent`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Team Members</CardTitle>
|
||||||
|
<CardDescription>Manage your team.</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>...</CardContent>
|
||||||
|
<CardFooter>
|
||||||
|
<Button>Invite</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Button has no isPending or isLoading prop
|
||||||
|
|
||||||
|
Compose with `Spinner` + `data-icon` + `disabled`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button disabled>
|
||||||
|
<Spinner data-icon="inline-start" />
|
||||||
|
Saving...
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TabsTrigger must be inside TabsList
|
||||||
|
|
||||||
|
Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Tabs defaultValue="account">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="account">Account</TabsTrigger>
|
||||||
|
<TabsTrigger value="password">Password</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<TabsContent value="account">...</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Avatar always needs AvatarFallback
|
||||||
|
|
||||||
|
Always include `AvatarFallback` for when the image fails to load:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="/avatar.png" alt="User" />
|
||||||
|
<AvatarFallback>JD</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use existing components instead of custom markup
|
||||||
|
|
||||||
|
| Instead of | Use |
|
||||||
|
|---|---|
|
||||||
|
| `<hr>` or `<div className="border-t">` | `<Separator />` |
|
||||||
|
| `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
|
||||||
|
| `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
# Forms & Inputs
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Forms use FieldGroup + Field
|
||||||
|
- InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
- Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
- Option sets (2–7 choices) use ToggleGroup
|
||||||
|
- FieldSet + FieldLegend for grouping related fields
|
||||||
|
- Field validation and disabled states
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Forms use FieldGroup + Field
|
||||||
|
|
||||||
|
Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldGroup>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" type="email" />
|
||||||
|
</Field>
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="password">Password</FieldLabel>
|
||||||
|
<Input id="password" type="password" />
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
|
||||||
|
|
||||||
|
**Choosing form controls:**
|
||||||
|
|
||||||
|
- Simple text input → `Input`
|
||||||
|
- Dropdown with predefined options → `Select`
|
||||||
|
- Searchable dropdown → `Combobox`
|
||||||
|
- Native HTML select (no JS) → `native-select`
|
||||||
|
- Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
|
||||||
|
- Single choice from few options → `RadioGroup`
|
||||||
|
- Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
|
||||||
|
- OTP/verification code → `InputOTP`
|
||||||
|
- Multi-line text → `Textarea`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## InputGroup requires InputGroupInput/InputGroupTextarea
|
||||||
|
|
||||||
|
Never use raw `Input` or `Textarea` inside an `InputGroup`.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<InputGroup>
|
||||||
|
<Input placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Buttons inside inputs use InputGroup + InputGroupAddon
|
||||||
|
|
||||||
|
Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="relative">
|
||||||
|
<Input placeholder="Search..." className="pr-10" />
|
||||||
|
<Button className="absolute right-0 top-0" size="icon">
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group"
|
||||||
|
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroupInput placeholder="Search..." />
|
||||||
|
<InputGroupAddon>
|
||||||
|
<Button size="icon">
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
</Button>
|
||||||
|
</InputGroupAddon>
|
||||||
|
</InputGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Option sets (2–7 choices) use ToggleGroup
|
||||||
|
|
||||||
|
Don't manually loop `Button` components with active state.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const [selected, setSelected] = useState("daily")
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{["daily", "weekly", "monthly"].map((option) => (
|
||||||
|
<Button
|
||||||
|
key={option}
|
||||||
|
variant={selected === option ? "default" : "outline"}
|
||||||
|
onClick={() => setSelected(option)}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
|
||||||
|
|
||||||
|
<ToggleGroup spacing={2}>
|
||||||
|
<ToggleGroupItem value="daily">Daily</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
```
|
||||||
|
|
||||||
|
Combine with `Field` for labelled toggle groups:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<FieldTitle id="theme-label">Theme</FieldTitle>
|
||||||
|
<ToggleGroup aria-labelledby="theme-label" spacing={2}>
|
||||||
|
<ToggleGroupItem value="light">Light</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="dark">Dark</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="system">System</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## FieldSet + FieldLegend for grouping related fields
|
||||||
|
|
||||||
|
Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<FieldSet>
|
||||||
|
<FieldLegend variant="label">Preferences</FieldLegend>
|
||||||
|
<FieldDescription>Select all that apply.</FieldDescription>
|
||||||
|
<FieldGroup className="gap-3">
|
||||||
|
<Field orientation="horizontal">
|
||||||
|
<Checkbox id="dark" />
|
||||||
|
<FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
|
||||||
|
</Field>
|
||||||
|
</FieldGroup>
|
||||||
|
</FieldSet>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field validation and disabled states
|
||||||
|
|
||||||
|
Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Invalid.
|
||||||
|
<Field data-invalid>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" aria-invalid />
|
||||||
|
<FieldDescription>Invalid email address.</FieldDescription>
|
||||||
|
</Field>
|
||||||
|
|
||||||
|
// Disabled.
|
||||||
|
<Field data-disabled>
|
||||||
|
<FieldLabel htmlFor="email">Email</FieldLabel>
|
||||||
|
<Input id="email" disabled />
|
||||||
|
</Field>
|
||||||
|
```
|
||||||
|
|
||||||
|
Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# Icons
|
||||||
|
|
||||||
|
**Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Icons in Button use data-icon attribute
|
||||||
|
|
||||||
|
Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="mr-2 size-4" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start"/>
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button>
|
||||||
|
Next
|
||||||
|
<ArrowRightIcon data-icon="inline-end"/>
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No sizing classes on icons inside components
|
||||||
|
|
||||||
|
Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon className="size-4" data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon className="mr-2 size-4" />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button>
|
||||||
|
<SearchIcon data-icon="inline-start" />
|
||||||
|
Search
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<SettingsIcon />
|
||||||
|
Settings
|
||||||
|
</DropdownMenuItem>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pass icons as component objects, not string keys
|
||||||
|
|
||||||
|
Use `icon={CheckIcon}`, not a string key to a lookup map.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
const iconMap = {
|
||||||
|
check: CheckIcon,
|
||||||
|
alert: AlertIcon,
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBadge({ icon }: { icon: string }) {
|
||||||
|
const Icon = iconMap[icon]
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon="check" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
|
||||||
|
import { CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
|
function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
|
||||||
|
return <Icon />
|
||||||
|
}
|
||||||
|
|
||||||
|
<StatusBadge icon={CheckIcon} />
|
||||||
|
```
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
# Styling & Customization
|
||||||
|
|
||||||
|
See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- Semantic colors
|
||||||
|
- Built-in variants first
|
||||||
|
- className for layout only
|
||||||
|
- No space-x-* / space-y-*
|
||||||
|
- Prefer size-* over w-* h-* when equal
|
||||||
|
- Prefer truncate shorthand
|
||||||
|
- No manual dark: color overrides
|
||||||
|
- Use cn() for conditional classes
|
||||||
|
- No manual z-index on overlay components
|
||||||
|
- Use shimmer / scroll-fade utilities, not custom animations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Semantic colors
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-blue-500 text-white">
|
||||||
|
<p className="text-gray-600">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="bg-primary text-primary-foreground">
|
||||||
|
<p className="text-muted-foreground">Secondary text</p>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No raw color values for status/state indicators
|
||||||
|
|
||||||
|
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="text-emerald-600">+20.1%</span>
|
||||||
|
<span className="text-green-500">Active</span>
|
||||||
|
<span className="text-red-600">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Badge variant="secondary">+20.1%</Badge>
|
||||||
|
<Badge>Active</Badge>
|
||||||
|
<span className="text-destructive">-3.2%</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Built-in variants first
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button className="border border-input bg-transparent hover:bg-accent">
|
||||||
|
Click me
|
||||||
|
</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Button variant="outline">Click me</Button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## className for layout only
|
||||||
|
|
||||||
|
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="bg-blue-100 text-blue-900 font-bold">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<Card className="max-w-md mx-auto">
|
||||||
|
<CardContent>Dashboard</CardContent>
|
||||||
|
</Card>
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize a component's appearance, prefer these approaches in order:
|
||||||
|
1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
|
||||||
|
2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
|
||||||
|
3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No space-x-* / space-y-*
|
||||||
|
|
||||||
|
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<Input />
|
||||||
|
<Input />
|
||||||
|
<Button>Submit</Button>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer size-* over w-* h-* when equal
|
||||||
|
|
||||||
|
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prefer truncate shorthand
|
||||||
|
|
||||||
|
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No manual dark: color overrides
|
||||||
|
|
||||||
|
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use cn() for conditional classes
|
||||||
|
|
||||||
|
Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## No manual z-index on overlay components
|
||||||
|
|
||||||
|
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Use shimmer / scroll-fade utilities, not custom animations
|
||||||
|
|
||||||
|
For a live "thinking…" or loading-text shimmer, apply the `shimmer` utility. Don't author a custom `@keyframes` or a `bg-clip-text` gradient sweep.
|
||||||
|
|
||||||
|
For scroll-aware edge fading on a scroll container, use `scroll-fade` (and the axis variants `scroll-fade-x` / `scroll-fade-b`). Don't hand-roll mask gradients. The chat components already apply these internally: `Attachment` shimmers its title during upload, and `MessageScrollerViewport` fades its edges.
|
||||||
|
|
||||||
|
**Incorrect:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="animate-pulse bg-gradient-to-r from-muted-foreground/40 via-foreground/70 to-muted-foreground/40 bg-clip-text text-transparent [animation:shimmer_1.6s_infinite]">
|
||||||
|
Thinking…
|
||||||
|
</span>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Correct:**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<span className="shimmer">Thinking…</span>
|
||||||
|
```
|
||||||
+1
-1
@@ -24,7 +24,7 @@
|
|||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
|
||||||
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
|
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
|
||||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/jdk-26.0.1">
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25">
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="module" value="true"/>
|
<attribute name="module" value="true"/>
|
||||||
</attributes>
|
</attributes>
|
||||||
|
|||||||
@@ -3,3 +3,12 @@
|
|||||||
/klalbs4.json
|
/klalbs4.json
|
||||||
/klalbs.json
|
/klalbs.json
|
||||||
/klalbs2.json
|
/klalbs2.json
|
||||||
|
|
||||||
|
# Dashboard / Frontend
|
||||||
|
dashboard/node_modules/
|
||||||
|
dashboard/dist/
|
||||||
|
dashboard/.pnpm-store/
|
||||||
|
.pnpm-debug.log*
|
||||||
|
dashboard/.env.local
|
||||||
|
dashboard/.env.*.local
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "dashboard"]
|
||||||
|
path = dashboard
|
||||||
|
url = https://git.code.cq.cn/SerinaNya/KLALB-dashboard.git
|
||||||
@@ -14,4 +14,15 @@
|
|||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
</natures>
|
</natures>
|
||||||
|
<filteredResources>
|
||||||
|
<filter>
|
||||||
|
<id>1787317094049</id>
|
||||||
|
<name></name>
|
||||||
|
<type>30</type>
|
||||||
|
<matcher>
|
||||||
|
<id>org.eclipse.core.resources.regexFilterMatcher</id>
|
||||||
|
<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
|
||||||
|
</matcher>
|
||||||
|
</filter>
|
||||||
|
</filteredResources>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "KLALBMain (VS Code 自动构建)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||||
|
"projectName": "KLALB",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "java",
|
||||||
|
"name": "KLALBMain (直接用 bin 目录)",
|
||||||
|
"request": "launch",
|
||||||
|
"mainClass": "org.kne.cloud.network.klalb.KLALBMain",
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"classPaths": [
|
||||||
|
"${workspaceFolder}/bin",
|
||||||
|
"${workspaceFolder}/src",
|
||||||
|
"${workspaceFolder}/lib/*"
|
||||||
|
],
|
||||||
|
"vmArgs": "--enable-native-access=ALL-UNNAMED --add-opens=java.base/jdk.internal.misc=ALL-UNNAMED"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.runtimes": [
|
||||||
|
{
|
||||||
|
"name": "JavaSE-25",
|
||||||
|
"path": "C:\\Program Files\\Zulu\\zulu-25",
|
||||||
|
"default": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system that merges multiple WAN links into one virtual IPv6/SRv6 network. Version constant lives in `src/org/kne/cloud/network/klalb/CONST.java`. Protocol specs and manuals are the Chinese `.docx` files in the repo root.
|
||||||
|
|
||||||
|
## Build & run
|
||||||
|
|
||||||
|
No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`.
|
||||||
|
|
||||||
|
Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text):
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
& "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
|
||||||
|
```
|
||||||
|
|
||||||
|
Warnings about `ThreadTool` varargs / deprecated `finalize` are pre-existing and expected — success = exit code 0. After recompiling, restart the running app (IDE-debugged JVMs keep old classes).
|
||||||
|
|
||||||
|
Run from the repo root — CWD matters:
|
||||||
|
- reads `klalb-config.json` from CWD
|
||||||
|
- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support)
|
||||||
|
- classpath must include `src` as well as `bin`: i18n bundles (`/klalb_*.properties`) and images (`/assets/*`) are classpath resources that Eclipse copies to `bin` but manual `javac` does not
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
java --enable-native-access=ALL-UNNAMED "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" -cp "bin;src;lib/*" org.kne.cloud.network.klalb.KLALBMain
|
||||||
|
```
|
||||||
|
|
||||||
|
IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JDK 25. `.classpath` now references the standard container `JavaSE-25` — an execution-environment spec that any JDK ≥25 satisfies, so it works unchanged on JDK 26 machines too (the original named `jdk-26.0.1` VM broke VS Code import on machines without it). `.vscode/settings.json` maps `JavaSE-25` to the locally installed Adoptium JDK; register every installed JDK there when adding another one. Keep compiler compliance ≤25 (`.settings` pins 19) so both JDKs stay usable.
|
||||||
|
|
||||||
|
Runtime gotchas (all verified):
|
||||||
|
- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs).
|
||||||
|
- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work.
|
||||||
|
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"enableTUN": false` in `klalb-config.json` or toggle off "启用 TUN 虚拟网卡" in GUI/Web settings.
|
||||||
|
- Routing broadcast (`RouterInfo`) transmits `deviceName`, which topology and node overview panels display. `deviceDescription` is NOT broadcast — it only leaves the node in full node-info query responses (see srv6 API below); `ExtraRoutes` remain local controller configs.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `main()` harnesses requiring real network peers. Practical check = compile succeeds + app launches.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
|
||||||
|
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
|
||||||
|
- API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routes`, `/api/nodes` (topology graph), `/api/interfaces`, `/api/config`.
|
||||||
|
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
|
||||||
|
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
|
||||||
|
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
|
||||||
|
- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config.
|
||||||
|
- `...network.kltp` — custom reliable transport protocol (packets/streams).
|
||||||
|
- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation.
|
||||||
|
- **Node-info query API** (`...network.srv6`, JSON datagrams on `KLALBRoutingProtocol.DEFAULT_PORT=1001`): `KLALBRoutingProtocolAPIServer/Client` speak two request types —
|
||||||
|
- `nodeinfotinyreq/resp` → device name ONLY; never gated by any flag (name is public via broadcast anyway).
|
||||||
|
- `nodeinfofullreq/resp` → externalEndpoints + deviceName + deviceDescription. `denyExternalEndpointQuery=true` hides ONLY the endpoint list (`data=null`); name/description still answer.
|
||||||
|
- GUI rule: opening `NodeInformationPanel` = Full query; use `requestNodeInfoTiny` for lightweight/background lookups. Legacy `openlines*` message types were removed — mixed-version meshes get silence, so upgrade the whole network together.
|
||||||
|
- `JsonDataPacket` stores its UTF-8 payload length in a 2-byte header field: keep every JSON message under 64 KiB.
|
||||||
|
- `...network.frpc` — frp client integration.
|
||||||
|
- `...klalb.ui` — all Swing UI code.
|
||||||
|
|
||||||
|
## Frontend (Dashboard)
|
||||||
|
|
||||||
|
Located in `dashboard/`:
|
||||||
|
- **Git layout**: `dashboard/` is a separate git repo wired in as a submodule (own origin on `git.code.cq.cn`). Commit frontend changes inside `dashboard/` first, then bump the submodule pointer in the parent repo — parent-repo commits alone do not capture them.
|
||||||
|
- **Stack**: Vite + React 19 + TypeScript + Tailwind CSS v4 + `@base-ui/react` (style: `base-nova`, icons: `lucide-react`, toasts: `@base-ui/react/toast`).
|
||||||
|
- **Routing**: Hash-based routing (`#/overview`, `#/connections`, `#/topology`, `#/settings`, etc.) for seamless SPA hosting under Java `KLALBWebServer`.
|
||||||
|
- **Package Manager**: `pnpm` (run all commands from `dashboard/` directory).
|
||||||
|
- **Component installation**: **Must** use CLI via `pnpm dlx shadcn@latest add <component>` (e.g. `pnpm dlx shadcn@latest add alert card badge toast`). Never create or fake shadcn components manually. Non-shadcn libs (`@xyflow/react`, `d3-force`) are installed via plain `pnpm add`.
|
||||||
|
- **Commands**:
|
||||||
|
- `pnpm dev` — Start Vite dev server (proxies `/api` to backend `http://127.0.0.1:4665`).
|
||||||
|
- `pnpm build` — Typecheck and build SPA to `dashboard/dist` (which Java `KLALBWebServer` serves directly).
|
||||||
|
- `pnpm lint` / `pnpm typecheck` — Verification.
|
||||||
|
- **Pages & data flow**:
|
||||||
|
- Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links).
|
||||||
|
- Settings loads/saves `/api/config` (`use-klalb-config.ts`); save payload must keep legacy field aliases alongside new names for compatibility.
|
||||||
|
- Topology polls `/api/nodes` every 1s (`use-topology.ts`) — SSE does NOT carry topology.
|
||||||
|
- Topology layout: `d3-force` headless simulation (recomputed only when node/edge structure changes) rendered by `@xyflow/react` with custom `device-node` / `link-edge` components in `src/components/topology/`.
|
||||||
|
- React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern).
|
||||||
|
|
||||||
|
## Config
|
||||||
|
|
||||||
|
`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`).
|
||||||
|
|
||||||
|
Key controller config fields:
|
||||||
|
- `externalEndpoints` / `autoConnections`: published vs auto-connect endpoint lists (renamed from `openConnections`, which itself replaced legacy `LineTable`; the old name was a developer naming mistake — these addresses are this node's externally published endpoints, not "connections").
|
||||||
|
- `ntpServers`: time server list (replaces `ntpServerTable`).
|
||||||
|
- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`).
|
||||||
|
- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name).
|
||||||
|
- `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input.
|
||||||
|
|
||||||
|
Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names.
|
||||||
|
|
||||||
|
Gson quirks:
|
||||||
|
- **gson-2.1 (vendored) is ancient**: its `JSON_ELEMENT` adapter factory only matches exact `JsonElement.class`, NOT subclasses. Calling `gson.toJson(Object)` with a runtime `JsonObject`/`JsonArray` reflectively serializes the internal field as `{"members": {...}}`. `KLALBWebServer.sendJsonResponse` guards against this by using `JsonElement.toString()` for JsonElement instances — keep that guard when adding new response paths. SSE avoids the issue entirely via `JsonObject.toString()`.
|
||||||
|
- `/api/config` GET/POST is parsed field-by-field in `KLALBWebServer.handleConfig` (NOT whole-object Gson reflection) because polymorphic fields (`List<InetAddress>`, `List<MultiProtocolSocketAddress>`) break reflective mapping. Keep new config fields in sync there, accepting both legacy and new JSON key names.
|
||||||
|
- `InetAddress`, `MultiProtocolSocketAddress`, and `KLALBConfigItem` custom adapters are registered on the shared Gson in `KLALBProxySystem`; the web server reuses that instance via `proxySystem.getGson()`.
|
||||||
|
- Saving via web API persists through `KLALBProxySystem.saveConfigToFile()` (GUI save consumer takes precedence when present).
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese.
|
||||||
|
- UI strings go through `UIEnv.getRsb().getString(...)`; add keys to **both** `src/klalb_zh_CN.properties` and `src/klalb_en_US.properties`.
|
||||||
|
- `client.cfg`, `server.cfg`, `linetable.txt` at the root are example line-table/port-rule files loaded via the GUI file picker — not hardwired paths.
|
||||||
Submodule
+1
Submodule dashboard added at f38d640ac4
+63
-73
@@ -2,121 +2,111 @@
|
|||||||
{
|
{
|
||||||
"language": "ZH_CN",
|
"language": "ZH_CN",
|
||||||
"nogui": false,
|
"nogui": false,
|
||||||
"VirtualAddress": "2486:1:0:0:0:0:0:8888",
|
"VirtualAddress": "2486:1:0:0:0:0:0:8889",
|
||||||
"VirtualASN": 2142606939373348329,
|
"VirtualASN": 2142606939373348329,
|
||||||
"DNS": [
|
"DNS": [
|
||||||
"2486:1:0:0:0:0:0:8888"
|
"2486:1:0:0:0:0:0:8888"
|
||||||
],
|
],
|
||||||
"TCPListen": "0.0.0.0:4565",
|
"TCPListen": "tcp://0.0.0.0:4565",
|
||||||
"UDPListen": "{UDP}0.0.0.0:4572",
|
"UDPListen": "udp://0.0.0.0:4572",
|
||||||
"VirtualSocketName": "KLALB_Stream",
|
"VirtualSocketName": "kltp",
|
||||||
"LineTable": [
|
"externalEndpoints": [
|
||||||
"07f4acdef99b.ofalias.net:4565"
|
"tcp://kne03.yoyo250.fun:4565",
|
||||||
|
"tcp://kne04.yoyo250.fun:4565",
|
||||||
|
"tcp://07f4acdef99b.ofalias.net:4565",
|
||||||
|
"tcp://kne01.yoyo250.fun:4565",
|
||||||
|
"tcp://kne02.yoyo250.fun:4565"
|
||||||
],
|
],
|
||||||
"ConnectLineTable": [
|
"autoConnections": [
|
||||||
"home-ipv6.sliveridc1.cn:4565",
|
"tcp://07f4acdef99b.ofalias.net:4565",
|
||||||
"home-ltipv6.sliveridc1.cn:4565",
|
"tcp://kne01.yoyo250.fun:4565",
|
||||||
"cn-gd-gz.sliveridc1.cn:4565"
|
"tcp://kne02.yoyo250.fun:4565"
|
||||||
],
|
],
|
||||||
"ntpServerTable": [
|
"ntpServers": [
|
||||||
"{UDP}ntp1.aliyun.com:123",
|
"ntp://ntp1.aliyun.com",
|
||||||
"{UDP}ntp2.aliyun.com:123",
|
"ntp://ntp2.aliyun.com",
|
||||||
"{UDP}ntp3.aliyun.com:123",
|
"ntp://ntp3.aliyun.com",
|
||||||
"{UDP}ntp4.aliyun.com:123",
|
"ntp://ntp4.aliyun.com",
|
||||||
"{UDP}ntp5.aliyun.com:123",
|
"ntp://ntp5.aliyun.com",
|
||||||
"{UDP}ntp6.aliyun.com:123",
|
"ntp://ntp6.aliyun.com",
|
||||||
"{UDP}ntp7.aliyun.com:123",
|
"ntp://ntp7.aliyun.com",
|
||||||
"{UDP}ntp1.tencent.com:123",
|
"ntp://ntp1.tencent.com",
|
||||||
"{UDP}ntp2.tencent.com:123",
|
"ntp://ntp2.tencent.com",
|
||||||
"{UDP}ntp3.tencent.com:123",
|
"ntp://ntp4.tencent.com",
|
||||||
"{UDP}ntp4.tencent.com:123",
|
"ntp://ntp5.tencent.com",
|
||||||
"{UDP}ntp5.tencent.com:123",
|
"ntp://time.google.com",
|
||||||
"{UDP}time.google.com:123",
|
"ntp://time.apple.com",
|
||||||
"{UDP}time.apple.com:123",
|
"ntp://pool.ntp.org",
|
||||||
"{UDP}pool.ntp.org:123",
|
"ntp://ntp.ntsc.ac.cn",
|
||||||
"{UDP}ntp.ntsc.ac.cn:123",
|
"ntp://us.ntp.org.cn"
|
||||||
"{UDP}us.ntp.org.cn:123"
|
|
||||||
],
|
],
|
||||||
"denyLineTableQuery": true,
|
"ExtraRoutes": [],
|
||||||
"denyLineTableBroadcast": true,
|
"denyExternalEndpointQuery": false,
|
||||||
|
"denyExternalEndpointBroadcast": false,
|
||||||
"congestionAlgorithm": "BBR",
|
"congestionAlgorithm": "BBR",
|
||||||
"burstLimit": 1.5,
|
"burstLimit": 2.0,
|
||||||
"delayUpperBound": 1.3,
|
"delayUpperBound": 1.2,
|
||||||
"delayLowerBound": 1.3,
|
"delayLowerBound": 1.1,
|
||||||
"nagleDelayTime": 1000000,
|
"nagleDelayTime": 1000000,
|
||||||
"linkNagleDelayTime": 0,
|
"linkNagleDelayTime": 0,
|
||||||
"linkConnectionsCount": 1,
|
"linkConnectionsCount": 1,
|
||||||
|
"enableTUN": false,
|
||||||
"TUNName": "KLALB_SRv6",
|
"TUNName": "KLALB_SRv6",
|
||||||
|
"performanceStrategy": "multiscatter",
|
||||||
|
"DeviceName": "Device Name",
|
||||||
|
"DeviceDescription": "The Description of Device",
|
||||||
|
"webUI": true,
|
||||||
|
"webListen": "http://0.0.0.0:4665",
|
||||||
"NetworkInterfaceExcepts": [],
|
"NetworkInterfaceExcepts": [],
|
||||||
"Type": "KLALBController"
|
"Type": "KLALBController"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "0.0.0.0:4572",
|
"Listen": "kltp://[::0]:5201",
|
||||||
"Bridge": {
|
|
||||||
"KLALB": "SocketBridge",
|
|
||||||
"RDP": "SocketBridge",
|
|
||||||
"HTTP": "SocketBridge",
|
|
||||||
"HTTPS": "SocketBridge",
|
|
||||||
"DEFAULT": "MinecraftSocketBridge23332"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"KLALB": "127.0.0.1:4565",
|
|
||||||
"RDP": "127.0.0.1:3389",
|
|
||||||
"HTTP": "127.0.0.1:5212",
|
|
||||||
"HTTPS": "192.168.1.235:443",
|
|
||||||
"DEFAULT": "127.0.0.1:36555"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "{KLALB_Stream}[::0]:23332",
|
|
||||||
"Bridge": {
|
|
||||||
"DEFAULT": "MinecraftSocketBridge23332"
|
|
||||||
},
|
|
||||||
"Connect": {
|
|
||||||
"DEFAULT": "127.0.0.1:36555"
|
|
||||||
},
|
|
||||||
"Type": "SocketBridge"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Listen": "{KLALB_Stream}[::0]:23333",
|
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "SocketBridge"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:5212"
|
"DEFAULT": "tcp://127.0.0.1:5201"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "{KLALB_Stream}[::0]:25565",
|
"Listen": "tcp://[::1]:5202",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "SocketBridge"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:25566"
|
"DEFAULT": "kltp://[2486:5acd:e339:4837:a6d9:3aed:4de2:30fd]:5201"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "0.0.0.0:25561",
|
"Listen": "tcp://127.0.0.1:35000",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"KLALB": "SocketBridge",
|
"DEFAULT": "SocketBridge"
|
||||||
"DEFAULT": "MinecraftSocketBridge23330"
|
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"KLALB": "127.0.0.1:4565",
|
"DEFAULT": "kltp://[2486:1::8888]:23333"
|
||||||
"DEFAULT": "127.0.0.1:25562"
|
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Listen": "{KLALB_Stream}[::0]:23330",
|
"Listen": "kltp://[::0]:25565",
|
||||||
"Bridge": {
|
"Bridge": {
|
||||||
"DEFAULT": "MinecraftSocketBridge23330"
|
"DEFAULT": "SocketBridge"
|
||||||
},
|
},
|
||||||
"Connect": {
|
"Connect": {
|
||||||
"DEFAULT": "127.0.0.1:25562"
|
"DEFAULT": "tcp://127.0.0.1:25566"
|
||||||
|
},
|
||||||
|
"Type": "SocketBridge"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Listen": "tcp://127.0.0.1:35565",
|
||||||
|
"Bridge": {
|
||||||
|
"DEFAULT": "SocketBridge"
|
||||||
|
},
|
||||||
|
"Connect": {
|
||||||
|
"DEFAULT": "kltp://[2486:1::8888]:25565"
|
||||||
},
|
},
|
||||||
"Type": "SocketBridge"
|
"Type": "SocketBridge"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
virtualip=cefe:49b8:f837:4b00:b9cd:dbdd:c299:349
|
|
||||||
bind=0.0.0.0:4569
|
|
||||||
local=127.0.0.1:5212
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"skills": {
|
||||||
|
"shadcn": {
|
||||||
|
"source": "shadcn/ui",
|
||||||
|
"sourceType": "github",
|
||||||
|
"skillPath": "skills/shadcn/SKILL.md",
|
||||||
|
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,10 @@ networkgraph=Network graph
|
|||||||
language=Language
|
language=Language
|
||||||
basicsettings=Basic settings
|
basicsettings=Basic settings
|
||||||
ipv6addr=IPv6 address
|
ipv6addr=IPv6 address
|
||||||
|
devicename=Device name
|
||||||
|
devicedescription=Device description
|
||||||
dnsserver=DNS server
|
dnsserver=DNS server
|
||||||
|
extraroutes=Extra routes
|
||||||
asnumber=AS number
|
asnumber=AS number
|
||||||
tcplistening=TCP listening
|
tcplistening=TCP listening
|
||||||
udplistening=UDP listening
|
udplistening=UDP listening
|
||||||
@@ -34,6 +37,7 @@ saveconfigsuccess=Save config success
|
|||||||
warning=Warning
|
warning=Warning
|
||||||
invaildipv6addr=IPv6 address:Invaild Input
|
invaildipv6addr=IPv6 address:Invaild Input
|
||||||
invailddnsserver=DNS server:Invaild Input
|
invailddnsserver=DNS server:Invaild Input
|
||||||
|
invaildextraroutes=Extra routes:Invaild Input
|
||||||
invaildasnumber=AS number:Invaild Input
|
invaildasnumber=AS number:Invaild Input
|
||||||
invaildtcplisten=TCP listen:Invaild Input
|
invaildtcplisten=TCP listen:Invaild Input
|
||||||
invaildudplisten=UDP listen:Invaild Input
|
invaildudplisten=UDP listen:Invaild Input
|
||||||
@@ -105,3 +109,10 @@ uploadbasedelay=Upload Base
|
|||||||
burstlimit=Burst limit
|
burstlimit=Burst limit
|
||||||
performancesettings=Performance settings
|
performancesettings=Performance settings
|
||||||
performancestrategy=Performance strategy
|
performancestrategy=Performance strategy
|
||||||
|
singlecore=Single-Core - Cache Affinity First (Best energy/performance ratio, for low-power & cloud)
|
||||||
|
multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose physical servers)
|
||||||
|
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
|
||||||
|
webapisettings=Web API settings
|
||||||
|
enablewebapi=Enable Web API
|
||||||
|
weblistenaddr=Web API listen address
|
||||||
|
invaildweblistenaddr=Invalid Web API listen address
|
||||||
|
|||||||
@@ -3,7 +3,10 @@ networkgraph=网络拓扑图
|
|||||||
language=语言
|
language=语言
|
||||||
basicsettings=基本设置
|
basicsettings=基本设置
|
||||||
ipv6addr=IPv6地址
|
ipv6addr=IPv6地址
|
||||||
|
devicename=设备名称
|
||||||
|
devicedescription=设备描述
|
||||||
dnsserver=DNS服务器
|
dnsserver=DNS服务器
|
||||||
|
extraroutes=额外路由
|
||||||
asnumber=AS号码
|
asnumber=AS号码
|
||||||
tcplistening=TCP监听端口
|
tcplistening=TCP监听端口
|
||||||
udplistening=UDP监听端口
|
udplistening=UDP监听端口
|
||||||
@@ -34,6 +37,7 @@ saveconfigsuccess=保存配置成功
|
|||||||
warning=警告
|
warning=警告
|
||||||
invaildipv6addr=IPv6地址:非法输入
|
invaildipv6addr=IPv6地址:非法输入
|
||||||
invailddnsserver=DNS服务器:非法输入
|
invailddnsserver=DNS服务器:非法输入
|
||||||
|
invaildextraroutes=额外路由:非法输入
|
||||||
invaildasnumber=AS号码:非法输入
|
invaildasnumber=AS号码:非法输入
|
||||||
invaildtcplisten=TCP监听端口:非法输入
|
invaildtcplisten=TCP监听端口:非法输入
|
||||||
invaildudplisten=UDP监听端口:非法输入
|
invaildudplisten=UDP监听端口:非法输入
|
||||||
@@ -105,3 +109,10 @@ uploadbasedelay=上传延迟基线
|
|||||||
burstlimit=突发限制
|
burstlimit=突发限制
|
||||||
performancesettings=性能设置
|
performancesettings=性能设置
|
||||||
performancestrategy=性能策略
|
performancestrategy=性能策略
|
||||||
|
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
|
||||||
|
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
|
||||||
|
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
|
||||||
|
webapisettings=Web API 设置
|
||||||
|
enablewebapi=启用 Web API
|
||||||
|
weblistenaddr=Web API 监听地址:端口
|
||||||
|
invaildweblistenaddr=无效的 Web API 监听地址:端口
|
||||||
|
|||||||
@@ -3,14 +3,9 @@ package org.kne.cloud.network;
|
|||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
|
|
||||||
public class DatagramSocketListener implements Closeable,AutoCloseable{
|
public class DatagramSocketListener implements Closeable,AutoCloseable{
|
||||||
private DatagramServerSocket serverSocket;
|
private DatagramServerSocket serverSocket;
|
||||||
public DatagramServerSocket getDatagramServerSocket() {
|
public DatagramServerSocket getDatagramServerSocket() {
|
||||||
@@ -36,10 +31,10 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public DatagramSocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public DatagramSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
|
public DatagramSocketListener(DatagramServerSocket tserverSocket) throws IOException {
|
||||||
@@ -49,7 +44,7 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
private void open() throws IOException {
|
private void open() throws IOException {
|
||||||
if(serverSocket==null)
|
if(serverSocket==null)
|
||||||
serverSocket=multipurposeSocketAddress.listenDatagramServerSocket();
|
serverSocket= multiProtocolSocketAddress.listenDatagramServerSocket();
|
||||||
//new Thread(r).start();
|
//new Thread(r).start();
|
||||||
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
|
ThreadTool.makePThreadIfSupport("端口监听线程",r).start();
|
||||||
}
|
}
|
||||||
@@ -63,8 +58,8 @@ public class DatagramSocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<DatagramSocket> getCon() {
|
public Consumer<DatagramSocket> getCon() {
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
|
||||||
public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress> {
|
public class HostPortMap extends LinkedHashMap<String, MultiProtocolSocketAddress> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -11,9 +10,9 @@ public class HostPortMap extends LinkedHashMap<String, MultipurposeSocketAddress
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress putN$H(String str) {
|
public MultiProtocolSocketAddress putN$H(String str) {
|
||||||
String[] strx=str.split("\\$");
|
String[] strx=str.split("\\$");
|
||||||
return super.put(strx[0], new MultipurposeSocketAddress( strx[1]));
|
return super.put(strx[0], new MultiProtocolSocketAddress( strx[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,8 @@ import java.io.DataInputStream;
|
|||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.*;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.MulticastSocket;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.NoRouteToHostException;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -29,12 +21,11 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
private NetworkInterface ninterface;
|
private NetworkInterface ninterface;
|
||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
private InetSocketAddress group;
|
private InetSocketAddress group;
|
||||||
private InetSocketAddress bind;
|
|
||||||
private int type;
|
private int type;
|
||||||
private List<MultipurposeSocketAddress> msas;
|
private List<MultiProtocolSocketAddress> msas;
|
||||||
private UUID selfUUID;
|
private UUID selfUUID;
|
||||||
|
|
||||||
private volatile Consumer<MultipurposeSocketAddress> con;
|
private volatile Consumer<MultiProtocolSocketAddress> con;
|
||||||
private long timeInterval;
|
private long timeInterval;
|
||||||
|
|
||||||
public long getTimeInterval() {
|
public long getTimeInterval() {
|
||||||
@@ -45,7 +36,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
this.timeInterval = timeInterval;
|
this.timeInterval = timeInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NetworkInterface getNinterface() {
|
public NetworkInterface getInterface() {
|
||||||
return ninterface;
|
return ninterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,22 +45,24 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getBind() {
|
public InetSocketAddress getBind() {
|
||||||
return bind;
|
return (InetSocketAddress) soc.getLocalSocketAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<MultipurposeSocketAddress> getCon() {
|
public Consumer<MultiProtocolSocketAddress> getCon() {
|
||||||
return con;
|
return con;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCon(Consumer<MultipurposeSocketAddress> con) {
|
public void setCon(Consumer<MultiProtocolSocketAddress> con) {
|
||||||
this.con = con;
|
this.con = con;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
|
public IPMulticastDiscovery(InetSocketAddress bind, InetSocketAddress group, NetworkInterface ninterface,
|
||||||
List<MultipurposeSocketAddress> msas,UUID node,long timeInterval) throws IOException {
|
List<MultiProtocolSocketAddress> msas, UUID node, long timeInterval) throws IOException {
|
||||||
soc = new MulticastSocket(bind);
|
soc = new MulticastSocket(null);
|
||||||
|
soc.setReuseAddress(true);
|
||||||
|
soc.bind(bind);
|
||||||
|
soc.setNetworkInterface(ninterface);
|
||||||
soc.joinGroup(group, ninterface);
|
soc.joinGroup(group, ninterface);
|
||||||
this.bind = bind;
|
|
||||||
this.group = group;
|
this.group = group;
|
||||||
this.ninterface = ninterface;
|
this.ninterface = ninterface;
|
||||||
this.msas = msas;
|
this.msas = msas;
|
||||||
@@ -82,46 +75,22 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
|
ThreadTool.makeVDaemonThreadIfSupport("线路通知线程", () -> {
|
||||||
try {
|
try {
|
||||||
while (!closed) {
|
while (!closed) {
|
||||||
HashSet<MultipurposeSocketAddress> s = new HashSet<MultipurposeSocketAddress>();
|
/*MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
||||||
|
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());*/
|
||||||
|
if(msas!=null) {
|
||||||
synchronized (msas) {
|
synchronized (msas) {
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = msas.iterator(); iterator.hasNext();) {
|
for (Iterator<MultiProtocolSocketAddress> iterator = msas.iterator(); iterator.hasNext(); ) {
|
||||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator
|
MultiProtocolSocketAddress multiProtocolSocketAddress = (MultiProtocolSocketAddress) iterator
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(
|
|
||||||
multipurposeSocketAddress.getType(), "::0", multipurposeSocketAddress.getPort());
|
|
||||||
try {
|
try {
|
||||||
if (s.add(mpsa)) {
|
send(multiProtocolSocketAddress);
|
||||||
boolean bf = true;
|
} catch (NoRouteToHostException | UnknownHostException e) {
|
||||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
|
||||||
while (ei.hasMoreElements()) {
|
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
|
||||||
if (inetAddress.equals(multipurposeSocketAddress.getInetAddress())) {
|
|
||||||
bf = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bf) {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
||||||
DataOutputStream dops = new DataOutputStream(baos);
|
|
||||||
dops.writeInt(multipurposeSocketAddress.getPort());
|
|
||||||
dops.writeUTF(multipurposeSocketAddress.getType());
|
|
||||||
dops.writeLong(selfUUID.getMostSignificantBits());
|
|
||||||
dops.writeLong(selfUUID.getLeastSignificantBits());
|
|
||||||
dops.close();
|
|
||||||
byte[] b = baos.toByteArray();
|
|
||||||
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
|
||||||
soc.send(dp);
|
|
||||||
if(debug)
|
|
||||||
System.out.println("发送广播:"+multipurposeSocketAddress+" "+group+" "+selfUUID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (NoRouteToHostException|UnknownHostException e) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Thread.sleep(timeInterval);
|
Thread.sleep(timeInterval);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -143,27 +112,8 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
try {
|
try {
|
||||||
loop:while (!closed) {
|
loop:while (!closed) {
|
||||||
try {
|
try {
|
||||||
byte[] b = new byte[65535];
|
MultiProtocolSocketAddress mpsa = receive();
|
||||||
DatagramPacket dp = new DatagramPacket(b, b.length);
|
if (mpsa == null) continue;
|
||||||
soc.receive(dp);
|
|
||||||
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
|
||||||
DataInputStream dis = new DataInputStream(bis);
|
|
||||||
int port = dis.readInt();
|
|
||||||
String type = dis.readUTF();
|
|
||||||
long h=dis.readLong();
|
|
||||||
long l=dis.readLong();
|
|
||||||
UUID uid=new UUID(h, l);
|
|
||||||
dis.close();
|
|
||||||
MultipurposeSocketAddress mpsa = new MultipurposeSocketAddress(type, dp.getAddress().getHostAddress(),
|
|
||||||
port);
|
|
||||||
if(debug) {
|
|
||||||
if(uid.equals(selfUUID)) {
|
|
||||||
System.out.println("丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
|
||||||
continue;
|
|
||||||
}else {
|
|
||||||
System.out.println("接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InetAddress mpsai=mpsa.getInetAddress();
|
InetAddress mpsai=mpsa.getInetAddress();
|
||||||
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
Enumeration<InetAddress> ei = ninterface.getInetAddresses();
|
||||||
while (ei.hasMoreElements()) {
|
while (ei.hasMoreElements()) {
|
||||||
@@ -186,7 +136,7 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
if(!isClosed())
|
if(!isClosed())
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}catch (IOException e) {
|
}catch (IOException e) {
|
||||||
System.out.println(bind + " " + group + " " + ninterface);
|
System.out.println(getBind() + " " + group + " " + ninterface);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
@@ -196,7 +146,48 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MultiProtocolSocketAddress receive() throws IOException {
|
||||||
|
byte[] b = new byte[65535];
|
||||||
|
DatagramPacket dp = new DatagramPacket(b, b.length);
|
||||||
|
soc.receive(dp);
|
||||||
|
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
|
||||||
|
DataInputStream dis = new DataInputStream(bis);
|
||||||
|
String val = dis.readUTF();
|
||||||
|
long h=dis.readLong();
|
||||||
|
long l=dis.readLong();
|
||||||
|
UUID uid=new UUID(h, l);
|
||||||
|
dis.close();
|
||||||
|
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(val);
|
||||||
|
if(mpsa.getInetAddress().isAnyLocalAddress()){
|
||||||
|
mpsa=new MultiProtocolSocketAddress(mpsa.getProtocol(),dp.getAddress().getHostAddress(),mpsa.getPort());
|
||||||
|
}
|
||||||
|
if(debug) {
|
||||||
|
if(uid.equals(selfUUID)) {
|
||||||
|
System.out.println(ninterface.getDisplayName()+" 丢弃广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||||
|
return null;
|
||||||
|
}else {
|
||||||
|
System.out.println(ninterface.getDisplayName()+ " 接收广播:"+mpsa+" "+dp.getSocketAddress()+" "+uid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mpsa;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void send(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream dops = new DataOutputStream(baos);
|
||||||
|
dops.writeUTF(multiProtocolSocketAddress.toString());
|
||||||
|
dops.writeLong(selfUUID.getMostSignificantBits());
|
||||||
|
dops.writeLong(selfUUID.getLeastSignificantBits());
|
||||||
|
dops.close();
|
||||||
|
byte[] b = baos.toByteArray();
|
||||||
|
DatagramPacket dp = new DatagramPacket(b, b.length, group);
|
||||||
|
soc.send(dp);
|
||||||
|
if(debug)
|
||||||
|
System.out.println(ninterface.getDisplayName()+" 发送广播:"+ multiProtocolSocketAddress +" "+group+" "+selfUUID);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+101
-95
@@ -3,14 +3,7 @@ package org.kne.cloud.network;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.DatagramSocket;
|
import java.net.*;
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -18,7 +11,6 @@ import java.util.*;
|
|||||||
import javax.net.ServerSocketFactory;
|
import javax.net.ServerSocketFactory;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
@@ -27,17 +19,18 @@ import com.google.gson.JsonParseException;
|
|||||||
import com.google.gson.JsonPrimitive;
|
import com.google.gson.JsonPrimitive;
|
||||||
import com.google.gson.JsonSerializationContext;
|
import com.google.gson.JsonSerializationContext;
|
||||||
import com.google.gson.JsonSerializer;
|
import com.google.gson.JsonSerializer;
|
||||||
|
import org.kne.cloud.network.ntp.NTPSocketType;
|
||||||
|
|
||||||
public class MultipurposeSocketAddress implements Serializable{
|
public class MultiProtocolSocketAddress implements Serializable{
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
|
private static final Map<String,SocketType> socketTypeRegister=new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
socketTypeRegister.put("TCP", new SocketType(new DefaultSocketFactory(), new DefaultServerSocketFactory(),new DefaultSocketChannelFactory(),new DefaultServerSocketChannelFactory()));
|
socketTypeRegister.put("tcp", TCPSocketType.getInstance());
|
||||||
socketTypeRegister.put("UDP", new SocketType(new DefaultDatagramSocketFactory(),new DefaultDatagramServerSocketFactory()));
|
socketTypeRegister.put("udp", UDPSocketType.getInstance());
|
||||||
|
socketTypeRegister.put("ntp", NTPSocketType.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -46,46 +39,59 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return socketTypeRegister;
|
return socketTypeRegister;
|
||||||
}
|
}
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private String type;
|
private String protocol;
|
||||||
private String host;
|
private String host;
|
||||||
private ProtocolStack ps=new ProtocolStack();
|
private ProtocolStack ps=new ProtocolStack();
|
||||||
|
|
||||||
|
|
||||||
private int port;
|
private int port;
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String hostport) {
|
public MultiProtocolSocketAddress(String hostport) {
|
||||||
this(hostport,"TCP");
|
this(hostport,"tcp");
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(String hostport,String defaulttype) {
|
public MultiProtocolSocketAddress(String hostport, String defaultprotocol) {
|
||||||
hostport=hostport.trim();
|
try {
|
||||||
if (hostport.startsWith("{")) {
|
if (!hostport.contains("://")) {
|
||||||
int i=hostport.indexOf("}");
|
hostport = defaultprotocol.toLowerCase() + "://" + hostport;
|
||||||
if(i==-1) {
|
|
||||||
throw new IllegalArgumentException("missing }");
|
|
||||||
}
|
}
|
||||||
type=hostport.substring(1, i);
|
URI uri = new URI(hostport);
|
||||||
hostport=hostport.substring(i+1);
|
|
||||||
}else {
|
this.protocol = uri.getScheme().toLowerCase();
|
||||||
type=defaulttype;
|
this.host = uri.getHost();
|
||||||
|
if (this.host == null) {
|
||||||
|
throw new IllegalArgumentException("无效的主机地址: " + hostport);
|
||||||
}
|
}
|
||||||
int index =hostport.lastIndexOf(":");
|
this.port = resolvePort(uri, this.protocol);
|
||||||
host=hostport.substring(0,index);
|
|
||||||
host=host.replace("[", "");
|
// 可选:处理查询参数
|
||||||
host=host.replace("]", "");
|
String query = uri.getQuery();
|
||||||
String tps=hostport.substring(index+1);
|
if (query != null && !query.isEmpty()) {
|
||||||
int v=tps.indexOf("(");
|
// 解析查询参数作为扩展信息
|
||||||
if(v!=-1) {
|
|
||||||
ps.add(new Protocol(tps.substring(v+1,tps.lastIndexOf(")") )));
|
|
||||||
tps=tps.substring(0, v);
|
|
||||||
}
|
}
|
||||||
port=Integer.parseInt(tps);
|
} catch (URISyntaxException e) {
|
||||||
if(port<0) {
|
throw new IllegalArgumentException("无效的地址格式: " + hostport, e);
|
||||||
throw new IllegalArgumentException("port < 0");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int resolvePort(URI uri, String protocol) {
|
||||||
|
int port = uri.getPort();
|
||||||
|
if (port != -1) {
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
SocketType socketType = socketTypeRegister.get(protocol);
|
||||||
|
if (socketType != null) {
|
||||||
|
int defaultPort = socketType.getDefaultPort();
|
||||||
|
if (defaultPort != -1) {
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"未指定端口号,且协议 " + protocol + " 没有默认端口: " + uri
|
||||||
|
);
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hash(host, port, ps, type);
|
return Objects.hash(host, port, ps, protocol);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
@@ -95,9 +101,9 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return false;
|
return false;
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||||
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
|
return Objects.equals(host, other.host) && port == other.port && Objects.equals(ps, other.ps)
|
||||||
&& Objects.equals(type, other.type);
|
&& Objects.equals(protocol, other.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean equals2(Object obj) {
|
public boolean equals2(Object obj) {
|
||||||
@@ -107,7 +113,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return false;
|
return false;
|
||||||
if (getClass() != obj.getClass())
|
if (getClass() != obj.getClass())
|
||||||
return false;
|
return false;
|
||||||
MultipurposeSocketAddress other = (MultipurposeSocketAddress) obj;
|
MultiProtocolSocketAddress other = (MultiProtocolSocketAddress) obj;
|
||||||
InetAddress addra=null;
|
InetAddress addra=null;
|
||||||
try {
|
try {
|
||||||
addra = InetAddress.getByName(host);
|
addra = InetAddress.getByName(host);
|
||||||
@@ -121,25 +127,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
boolean hostequals=Objects.equals(addra, addrb);
|
boolean hostequals=Objects.equals(addra, addrb);
|
||||||
|
|
||||||
return hostequals && port == other.port && Objects.equals(ps, other.ps)
|
return hostequals && port == other.port && Objects.equals(ps, other.ps)
|
||||||
&& Objects.equals(type, other.type);
|
&& Objects.equals(protocol, other.protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String host2, int port2) {
|
public MultiProtocolSocketAddress(String host2, int port2) {
|
||||||
this("TCP", host2, port2);
|
this("tcp", host2, port2);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress(String type,String host2, int port2) {
|
public MultiProtocolSocketAddress(String protocol, String host2, int port2) {
|
||||||
host=host2;
|
host=host2;
|
||||||
port=port2;
|
port=port2;
|
||||||
this.type=type;
|
this.protocol = protocol;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(InetSocketAddress remoteSocketAddress) {
|
public MultiProtocolSocketAddress(InetSocketAddress remoteSocketAddress) {
|
||||||
this("TCP",remoteSocketAddress);
|
this("tcp",remoteSocketAddress);
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress(String type,InetSocketAddress remoteSocketAddress) {
|
public MultiProtocolSocketAddress(String protocol, InetSocketAddress remoteSocketAddress) {
|
||||||
host=remoteSocketAddress.getAddress().getHostAddress();
|
host=remoteSocketAddress.getAddress().getHostAddress();
|
||||||
port=remoteSocketAddress.getPort();
|
port=remoteSocketAddress.getPort();
|
||||||
this.type=type;
|
this.protocol = protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -149,8 +155,8 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
public int getPort() {
|
public int getPort() {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
public String getType() {
|
public String getProtocol() {
|
||||||
return type;
|
return protocol;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetSocketAddress getSocketAddress() {
|
public InetSocketAddress getSocketAddress() {
|
||||||
@@ -159,27 +165,25 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb=new StringBuilder();
|
try {
|
||||||
if(!"TCP".equals(type)) {
|
// 获取协议的默认端口
|
||||||
sb.append('{').append(type).append('}');
|
SocketType socketType = socketTypeRegister.get(protocol);
|
||||||
}
|
int defaultPort = (socketType != null) ? socketType.getDefaultPort() : -1;
|
||||||
if(host.contains(":")) {
|
|
||||||
sb.append('[').append( host).append( "]:").append( port);
|
|
||||||
}else {
|
|
||||||
sb.append( host).append( ':').append( port);
|
|
||||||
}
|
|
||||||
if(!ps.isEmpty()) {
|
|
||||||
sb.append('(').append(ps.pop().getName()).append(')');
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 如果当前端口等于默认端口,则隐去端口号
|
||||||
|
int portToUse = (defaultPort != -1 && this.port == defaultPort) ? -1 : this.port;
|
||||||
|
|
||||||
|
URI uri = new URI(protocol, null, host, portToUse, null, null, null);
|
||||||
|
return uri.toString();
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return protocol + "://" + host + ":" + port;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
public Socket connectSocket(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -194,7 +198,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
public Socket connectSocket(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -209,7 +213,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket() throws UnknownHostException, IOException {
|
public Socket connectSocket() throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -223,7 +227,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
|
public Socket connectSocket(int timeout) throws UnknownHostException, IOException {
|
||||||
SocketFactory sf=socketTypeRegister.get(type).getSocketFactory();
|
SocketFactory sf=socketTypeRegister.get(protocol).getSocketFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -244,7 +248,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport,int timeout) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -265,7 +269,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(InetAddress bindip,int bindport) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -280,7 +284,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel() throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -294,7 +298,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
|
public SocketChannel connectSocketChannel(int timeout) throws UnknownHostException, IOException {
|
||||||
SocketChannelFactory sf=socketTypeRegister.get(type).getSocketChannelFactory();
|
SocketChannelFactory sf=socketTypeRegister.get(protocol).getSocketChannelFactory();
|
||||||
if(sf==null) {
|
if(sf==null) {
|
||||||
throw new UnsupportedOperationException("Socket Unsupported");
|
throw new UnsupportedOperationException("Socket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -319,7 +323,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
|
public ServerSocket listenServerSocket() throws UnknownHostException, IOException {
|
||||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -327,7 +331,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
|
public ServerSocket listenServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||||
ServerSocketFactory srf=socketTypeRegister.get(type).getServerSocketFactory();
|
ServerSocketFactory srf=socketTypeRegister.get(protocol).getServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -337,7 +341,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
|
public ServerSocketChannel listenServerSocketChannel() throws UnknownHostException, IOException {
|
||||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -345,7 +349,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
|
public ServerSocketChannel listenServerSocketChannel(int backlog) throws UnknownHostException, IOException {
|
||||||
ServerSocketChannelFactory srf=socketTypeRegister.get(type).getServerSocketChannelFactory();
|
ServerSocketChannelFactory srf=socketTypeRegister.get(protocol).getServerSocketChannelFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
throw new UnsupportedOperationException("ServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -355,13 +359,13 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
|
|
||||||
|
|
||||||
public boolean isStream() {
|
public boolean isStream() {
|
||||||
return checkIsStream(type);
|
return checkIsStream(protocol);
|
||||||
}
|
}
|
||||||
public static boolean checkIsStream(String type2) {
|
public static boolean checkIsStream(String type2) {
|
||||||
return socketTypeRegister.get(type2).isStream();
|
return socketTypeRegister.get(type2).isStream();
|
||||||
}
|
}
|
||||||
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
|
public DatagramSocket connectDatagramSocket(InetAddress bindip,int bindport) throws IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -376,7 +380,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return dgd;
|
return dgd;
|
||||||
}
|
}
|
||||||
public DatagramSocket connectDatagramSocket() throws IOException {
|
public DatagramSocket connectDatagramSocket() throws IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -390,7 +394,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return dgd;
|
return dgd;
|
||||||
}
|
}
|
||||||
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
|
public DatagramSocket listenDatagramSocket() throws UnknownHostException, IOException {
|
||||||
DatagramSocketFactory dgs=socketTypeRegister.get(type).getDatagramSocketFactory();
|
DatagramSocketFactory dgs=socketTypeRegister.get(protocol).getDatagramSocketFactory();
|
||||||
if(dgs==null) {
|
if(dgs==null) {
|
||||||
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -405,7 +409,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
|
public DatagramServerSocket listenDatagramServerSocket() throws UnknownHostException, IOException {
|
||||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -413,7 +417,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return sk;
|
return sk;
|
||||||
}
|
}
|
||||||
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
|
public DatagramServerSocket listenDatagramServerSocket(int backlog) throws UnknownHostException, IOException {
|
||||||
DatagramServerSocketFactory srf=socketTypeRegister.get(type).getDatagramServerSocketFactory();
|
DatagramServerSocketFactory srf=socketTypeRegister.get(protocol).getDatagramServerSocketFactory();
|
||||||
if(srf==null) {
|
if(srf==null) {
|
||||||
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
throw new UnsupportedOperationException("DatagramServerSocket Unsupported");
|
||||||
}
|
}
|
||||||
@@ -427,7 +431,7 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
return InetAddress.getAllByName(host);
|
return InetAddress.getAllByName(host);
|
||||||
}
|
}
|
||||||
public boolean supportNIO() {
|
public boolean supportNIO() {
|
||||||
SocketType stp= socketTypeRegister.get(getType());
|
SocketType stp= socketTypeRegister.get(getProtocol());
|
||||||
if(stp==null)
|
if(stp==null)
|
||||||
return false;
|
return false;
|
||||||
if(stp.getSocketChannelFactory()==null) {
|
if(stp.getSocketChannelFactory()==null) {
|
||||||
@@ -437,35 +441,37 @@ public class MultipurposeSocketAddress implements Serializable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JsonDeserializer<MultipurposeSocketAddress>getDefaultJsonDeserializer(){
|
public static JsonDeserializer<MultiProtocolSocketAddress>getDefaultJsonDeserializer(){
|
||||||
return new JsonDeserializer<MultipurposeSocketAddress>() {
|
return new JsonDeserializer<MultiProtocolSocketAddress>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MultipurposeSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
public MultiProtocolSocketAddress deserialize(JsonElement arg0, Type arg1, JsonDeserializationContext arg2)
|
||||||
throws JsonParseException {
|
throws JsonParseException {
|
||||||
if(arg0.isJsonPrimitive()) {
|
if(arg0.isJsonPrimitive()) {
|
||||||
JsonPrimitive jp=(JsonPrimitive) arg0;
|
JsonPrimitive jp=(JsonPrimitive) arg0;
|
||||||
if(jp.isString()) {
|
if(jp.isString()) {
|
||||||
return new MultipurposeSocketAddress(jp.getAsString());
|
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(jp.getAsString());
|
||||||
|
// System.out.println(msa);
|
||||||
|
return msa;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new JsonParseException("not a string:"+arg0);
|
throw new JsonParseException("not a string:"+arg0);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static JsonSerializer<MultipurposeSocketAddress>getDefaultJsonSerializer(){
|
public static JsonSerializer<MultiProtocolSocketAddress>getDefaultJsonSerializer(){
|
||||||
return new JsonSerializer<MultipurposeSocketAddress>() {
|
return new JsonSerializer<MultiProtocolSocketAddress>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public JsonElement serialize(MultipurposeSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
public JsonElement serialize(MultiProtocolSocketAddress arg0, Type arg1, JsonSerializationContext arg2) {
|
||||||
return new JsonPrimitive(arg0.toString());
|
return new JsonPrimitive(arg0.toString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
|
public static void registerToGsonBuilder(GsonBuilder gsonBuilder) {
|
||||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonDeserializer());
|
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonDeserializer());
|
||||||
gsonBuilder.registerTypeAdapter(MultipurposeSocketAddress.class, getDefaultJsonSerializer());
|
gsonBuilder.registerTypeAdapter(MultiProtocolSocketAddress.class, getDefaultJsonSerializer());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
public interface NetworkService {
|
public interface NetworkService {
|
||||||
public void listen(MultipurposeSocketAddress msa);
|
public void listen(MultiProtocolSocketAddress msa);
|
||||||
public void unlisten(MultipurposeSocketAddress msc);
|
public void unlisten(MultiProtocolSocketAddress msc);
|
||||||
|
|
||||||
public void connect(MultipurposeSocketAddress msa);
|
public void connect(MultiProtocolSocketAddress msa);
|
||||||
public void unconnect(MultipurposeSocketAddress msc);
|
public void unconnect(MultiProtocolSocketAddress msc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.channels.*;
|
|
||||||
import java.nio.channels.spi.AbstractInterruptibleChannel;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
public class PortRelay {
|
public class PortRelay {
|
||||||
private int port;
|
private int port;
|
||||||
@@ -16,8 +10,8 @@ public class PortRelay {
|
|||||||
public PortRelay(int port, HostPortMap services) throws IOException {
|
public PortRelay(int port, HostPortMap services) throws IOException {
|
||||||
this.services = services;
|
this.services = services;
|
||||||
this.port = port;
|
this.port = port;
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
MultiProtocolSocketAddress.getSocketTypeRegister().put("ProtocolDetectorServerSocket",new SocketType(null, new ProtocolDetectorServerSocketFactory()) );
|
||||||
ssc=new SocketListener(new MultipurposeSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
ssc=new SocketListener(new MultiProtocolSocketAddress("ProtocolDetectorServerSocket", "0.0.0.0", port));
|
||||||
}
|
}
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
ssc.setCon((s)->{
|
ssc.setCon((s)->{
|
||||||
@@ -30,7 +24,7 @@ public class PortRelay {
|
|||||||
}else {
|
}else {
|
||||||
nx=pds.getProtocolStack().pop().getName();
|
nx=pds.getProtocolStack().pop().getName();
|
||||||
}
|
}
|
||||||
MultipurposeSocketAddress hp=services.get(nx);
|
MultiProtocolSocketAddress hp=services.get(nx);
|
||||||
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
|
InetSocketAddress sa=(InetSocketAddress) s.getRemoteSocketAddress();
|
||||||
System.out.println(sa.getAddress().getHostAddress() + ":" + port
|
System.out.println(sa.getAddress().getHostAddress() + ":" + port
|
||||||
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
|
+ "-" + "(" + nx + ")->" + hp.getHost() + ":" + hp.getPort());
|
||||||
|
|||||||
@@ -1,23 +1,19 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ServiceElement {
|
public class ServiceElement {
|
||||||
public String name;
|
public String name;
|
||||||
public String protocol;
|
public String protocol;
|
||||||
public MultipurposeSocketAddress ipport;
|
public MultiProtocolSocketAddress ipport;
|
||||||
public ServiceElement(String ini) throws UnknownHostException {
|
public ServiceElement(String ini) throws UnknownHostException {
|
||||||
String[]t=ini.split("\\$");
|
String[]t=ini.split("\\$");
|
||||||
if(t.length==1) {
|
if(t.length==1) {
|
||||||
protocol="";
|
protocol="";
|
||||||
ipport=new MultipurposeSocketAddress(t[0]);
|
ipport=new MultiProtocolSocketAddress(t[0]);
|
||||||
}else {
|
}else {
|
||||||
protocol=t[0];
|
protocol=t[0];
|
||||||
ipport=new MultipurposeSocketAddress(t[1]);
|
ipport=new MultiProtocolSocketAddress(t[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public ServiceElement() {
|
public ServiceElement() {
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class ServiceToSocketProxy extends Proxy{
|
public class ServiceToSocketProxy extends Proxy{
|
||||||
public NetworkService getSrc() {
|
public NetworkService getSrc() {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress getDes() {
|
public MultiProtocolSocketAddress getDes() {
|
||||||
return des;
|
return des;
|
||||||
}
|
}
|
||||||
public ServiceToSocketProxy(String l, String r) {
|
public ServiceToSocketProxy(String l, String r) {
|
||||||
src=getRegister().get(l.substring(1, l.length()-1));
|
src=getRegister().get(l.substring(1, l.length()-1));
|
||||||
des=new MultipurposeSocketAddress(r);
|
des=new MultiProtocolSocketAddress(r);
|
||||||
}
|
}
|
||||||
private NetworkService src;
|
private NetworkService src;
|
||||||
private MultipurposeSocketAddress des;
|
private MultiProtocolSocketAddress des;
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// TODO 自动生成的方法存根
|
// TODO 自动生成的方法存根
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public SocketChannelListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public SocketChannelListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
|
public SocketChannelListener(ServerSocketChannel tserverSocket) throws IOException {
|
||||||
@@ -62,9 +62,9 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
protected void open() throws UnknownHostException, IOException {
|
protected void open() throws UnknownHostException, IOException {
|
||||||
if(serverSocketChannel==null)
|
if(serverSocketChannel==null)
|
||||||
serverSocketChannel=multipurposeSocketAddress.listenServerSocketChannel();
|
serverSocketChannel= multiProtocolSocketAddress.listenServerSocketChannel();
|
||||||
if(serverSocketChannel==null) {
|
if(serverSocketChannel==null) {
|
||||||
throw new IOException(multipurposeSocketAddress.getType()+" unsupport SocketChannel");
|
throw new IOException(multiProtocolSocketAddress.getProtocol()+" unsupport SocketChannel");
|
||||||
}
|
}
|
||||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||||
}
|
}
|
||||||
@@ -82,8 +82,8 @@ public class SocketChannelListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<SocketChannel> getCon() {
|
public Consumer<SocketChannel> getCon() {
|
||||||
|
|||||||
@@ -2,15 +2,12 @@ package org.kne.cloud.network;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.net.ServerSocketFactory;
|
|
||||||
|
|
||||||
public class SocketListener implements Closeable,AutoCloseable{
|
public class SocketListener implements Closeable,AutoCloseable{
|
||||||
protected ServerSocket serverSocket;
|
protected ServerSocket serverSocket;
|
||||||
public ServerSocket getServerSocket() {
|
public ServerSocket getServerSocket() {
|
||||||
@@ -53,10 +50,10 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private MultipurposeSocketAddress multipurposeSocketAddress;
|
private MultiProtocolSocketAddress multiProtocolSocketAddress;
|
||||||
|
|
||||||
public SocketListener(MultipurposeSocketAddress multipurposeSocketAddress) throws IOException {
|
public SocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
|
||||||
this.multipurposeSocketAddress=multipurposeSocketAddress;
|
this.multiProtocolSocketAddress = multiProtocolSocketAddress;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
public SocketListener(ServerSocket tserverSocket) throws IOException {
|
public SocketListener(ServerSocket tserverSocket) throws IOException {
|
||||||
@@ -65,7 +62,7 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
}
|
}
|
||||||
protected void open() throws UnknownHostException, IOException {
|
protected void open() throws UnknownHostException, IOException {
|
||||||
if(serverSocket==null)
|
if(serverSocket==null)
|
||||||
serverSocket=multipurposeSocketAddress.listenServerSocket();
|
serverSocket= multiProtocolSocketAddress.listenServerSocket();
|
||||||
ThreadTool.makeVThread("端口监听线程", r).start();
|
ThreadTool.makeVThread("端口监听线程", r).start();
|
||||||
}
|
}
|
||||||
public void close() {
|
public void close() {
|
||||||
@@ -82,8 +79,8 @@ public class SocketListener implements Closeable,AutoCloseable{
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getMultipurposeSocketAddress() {
|
public MultiProtocolSocketAddress getMultipurposeSocketAddress() {
|
||||||
return multipurposeSocketAddress;
|
return multiProtocolSocketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Consumer<Socket> getCon() {
|
public Consumer<Socket> getCon() {
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
package org.kne.cloud.network;
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Socket;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class SocketToServiceProxy extends Proxy {
|
public class SocketToServiceProxy extends Proxy {
|
||||||
public SocketToServiceProxy(String l, String r) {
|
public SocketToServiceProxy(String l, String r) {
|
||||||
src=new MultipurposeSocketAddress(l);
|
src=new MultiProtocolSocketAddress(l);
|
||||||
des=getRegister().get(r.substring(1, r.length()-1)) ;
|
des=getRegister().get(r.substring(1, r.length()-1)) ;
|
||||||
}
|
}
|
||||||
public MultipurposeSocketAddress getSrc() {
|
public MultiProtocolSocketAddress getSrc() {
|
||||||
return src;
|
return src;
|
||||||
}
|
}
|
||||||
public NetworkService getDes() {
|
public NetworkService getDes() {
|
||||||
return des;
|
return des;
|
||||||
}
|
}
|
||||||
private MultipurposeSocketAddress src;
|
private MultiProtocolSocketAddress src;
|
||||||
private NetworkService des;
|
private NetworkService des;
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
|
|||||||
@@ -4,10 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketOption;
|
|
||||||
import java.net.SocketOptions;
|
|
||||||
import java.net.StandardSocketOptions;
|
import java.net.StandardSocketOptions;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -15,8 +12,8 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
private SocketListener sl;
|
private SocketListener sl;
|
||||||
private SocketChannelListener scl;
|
private SocketChannelListener scl;
|
||||||
private DatagramSocketListener dsl;
|
private DatagramSocketListener dsl;
|
||||||
private MultipurposeSocketAddress listen, cbind, defaultConnect;
|
private MultiProtocolSocketAddress listen, cbind, defaultConnect;
|
||||||
private Map<String, MultipurposeSocketAddress> detectedConnect;
|
private Map<String, MultiProtocolSocketAddress> detectedConnect;
|
||||||
private SocketBridgeFactory defaultFactory;
|
private SocketBridgeFactory defaultFactory;
|
||||||
private SocketChannelBridgeFactory defaultChannelFactory;
|
private SocketChannelBridgeFactory defaultChannelFactory;
|
||||||
private Map<String, SocketBridgeFactory> detectedFactory;
|
private Map<String, SocketBridgeFactory> detectedFactory;
|
||||||
@@ -30,28 +27,28 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
this.defaultSoTimeout = defaultSoTimeout;
|
this.defaultSoTimeout = defaultSoTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress connect) throws IOException {
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress connect) throws IOException {
|
||||||
this(listen, new MultipurposeSocketAddress(new InetSocketAddress(0)), connect);
|
this(listen, new MultiProtocolSocketAddress(new InetSocketAddress(0)), connect);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(String l, String r) throws IOException {
|
public SocketToSocketProxy(String l, String r) throws IOException {
|
||||||
this(new MultipurposeSocketAddress(l), new MultipurposeSocketAddress(r));
|
this(new MultiProtocolSocketAddress(l), new MultiProtocolSocketAddress(r));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
|
this(listen,cbind,defaultConnect,detectedConnect,new DefaultSocketBridgeFactory(),null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
|
this(listen, cbind, defaultConnect, detectedConnect, defaultFactory, detectedFactory, new DefaultSocketChannelBridgeFactory());
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect, Map<String, MultipurposeSocketAddress> detectedConnect,SocketBridgeFactory defaultFactory,Map<String, SocketBridgeFactory> detectedFactory,SocketChannelBridgeFactory defaultChannelFactory)
|
MultiProtocolSocketAddress defaultConnect, Map<String, MultiProtocolSocketAddress> detectedConnect, SocketBridgeFactory defaultFactory, Map<String, SocketBridgeFactory> detectedFactory, SocketChannelBridgeFactory defaultChannelFactory)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this.listen = listen;
|
this.listen = listen;
|
||||||
this.cbind = cbind;
|
this.cbind = cbind;
|
||||||
@@ -66,8 +63,8 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SocketToSocketProxy(MultipurposeSocketAddress listen, MultipurposeSocketAddress cbind,
|
public SocketToSocketProxy(MultiProtocolSocketAddress listen, MultiProtocolSocketAddress cbind,
|
||||||
MultipurposeSocketAddress defaultConnect) throws IOException {
|
MultiProtocolSocketAddress defaultConnect) throws IOException {
|
||||||
this(listen, cbind, defaultConnect,null);
|
this(listen, cbind, defaultConnect,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +109,7 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
|
ProtocolStack ps = ((ProtocolDetectorSocket) sox).getProtocolStack();
|
||||||
if (!ps.isEmpty()) {
|
if (!ps.isEmpty()) {
|
||||||
String pname=ps.pop().getName();
|
String pname=ps.pop().getName();
|
||||||
MultipurposeSocketAddress pmsa = detectedConnect.get(pname);
|
MultiProtocolSocketAddress pmsa = detectedConnect.get(pname);
|
||||||
if(pmsa!=null) {
|
if(pmsa!=null) {
|
||||||
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
|
sk = pmsa.connectSocket(InetAddress.getByName(cbind.getHost()), cbind.getPort());
|
||||||
}else {
|
}else {
|
||||||
@@ -178,19 +175,19 @@ public class SocketToSocketProxy extends Proxy {
|
|||||||
sb.run();
|
sb.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getListen() {
|
public MultiProtocolSocketAddress getListen() {
|
||||||
return listen;
|
return listen;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getCbind() {
|
public MultiProtocolSocketAddress getCbind() {
|
||||||
return cbind;
|
return cbind;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getDefaultConnect() {
|
public MultiProtocolSocketAddress getDefaultConnect() {
|
||||||
return defaultConnect;
|
return defaultConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, MultipurposeSocketAddress> getDetectedConnect() {
|
public Map<String, MultiProtocolSocketAddress> getDetectedConnect() {
|
||||||
return detectedConnect;
|
return detectedConnect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public class SocketType {
|
|||||||
private DatagramSocketFactory datagramSocketFactory;
|
private DatagramSocketFactory datagramSocketFactory;
|
||||||
private DatagramServerSocketFactory datagramServerSocketFactory;
|
private DatagramServerSocketFactory datagramServerSocketFactory;
|
||||||
private boolean stream;
|
private boolean stream;
|
||||||
|
private int defaultPort=-1;
|
||||||
|
|
||||||
public SocketFactory Channel() {
|
public SocketFactory Channel() {
|
||||||
return socketFactory;
|
return socketFactory;
|
||||||
@@ -74,5 +75,7 @@ public class SocketType {
|
|||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getDefaultPort(){
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
|
public class TCPSocketType extends SocketType {
|
||||||
|
private static final TCPSocketType INSTANCE = new TCPSocketType();
|
||||||
|
|
||||||
|
private TCPSocketType() {
|
||||||
|
super(
|
||||||
|
new DefaultSocketFactory(),
|
||||||
|
new DefaultServerSocketFactory(),
|
||||||
|
new DefaultSocketChannelFactory(),
|
||||||
|
new DefaultServerSocketChannelFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TCPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStream() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package org.kne.cloud.network;
|
||||||
|
|
||||||
|
public class UDPSocketType extends SocketType {
|
||||||
|
private static final UDPSocketType INSTANCE = new UDPSocketType();
|
||||||
|
|
||||||
|
protected UDPSocketType() {
|
||||||
|
super(
|
||||||
|
new DefaultDatagramSocketFactory(),
|
||||||
|
new DefaultDatagramServerSocketFactory()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UDPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isStream() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,16 +4,13 @@ import java.io.IOException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketImpl;
|
import java.net.SocketImpl;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.ini4j.Profile.Section;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
public class FrpcServerSocket extends ServerSocket {
|
public class FrpcServerSocket extends ServerSocket {
|
||||||
private FrpcProcess frpc;
|
private FrpcProcess frpc;
|
||||||
@@ -76,10 +73,10 @@ public class FrpcServerSocket extends ServerSocket {
|
|||||||
return frpcini.getRemotePort(tunnel);
|
return frpcini.getRemotePort(tunnel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTunnelSocketAddress() {
|
public MultiProtocolSocketAddress getTunnelSocketAddress() {
|
||||||
if (!isBound())
|
if (!isBound())
|
||||||
return null;
|
return null;
|
||||||
return new MultipurposeSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
return new MultiProtocolSocketAddress(frpcini.getType(tunnel).toUpperCase(),getTunnelHost(), getTunnelPort());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -116,9 +116,7 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
|||||||
}*/
|
}*/
|
||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
import java.io.DataOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
@@ -128,15 +126,14 @@ import java.net.SocketException;
|
|||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket;
|
import org.kne.cloud.network.DatagramServerSocket;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
|
||||||
|
|
||||||
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class DatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatagramSocket ds;
|
private DatagramSocket ds;
|
||||||
|
|||||||
@@ -1,39 +1,31 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import javax.annotation.processing.Filer;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import javax.lang.model.element.Element;
|
|
||||||
import javax.tools.FileObject;
|
|
||||||
import javax.tools.JavaFileObject;
|
|
||||||
import javax.tools.JavaFileManager.Location;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonIOException;
|
import com.google.gson.JsonIOException;
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
public class KLALBConfig extends ArrayList<KLALBConfigItem>{
|
public class KLALBConfig extends ArrayList<KLALBConfigItem>{
|
||||||
|
|
||||||
|
public KLALBConfig(){
|
||||||
|
}
|
||||||
|
public static KLALBConfig getDefault(){
|
||||||
|
KLALBConfig config=new KLALBConfig();
|
||||||
|
config.add(new KLALBControllerConfigItem());
|
||||||
|
return config;
|
||||||
|
}
|
||||||
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
|
public static void main(String[] args) throws UnknownHostException, JsonSyntaxException, JsonIOException, FileNotFoundException {
|
||||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||||
Gson gson=gb.create();
|
Gson gson=gb.create();
|
||||||
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
|
KLALBConfig kc=gson.fromJson(new FileReader(new File("klalb-config.json")),KLALBConfig.class);
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.JsonDeserializationContext;
|
import com.google.gson.JsonDeserializationContext;
|
||||||
import com.google.gson.JsonDeserializer;
|
import com.google.gson.JsonDeserializer;
|
||||||
@@ -69,6 +62,7 @@ public class KLALBConfigItem {
|
|||||||
String s=jobj.get("Type").getAsString();
|
String s=jobj.get("Type").getAsString();
|
||||||
switch(s) {
|
switch(s) {
|
||||||
case "KLALBController":
|
case "KLALBController":
|
||||||
|
normalizeLegacyControllerKeys(jobj);
|
||||||
return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType());
|
return arg2.deserialize(arg0, new TypeToken<KLALBControllerConfigItem>() {}.getType());
|
||||||
case "SocketBridge":
|
case "SocketBridge":
|
||||||
return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType());
|
return arg2.deserialize(arg0, new TypeToken<SocketBridgeConfigItem>() {}.getType());
|
||||||
@@ -78,6 +72,39 @@ public class KLALBConfigItem {
|
|||||||
}
|
}
|
||||||
throw new JsonParseException("not a object:"+arg0);
|
throw new JsonParseException("not a object:"+arg0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将历史命名错误的配置键归一化为当前键名(仅在当前键名不存在时生效):
|
||||||
|
* openConnections/lineTable → externalEndpoints,
|
||||||
|
* denyConnectionQuery/denyLineTableQuery → denyExternalEndpointQuery,
|
||||||
|
* denyConnectionBroadcast/denyLineTableBroadcast → denyExternalEndpointBroadcast。
|
||||||
|
*/
|
||||||
|
private void normalizeLegacyControllerKeys(JsonObject jobj) {
|
||||||
|
renameLegacyKey(jobj,"externalEndpoints","openConnections","OpenConnections","lineTable","LineTable");
|
||||||
|
renameLegacyKey(jobj,"denyExternalEndpointQuery","denyConnectionQuery","denyLineTableQuery");
|
||||||
|
renameLegacyKey(jobj,"denyExternalEndpointBroadcast","denyConnectionBroadcast","denyLineTableBroadcast");
|
||||||
|
if (!jobj.has("webListen") && jobj.has("webPort") && !jobj.get("webPort").isJsonNull()) {
|
||||||
|
int port = jobj.get("webPort").getAsInt();
|
||||||
|
jobj.add("webListen", new JsonPrimitive("http://0.0.0.0:" + port));
|
||||||
|
}
|
||||||
|
jobj.remove("webPort");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renameLegacyKey(JsonObject jobj, String newKey, String... legacyKeys) {
|
||||||
|
if (jobj.has(newKey)) {
|
||||||
|
for (String legacyKey : legacyKeys) {
|
||||||
|
jobj.remove(legacyKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String legacyKey : legacyKeys) {
|
||||||
|
if (jobj.has(legacyKey)) {
|
||||||
|
jobj.add(newKey, jobj.get(legacyKey));
|
||||||
|
jobj.remove(legacyKey);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
public static JsonSerializer<KLALBConfigItem>getDefaultJsonSerializer(){
|
public static JsonSerializer<KLALBConfigItem>getDefaultJsonSerializer(){
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import java.util.function.Consumer;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.IPMulticastDiscovery;
|
import org.kne.cloud.network.IPMulticastDiscovery;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.PortPair;
|
import org.kne.cloud.network.PortPair;
|
||||||
import org.kne.cloud.network.SocketType;
|
import org.kne.cloud.network.SocketType;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
@@ -67,7 +67,7 @@ public class KLALBController {
|
|||||||
public HighAccuracyClock getClock() {
|
public HighAccuracyClock getClock() {
|
||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
private final long TIME_WINDOW=5000000000L;
|
private final long TIME_WINDOW=2000000000L;
|
||||||
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
|
private SpeedAndTrafficMonitorDataImpl linkMonitor = new SpeedAndTrafficMonitorDataImpl(
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100,TIME_WINDOW ),
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||||
@@ -76,8 +76,13 @@ public class KLALBController {
|
|||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "up", 100, TIME_WINDOW),
|
||||||
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
new HashMapTimestampMonitor<UUID>(HighAccuracyClock.SYSTEM_CLOCK, "down", 100, TIME_WINDOW));
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> selflineTable = new ArrayList<>();
|
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
|
||||||
|
|
||||||
|
private List<MultiProtocolSocketAddress> listensSocketAddress = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getListenSocketAddress() {
|
||||||
|
return listensSocketAddress;
|
||||||
|
}
|
||||||
private static final boolean debug=false;
|
private static final boolean debug=false;
|
||||||
private static final boolean showpacket = false;
|
private static final boolean showpacket = false;
|
||||||
|
|
||||||
@@ -86,10 +91,12 @@ public class KLALBController {
|
|||||||
|
|
||||||
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
|
private static List<IPMulticastDiscovery> ipmd = new ArrayList<>();
|
||||||
|
|
||||||
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
private NetworkInterfaceManager networkInterfaceManager=new NetworkInterfaceManager();
|
||||||
|
public NetworkInterfaceManager getNetworkInterfaceManager() {
|
||||||
|
return networkInterfaceManager;
|
||||||
|
}
|
||||||
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||||
return networkInterfaceExcept;
|
return networkInterfaceManager.getNetworkInterfaceExcept();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<InetAddress> dnsAddresses = new ArrayList<>();
|
private List<InetAddress> dnsAddresses = new ArrayList<>();
|
||||||
@@ -111,55 +118,28 @@ public class KLALBController {
|
|||||||
|
|
||||||
private Timer twk = new Timer("网卡检测扫描计时器", true);
|
private Timer twk = new Timer("网卡检测扫描计时器", true);
|
||||||
|
|
||||||
private List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
|
||||||
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
|
||||||
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
|
||||||
while (eu.hasMoreElements()) {
|
|
||||||
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
|
||||||
|
|
||||||
if (networkInterface.getDisplayName()
|
|
||||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (networkInterface.isUp()) {
|
|
||||||
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
|
||||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
|
||||||
while (ei.hasMoreElements()) {
|
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
|
||||||
if (checkIsSelfLocator(inetAddress)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
addresses.add(inetAddress);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TimerTask tsk1 = new TimerTask() {
|
private TimerTask tsk1 = new TimerTask() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
List<InetAddress> localaddress = getAllNetworkInterfaceAddress();
|
List<InetAddress> localaddress = networkInterfaceManager.getAllNetworkInterfaceAddress();
|
||||||
|
|
||||||
for (InetAddress inetAddress : localaddress) {
|
for (InetAddress inetAddress : localaddress) {
|
||||||
if (!inetAddress.isLoopbackAddress())
|
for (Iterator<MultiProtocolSocketAddress> iterator = listensSocketAddress.iterator(); iterator.hasNext();) {
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = listens.iterator(); iterator.hasNext();) {
|
MultiProtocolSocketAddress tcpl = (MultiProtocolSocketAddress) iterator.next();
|
||||||
MultipurposeSocketAddress tcpl = (MultipurposeSocketAddress) iterator.next();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (tcpl.getInetAddress().isAnyLocalAddress()
|
if (tcpl.getInetAddress().isAnyLocalAddress()
|
||||||
|| tcpl.getInetAddress().equals(inetAddress)) {
|
|| tcpl.getInetAddress().equals(inetAddress)) {
|
||||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(tcpl.getType(),
|
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(tcpl.getProtocol(),
|
||||||
inetAddress.getHostAddress(), tcpl.getPort());
|
inetAddress.getHostAddress(), tcpl.getPort());
|
||||||
// System.out.println(bind);
|
// System.out.println(bind);
|
||||||
synchronized (selflineTable) {
|
synchronized (externalEndpoints) {
|
||||||
if (!selflineTable.contains(bind)) {
|
if (!externalEndpoints.contains(bind)) {
|
||||||
selflineTable.add(bind);
|
externalEndpoints.add(bind);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,9 +155,8 @@ public class KLALBController {
|
|||||||
try {
|
try {
|
||||||
|
|
||||||
|
|
||||||
Set<MultipurposeSocketAddress> st = new HashSet<>();
|
Set<MultiProtocolSocketAddress> st = new HashSet<>();
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
|
||||||
if (link instanceof KLALBRemoteLink) {
|
if (link instanceof KLALBRemoteLink) {
|
||||||
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
|
KLALBRemoteLink multipurposeSocketAddress = (KLALBRemoteLink) link;
|
||||||
if (multipurposeSocketAddress.getSocketAddress() != null)
|
if (multipurposeSocketAddress.getSocketAddress() != null)
|
||||||
@@ -185,24 +164,24 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = st.iterator(); iterator.hasNext();) {
|
for (MultiProtocolSocketAddress target : st) {
|
||||||
MultipurposeSocketAddress target = (MultipurposeSocketAddress) iterator.next();
|
|
||||||
addRemoteLines(target);
|
addRemoteLines(target);
|
||||||
}
|
}
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
|
||||||
IPv6NetworkLink link = iterator.next();
|
/*
|
||||||
|
for (IPv6NetworkLink link : srv6Router.getLinkTabel()) {
|
||||||
if (link instanceof KLALBRemoteLink) {
|
if (link instanceof KLALBRemoteLink) {
|
||||||
KLALBRemoteLink reml = (KLALBRemoteLink) link;
|
KLALBRemoteLink reml = (KLALBRemoteLink) link;
|
||||||
if (reml.getSocketAddress() != null) {
|
if (reml.getSocketAddress() != null) {
|
||||||
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
|
if (reml.getBindAddress() != null || reml.getRemoteVaddr() != null)
|
||||||
try {
|
try {
|
||||||
MultipurposeSocketAddress iadr=reml.getBindAddress();
|
MultipurposeSocketAddress iadr = reml.getBindAddress();
|
||||||
if(iadr!=null) {
|
if (iadr != null) {
|
||||||
if (!localaddress.contains(iadr.getInetAddress())) {
|
if (!localaddress.contains(iadr.getInetAddress())) {
|
||||||
reml.close();
|
reml.close();
|
||||||
srv6Router.getLinkTabel().remove(reml);
|
srv6Router.getLinkTabel().remove(reml);
|
||||||
}
|
}
|
||||||
}else {
|
} else {
|
||||||
|
|
||||||
reml.close();
|
reml.close();
|
||||||
}
|
}
|
||||||
@@ -214,7 +193,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
} finally {
|
} finally {
|
||||||
lineslock.writeLock().unlock();
|
lineslock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
@@ -228,47 +207,31 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateBroadcastNetworkInterface() throws SocketException {
|
private void updateBroadcastNetworkInterface() throws SocketException {
|
||||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext(); ) {
|
||||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
||||||
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getNinterface().isUp())
|
if (ipMulticastDiscovery.isClosed() || (!ipMulticastDiscovery.getInterface().isUp())
|
||||||
|| (configItem != null && configItem.isDenyLineTableBroadcast())) {
|
|| (configItem != null && configItem.isDenyExternalEndpointBroadcast())) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
try {
|
try {
|
||||||
ipMulticastDiscovery.close();
|
ipMulticastDiscovery.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
if(debug)
|
if (debug)
|
||||||
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getNinterface());
|
System.out.println("关闭网卡地址广播:" + ipMulticastDiscovery.getInterface());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configItem != null && configItem.isDenyLineTableBroadcast()) {
|
if (configItem != null && configItem.isDenyExternalEndpointBroadcast()) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
Enumeration<NetworkInterface> eu2 = NetworkInterface.getNetworkInterfaces();
|
List<NetworkInterface> interfaceList = networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||||
while (eu2.hasMoreElements()) {
|
for(NetworkInterface networkInterface:interfaceList){
|
||||||
NetworkInterface networkInterface = (NetworkInterface) eu2.nextElement();
|
List<InetAddress> addressList= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||||
if (networkInterface.getDisplayName()
|
loop:for (InetAddress bidr:addressList) {
|
||||||
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkInterfaceExcept.contains(networkInterface)) {
|
for (IPMulticastDiscovery ipMulticastDiscovery : ipmd) {
|
||||||
continue;
|
if (networkInterface.equals(ipMulticastDiscovery.getInterface())
|
||||||
}
|
|
||||||
|
|
||||||
if (networkInterface.isUp()) {
|
|
||||||
|
|
||||||
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
|
||||||
loop: while (ei.hasMoreElements()) {
|
|
||||||
InetAddress bidr = (InetAddress) ei.nextElement();
|
|
||||||
if (checkIsSelfLocator(bidr)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (Iterator<IPMulticastDiscovery> iterator = ipmd.iterator(); iterator.hasNext();) {
|
|
||||||
IPMulticastDiscovery ipMulticastDiscovery = (IPMulticastDiscovery) iterator.next();
|
|
||||||
if (networkInterface.equals(ipMulticastDiscovery.getNinterface())
|
|
||||||
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
|
&& bidr.equals(ipMulticastDiscovery.getBind().getAddress())) {
|
||||||
continue loop;
|
continue loop;
|
||||||
}
|
}
|
||||||
@@ -278,13 +241,13 @@ public class KLALBController {
|
|||||||
// InetAddress bidr=InetAddress.getByName("::0");
|
// InetAddress bidr=InetAddress.getByName("::0");
|
||||||
|
|
||||||
if (bidr instanceof Inet6Address) {
|
if (bidr instanceof Inet6Address) {
|
||||||
IPMulticastDiscovery ipd = new IPMulticastDiscovery(
|
IPMulticastDiscovery ipd = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
|
||||||
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
|
new InetSocketAddress(InetAddress.getByName("ff02::2486"),
|
||||||
DISCOVERY_PORT),
|
DISCOVERY_PORT),
|
||||||
networkInterface, selflineTable, nodeuuid,10000L);
|
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||||
ipd.setCon((mpa) -> {
|
ipd.setCon((mpa) -> {
|
||||||
// System.out.println("添加本地IPv6链路:"+mpa);
|
if (debug)
|
||||||
|
System.out.println("添加本地IPv6链路:" + mpa);
|
||||||
try {
|
try {
|
||||||
if (!checkIsSelf(mpa))
|
if (!checkIsSelf(mpa))
|
||||||
addRemoteLines(mpa);
|
addRemoteLines(mpa);
|
||||||
@@ -295,13 +258,13 @@ public class KLALBController {
|
|||||||
ipmd.add(ipd);
|
ipmd.add(ipd);
|
||||||
} else if (bidr instanceof Inet4Address) {
|
} else if (bidr instanceof Inet4Address) {
|
||||||
// bidr=InetAddress.getByName("0.0.0.0");
|
// bidr=InetAddress.getByName("0.0.0.0");
|
||||||
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(
|
IPMulticastDiscovery ipd2 = new IPMulticastDiscovery(new InetSocketAddress(bidr,DISCOVERY_PORT),
|
||||||
new InetSocketAddress(bidr, DISCOVERY_PORT),
|
|
||||||
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
|
new InetSocketAddress(InetAddress.getByName("224.0.0.86"),
|
||||||
DISCOVERY_PORT),
|
DISCOVERY_PORT),
|
||||||
networkInterface, selflineTable,nodeuuid, 10000L);
|
networkInterface, listensSocketAddress, nodeuuid, 10000L);
|
||||||
ipd2.setCon((mpa) -> {
|
ipd2.setCon((mpa) -> {
|
||||||
// System.out.println("添加本地IPv4链路:"+mpa);
|
if (debug)
|
||||||
|
System.out.println("添加本地IPv4链路:" + mpa);
|
||||||
try {
|
try {
|
||||||
if (!checkIsSelf(mpa))
|
if (!checkIsSelf(mpa))
|
||||||
addRemoteLines(mpa);
|
addRemoteLines(mpa);
|
||||||
@@ -311,7 +274,7 @@ public class KLALBController {
|
|||||||
ipd2.start();
|
ipd2.start();
|
||||||
ipmd.add(ipd2);
|
ipmd.add(ipd2);
|
||||||
}
|
}
|
||||||
if(debug)
|
if (debug)
|
||||||
System.out.println("开启网卡地址广播:" + networkInterface);
|
System.out.println("开启网卡地址广播:" + networkInterface);
|
||||||
} catch (BindException e) {
|
} catch (BindException e) {
|
||||||
// e.printStackTrace();
|
// e.printStackTrace();
|
||||||
@@ -321,19 +284,22 @@ public class KLALBController {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private boolean checkIsSelf(MultipurposeSocketAddress inetAddress) throws UnknownHostException {
|
private boolean checkIsSelf(MultiProtocolSocketAddress inetAddress) throws UnknownHostException {
|
||||||
|
|
||||||
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|
return inetAddress.getInetAddress().isAnyLocalAddress() || inetAddress.getInetAddress().isLoopbackAddress()
|
||||||
|| selflineTable.contains(inetAddress);
|
|| externalEndpoints.contains(inetAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkIsSelfLocator(InetAddress inetAddress) {
|
private boolean checkIsSelfLocator(InetAddress inetAddress) {
|
||||||
@@ -345,16 +311,16 @@ public class KLALBController {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> nt = ntptable;
|
List<MultiProtocolSocketAddress> nt = ntptable;
|
||||||
// System.out.println("srv6 ip"+nb);
|
// System.out.println("srv6 ip"+nb);
|
||||||
if (nvc2 != null) {
|
if (nvc2 != null) {
|
||||||
Set<NTPPeer> sp = nvc2.getPeers();
|
Set<NTPPeer> sp = nvc2.getPeers();
|
||||||
for (MultipurposeSocketAddress server : nt) {
|
for (MultiProtocolSocketAddress server : nt) {
|
||||||
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
|
sp.add(new NTPPeer(server, NTPv4Packet.NTP_CLIENT));
|
||||||
|
|
||||||
}
|
}
|
||||||
sp.removeIf((p) -> {
|
sp.removeIf((p) -> {
|
||||||
for (MultipurposeSocketAddress server : nt) {
|
for (MultiProtocolSocketAddress server : nt) {
|
||||||
if (server.equals(p.getAddress())) {
|
if (server.equals(p.getAddress())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -365,10 +331,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private TimerTask tsk3 = new TimerTask() {
|
public void tryConnectMore(){
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (srv6Router != null && routingProtocol != null) {
|
if (srv6Router != null && routingProtocol != null) {
|
||||||
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
|
Set<IPv6AddressGroup> nb = srv6Router.getLocators();
|
||||||
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6AddressGroup> iterator = nb.iterator(); iterator.hasNext();) {
|
||||||
@@ -376,9 +339,9 @@ public class KLALBController {
|
|||||||
try {
|
try {
|
||||||
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
|
InetSocketAddress iaddr = new InetSocketAddress(neighbor.getAddress().toInet6Address(),
|
||||||
KLALBRoutingProtocol.DEFAULT_PORT);
|
KLALBRoutingProtocol.DEFAULT_PORT);
|
||||||
apiClient.requestOpenLines(iaddr, (v) -> {
|
apiClient.requestNodeInfoFull(iaddr, (v) -> {
|
||||||
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
|
ThreadTool.makeVDaemonThreadIfSupport("线路添加任务", () -> {
|
||||||
for (MultipurposeSocketAddress msa : v) {
|
for (MultiProtocolSocketAddress msa : v.getOpenLines()) {
|
||||||
// System.out.print(msa);
|
// System.out.print(msa);
|
||||||
addRemoteLines(msa);
|
addRemoteLines(msa);
|
||||||
}
|
}
|
||||||
@@ -391,7 +354,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
|
public SpeedAndTrafficMonitorDataImpl getLinkMonitor() {
|
||||||
return linkMonitor;
|
return linkMonitor;
|
||||||
@@ -403,6 +366,8 @@ public class KLALBController {
|
|||||||
|
|
||||||
private SocketType streamSocketType = new KLALBStreamSocketType();
|
private SocketType streamSocketType = new KLALBStreamSocketType();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class KLALBStreamSocketType extends SocketType {
|
public class KLALBStreamSocketType extends SocketType {
|
||||||
|
|
||||||
public KLALBStreamSocketType() {
|
public KLALBStreamSocketType() {
|
||||||
@@ -436,8 +401,8 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getSelflineTable() {
|
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||||
return selflineTable;
|
return externalEndpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -472,13 +437,13 @@ public class KLALBController {
|
|||||||
|
|
||||||
private KLALBControllerConfigItem configItem;
|
private KLALBControllerConfigItem configItem;
|
||||||
|
|
||||||
public void addRemoteLines(List<MultipurposeSocketAddress> select) {
|
public void addRemoteLines(List<MultiProtocolSocketAddress> select) {
|
||||||
for (MultipurposeSocketAddress target : select) {
|
for (MultiProtocolSocketAddress target : select) {
|
||||||
addRemoteLines(target);
|
addRemoteLines(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KLALBRemoteLink> addRemoteLines(MultipurposeSocketAddress target) {
|
public List<KLALBRemoteLink> addRemoteLines(MultiProtocolSocketAddress target) {
|
||||||
lineslock.writeLock().lock();
|
lineslock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
List<KLALBRemoteLink> added = new ArrayList<>();
|
List<KLALBRemoteLink> added = new ArrayList<>();
|
||||||
@@ -492,7 +457,7 @@ public class KLALBController {
|
|||||||
while (ei.hasMoreElements()) {
|
while (ei.hasMoreElements()) {
|
||||||
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||||
try {
|
try {
|
||||||
MultipurposeSocketAddress bind = new MultipurposeSocketAddress(
|
MultiProtocolSocketAddress bind = new MultiProtocolSocketAddress(
|
||||||
inetAddress.getHostAddress(), 0);
|
inetAddress.getHostAddress(), 0);
|
||||||
try {
|
try {
|
||||||
if (target.getInetAddress().isLoopbackAddress()
|
if (target.getInetAddress().isLoopbackAddress()
|
||||||
@@ -553,7 +518,7 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<KLALBRemoteLink> removeRemoteLines(MultipurposeSocketAddress mpsa) {
|
public List<KLALBRemoteLink> removeRemoteLines(MultiProtocolSocketAddress mpsa) {
|
||||||
|
|
||||||
List<KLALBRemoteLink> rmved = new ArrayList<>();
|
List<KLALBRemoteLink> rmved = new ArrayList<>();
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
@@ -569,7 +534,7 @@ public class KLALBController {
|
|||||||
return rmved;
|
return rmved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Inet6Address getRemoteVaddrBySocketAddress(MultipurposeSocketAddress target) throws SocketTimeoutException {
|
public Inet6Address getRemoteVaddrBySocketAddress(MultiProtocolSocketAddress target) throws SocketTimeoutException {
|
||||||
KLALBRemoteLink kr = null;
|
KLALBRemoteLink kr = null;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -593,7 +558,7 @@ public class KLALBController {
|
|||||||
return kr.getRemoteVaddr().getAddress().toInet6Address();
|
return kr.getRemoteVaddr().getAddress().toInet6Address();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkContainsTargetAndBind(MultipurposeSocketAddress target, MultipurposeSocketAddress bind) {
|
private boolean checkContainsTargetAndBind(MultiProtocolSocketAddress target, MultiProtocolSocketAddress bind) {
|
||||||
boolean b = false;
|
boolean b = false;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -609,7 +574,7 @@ public class KLALBController {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkContainsTarget(MultipurposeSocketAddress target) {
|
private boolean checkContainsTarget(MultiProtocolSocketAddress target) {
|
||||||
boolean b = false;
|
boolean b = false;
|
||||||
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
for (Iterator<IPv6NetworkLink> iterator = srv6Router.getLinkTabel().iterator(); iterator.hasNext();) {
|
||||||
IPv6NetworkLink link = iterator.next();
|
IPv6NetworkLink link = iterator.next();
|
||||||
@@ -628,19 +593,19 @@ public class KLALBController {
|
|||||||
lineslock.writeLock().lock();
|
lineslock.writeLock().lock();
|
||||||
try {
|
try {
|
||||||
krs.startIO();
|
krs.startIO();
|
||||||
String selflineTable = generateSelfLineTable();
|
String externalEndpointsText = generateExternalEndpointsString();
|
||||||
if (selflineTable != null && !selflineTable.equals(""))
|
if (externalEndpointsText != null && !externalEndpointsText.equals(""))
|
||||||
krs.sendPacket(new ADDLINESPacket(selflineTable));
|
krs.sendPacket(new ADDLINESPacket(externalEndpointsText));
|
||||||
srv6Router.getLinkTabel().add(krs);
|
srv6Router.getLinkTabel().add(krs);
|
||||||
} finally {
|
} finally {
|
||||||
lineslock.writeLock().unlock();
|
lineslock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateSelfLineTable() {
|
private String generateExternalEndpointsString() {
|
||||||
StringBuilder sbd = new StringBuilder();
|
StringBuilder sbd = new StringBuilder();
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineTable.iterator(); iterator.hasNext();) {
|
for (Iterator<MultiProtocolSocketAddress> iterator = externalEndpoints.iterator(); iterator.hasNext();) {
|
||||||
MultipurposeSocketAddress klalbRemoteLine = (MultipurposeSocketAddress) iterator.next();
|
MultiProtocolSocketAddress klalbRemoteLine = (MultiProtocolSocketAddress) iterator.next();
|
||||||
sbd.append(klalbRemoteLine.toString());
|
sbd.append(klalbRemoteLine.toString());
|
||||||
sbd.append('\n');
|
sbd.append('\n');
|
||||||
}
|
}
|
||||||
@@ -668,7 +633,12 @@ public class KLALBController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
|
private void loadSRv6ProtocolStack(IPv6AddressGroup selfx, boolean enableVirtualAdapter) {
|
||||||
|
networkInterfaceManager.getInetAddressesExcept().add(selfx.getAddress().toInet6Address());
|
||||||
srv6Router = new SRv6Router(selfx, clock);
|
srv6Router = new SRv6Router(selfx, clock);
|
||||||
|
if(configItem!=null) {
|
||||||
|
srv6Router.setPerformanceStrategy(PerformanceStrategy.fromDescription(configItem.getPerformanceStrategy()));
|
||||||
|
srv6Router.setDeviceName(configItem.getDeviceName());
|
||||||
|
}
|
||||||
srv6Router.runKLALBRouteProtocol();
|
srv6Router.runKLALBRouteProtocol();
|
||||||
routingProtocol = srv6Router.getKlalbRouteProtol();
|
routingProtocol = srv6Router.getKlalbRouteProtol();
|
||||||
routingProtocol.addReceiver((addr, packet) -> {
|
routingProtocol.addReceiver((addr, packet) -> {
|
||||||
@@ -677,11 +647,13 @@ public class KLALBController {
|
|||||||
rawPortBinder= new PortBinder(this.getSelf().getAddress());
|
rawPortBinder= new PortBinder(this.getSelf().getAddress());
|
||||||
System.out.println(" Loaded: SRv6 Stack");
|
System.out.println(" Loaded: SRv6 Stack");
|
||||||
|
|
||||||
String name=(configItem!=null)?configItem.getTUNName():CONST.KLALB_S_RV6;
|
String name = (configItem != null && configItem.getTUNName() != null) ? configItem.getTUNName() : CONST.KLALB_S_RV6;
|
||||||
if (enableVirtualAdapter&&(name!=null)) {
|
boolean isEnabled = configItem == null || configItem.isEnableTUN();
|
||||||
|
boolean enableTUN = enableVirtualAdapter && isEnabled && (name != null) && !name.trim().isEmpty() && !name.trim().equalsIgnoreCase("null");
|
||||||
|
if (enableTUN) {
|
||||||
Thread t=new Thread(()->{
|
Thread t=new Thread(()->{
|
||||||
try {
|
try {
|
||||||
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name,
|
IPv6TUNLoopbackNetworkLink tunlink = new IPv6TUNLoopbackNetworkLink(name.trim(),
|
||||||
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
|
new IPv6AddressGroup(srv6Router.getLocator().getAddress(), 32), SRv6Router.MTU, dnsAddresses);
|
||||||
tunlink.setMonitor(datatMonitor);
|
tunlink.setMonitor(datatMonitor);
|
||||||
// srv6Router.getLinkTabel().add(tunlink);
|
// srv6Router.getLinkTabel().add(tunlink);
|
||||||
@@ -695,7 +667,7 @@ public class KLALBController {
|
|||||||
});
|
});
|
||||||
t.start();
|
t.start();
|
||||||
}else {
|
}else {
|
||||||
System.out.println(" Tun Adapter Disbaled");
|
System.out.println(" Tun Adapter Disabled");
|
||||||
|
|
||||||
}
|
}
|
||||||
udpr=new UDPProtocolRegister(this);
|
udpr=new UDPProtocolRegister(this);
|
||||||
@@ -715,7 +687,7 @@ public class KLALBController {
|
|||||||
// System.out.println(context);
|
// System.out.println(context);
|
||||||
try {
|
try {
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context,
|
NTPv4Protocol nvc = new NTPv4Protocol(context,
|
||||||
new MultipurposeSocketAddress("{" + iproxyname + "_Datagram}0.0.0.0:123"));
|
new MultiProtocolSocketAddress(iproxyname + "_Datagram","0.0.0.0",123));
|
||||||
srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
|
srv6Router.addSRv6RouterListener(new SRv6RouterListener() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -726,7 +698,7 @@ public class KLALBController {
|
|||||||
for (Neighbor neighbor : nb) {
|
for (Neighbor neighbor : nb) {
|
||||||
if (neighbor.getLocator() != null)
|
if (neighbor.getLocator() != null)
|
||||||
sp.add(new NTPPeer(
|
sp.add(new NTPPeer(
|
||||||
new MultipurposeSocketAddress(iproxyname + "_Datagram",
|
new MultiProtocolSocketAddress(iproxyname + "_Datagram",
|
||||||
neighbor.getLocator().getAddress().toString(), 123),
|
neighbor.getLocator().getAddress().toString(), 123),
|
||||||
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|
||||||
@@ -747,7 +719,7 @@ public class KLALBController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
nvc2 = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
nvc2 = new NTPv4Protocol(context, new MultiProtocolSocketAddress("udp","0.0.0.0",0));
|
||||||
|
|
||||||
System.out.println(" Loaded: NTP Module");
|
System.out.println(" Loaded: NTP Module");
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
@@ -764,7 +736,6 @@ public class KLALBController {
|
|||||||
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
|
this.apiClient = new KLALBRoutingProtocolAPIClient(routingProtocol);
|
||||||
twk.schedule(tsk1, 5000, 5000);
|
twk.schedule(tsk1, 5000, 5000);
|
||||||
twk.schedule(tsk2, 5000, 5000);
|
twk.schedule(tsk2, 5000, 5000);
|
||||||
twk.schedule(tsk3, 5000, 5000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBController() {
|
public KLALBController() {
|
||||||
@@ -830,18 +801,18 @@ public class KLALBController {
|
|||||||
getIpv6Router().setASN(vasn);
|
getIpv6Router().setASN(vasn);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> linele = configItem.getLineTable();
|
List<MultiProtocolSocketAddress> linele = configItem.getExternalEndpoints();
|
||||||
if (linele != null) {
|
if (linele != null) {
|
||||||
getSelflineTable().addAll(linele);
|
getExternalEndpoints().addAll(linele);
|
||||||
}
|
}
|
||||||
List<MultipurposeSocketAddress> linetoc = configItem.getConnectLineTable();
|
List<MultiProtocolSocketAddress> linetoc = configItem.getAutoConnections();
|
||||||
if (linetoc != null) {
|
if (linetoc != null) {
|
||||||
linetoc.forEach((aline) -> {
|
linetoc.forEach((aline) -> {
|
||||||
addRemoteLines(aline);
|
addRemoteLines(aline);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MultipurposeSocketAddress> ntps = configItem.getNtpServerTable();
|
List<MultiProtocolSocketAddress> ntps = configItem.getNtpServers();
|
||||||
if (ntps != null) {
|
if (ntps != null) {
|
||||||
getNTPTable().addAll(ntps);
|
getNTPTable().addAll(ntps);
|
||||||
}
|
}
|
||||||
@@ -1004,24 +975,19 @@ public class KLALBController {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> listens = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
|
|
||||||
public void registerToProxyTypeAs(String proxyname) {
|
public void registerToProxyTypeAs(String proxyname) {
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Stream", streamSocketType);
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(proxyname + "_Datagram", datagramSocketType);
|
||||||
// ProxyProfileEntry.getRegister().put(proxyname, this);
|
// ProxyProfileEntry.getRegister().put(proxyname, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getListenSocketAddress() {
|
private List<MultiProtocolSocketAddress> ntptable = new CopyOnWriteArrayList();
|
||||||
return listens;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<MultipurposeSocketAddress> ntptable = new CopyOnWriteArrayList();
|
|
||||||
|
|
||||||
private KLALBRoutingProtocol routingProtocol;
|
private KLALBRoutingProtocol routingProtocol;
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getNTPTable() {
|
public List<MultiProtocolSocketAddress> getNTPTable() {
|
||||||
return ntptable;
|
return ntptable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,31 +3,87 @@ package org.kne.cloud.network.klalb;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class KLALBControllerConfigItem extends KLALBConfigItem {
|
public class KLALBControllerConfigItem extends KLALBConfigItem {
|
||||||
private String language;
|
private String language;
|
||||||
private boolean nogui;
|
private boolean nogui;
|
||||||
private String VirtualAddress;
|
private String VirtualAddress=KLALBUtils.randomKLALBIPv6Address().toString();
|
||||||
private Long VirtualASN;
|
private Long VirtualASN;
|
||||||
private List<InetAddress> DNS;
|
private List<InetAddress> DNS;
|
||||||
private MultipurposeSocketAddress TCPListen;
|
private MultiProtocolSocketAddress TCPListen=new MultiProtocolSocketAddress("0.0.0.0",4565);
|
||||||
private MultipurposeSocketAddress UDPListen;
|
private MultiProtocolSocketAddress UDPListen=new MultiProtocolSocketAddress("udp","0.0.0.0",4565);
|
||||||
private String VirtualSocketName;
|
private String VirtualSocketName;
|
||||||
private List<MultipurposeSocketAddress>LineTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress> externalEndpoints = new ArrayList<>();
|
||||||
private List<MultipurposeSocketAddress>ConnectLineTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress> autoConnections = new ArrayList<>();
|
||||||
private List<MultipurposeSocketAddress>ntpServerTable=new ArrayList<>();
|
private List<MultiProtocolSocketAddress> ntpServers = new ArrayList<>();
|
||||||
private boolean denyLineTableQuery=false;
|
private List<String> ExtraRoutes = new ArrayList<>();
|
||||||
private boolean denyLineTableBroadcast=false;
|
private boolean denyExternalEndpointQuery = false;
|
||||||
|
private boolean denyExternalEndpointBroadcast = false;
|
||||||
private String congestionAlgorithm="BBR";
|
private String congestionAlgorithm="BBR";
|
||||||
private double burstLimit=1.20;
|
private double burstLimit=1.50;
|
||||||
private double delayUpperBound=1.20;
|
private double delayUpperBound=1.20;
|
||||||
private double delayLowerBound=1.15;
|
private double delayLowerBound=1.15;
|
||||||
private long nagleDelayTime=1000000L;
|
private long nagleDelayTime=1000000L;
|
||||||
private long linkNagleDelayTime=1000000L;
|
private long linkNagleDelayTime=1000000L;
|
||||||
private int linkConnectionsCount=1;
|
private int linkConnectionsCount=1;
|
||||||
|
private boolean enableTUN = true;
|
||||||
private String TUNName=CONST.KLALB_S_RV6;
|
private String TUNName=CONST.KLALB_S_RV6;
|
||||||
|
private String performanceStrategy="multifill";
|
||||||
|
private String DeviceName;
|
||||||
|
private String DeviceDescription;
|
||||||
|
private boolean webUI = false;
|
||||||
|
private MultiProtocolSocketAddress webListen = new MultiProtocolSocketAddress("http", "0.0.0.0", 4665);
|
||||||
|
|
||||||
|
public boolean isEnableTUN() {
|
||||||
|
return enableTUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEnableTUN(boolean enableTUN) {
|
||||||
|
this.enableTUN = enableTUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isWebUI() {
|
||||||
|
return webUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebUI(boolean webUI) {
|
||||||
|
this.webUI = webUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultiProtocolSocketAddress getWebListen() {
|
||||||
|
return webListen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebListen(MultiProtocolSocketAddress webListen) {
|
||||||
|
this.webListen = webListen;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPerformanceStrategy() {
|
||||||
|
return performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPerformanceStrategy(String performanceStrategy) {
|
||||||
|
this.performanceStrategy = performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return DeviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
DeviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceDescription() {
|
||||||
|
return DeviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceDescription(String deviceDescription) {
|
||||||
|
DeviceDescription = deviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
|
public void setNetworkInterfaceExcepts(List<String> networkInterfaceExcepts) {
|
||||||
NetworkInterfaceExcepts = networkInterfaceExcepts;
|
NetworkInterfaceExcepts = networkInterfaceExcepts;
|
||||||
@@ -52,8 +108,8 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public KLALBControllerConfigItem(String type) {
|
public KLALBControllerConfigItem() {
|
||||||
super(type);
|
super("KLALBController");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -124,63 +180,76 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTCPListen() {
|
public MultiProtocolSocketAddress getTCPListen() {
|
||||||
return TCPListen;
|
return TCPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setTCPListen(MultipurposeSocketAddress tCPListen) {
|
public void setTCPListen(MultiProtocolSocketAddress tCPListen) {
|
||||||
TCPListen = tCPListen;
|
TCPListen = tCPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public MultipurposeSocketAddress getUDPListen() {
|
public MultiProtocolSocketAddress getUDPListen() {
|
||||||
return UDPListen;
|
return UDPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void setUDPListen(MultipurposeSocketAddress uDPListen) {
|
public void setUDPListen(MultiProtocolSocketAddress uDPListen) {
|
||||||
UDPListen = uDPListen;
|
UDPListen = uDPListen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public List<MultipurposeSocketAddress> getLineTable() {
|
public List<MultiProtocolSocketAddress> getExternalEndpoints() {
|
||||||
return LineTable;
|
return externalEndpoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setExternalEndpoints(List<MultiProtocolSocketAddress> externalEndpoints) {
|
||||||
|
this.externalEndpoints = externalEndpoints;
|
||||||
public void setLineTable(List<MultipurposeSocketAddress> lineTable) {
|
|
||||||
LineTable = lineTable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getAutoConnections() {
|
||||||
|
return autoConnections;
|
||||||
public List<MultipurposeSocketAddress> getConnectLineTable() {
|
|
||||||
return ConnectLineTable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAutoConnections(List<MultiProtocolSocketAddress> autoConnections) {
|
||||||
|
this.autoConnections = autoConnections;
|
||||||
public void setConnectLineTable(List<MultipurposeSocketAddress> connectLineTable) {
|
|
||||||
ConnectLineTable = connectLineTable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getConnectLineTable() {
|
||||||
|
return autoConnections;
|
||||||
public List<MultipurposeSocketAddress> getNtpServerTable() {
|
|
||||||
return ntpServerTable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setConnectLineTable(List<MultiProtocolSocketAddress> connectLineTable) {
|
||||||
|
this.autoConnections = connectLineTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getNtpServers() {
|
||||||
|
return ntpServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNtpServers(List<MultiProtocolSocketAddress> ntpServers) {
|
||||||
|
this.ntpServers = ntpServers;
|
||||||
|
}
|
||||||
|
|
||||||
public void setNtpServerTable(List<MultipurposeSocketAddress> ntpServerTable) {
|
public List<MultiProtocolSocketAddress> getNtpServerTable() {
|
||||||
this.ntpServerTable = ntpServerTable;
|
return ntpServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNtpServerTable(List<MultiProtocolSocketAddress> ntpServerTable) {
|
||||||
|
this.ntpServers = ntpServerTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getExtraRoutes() {
|
||||||
|
return ExtraRoutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExtraRoutes(List<String> extraRoutes) {
|
||||||
|
ExtraRoutes = extraRoutes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -243,23 +312,20 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean isDenyLineTableQuery() {
|
public boolean isDenyExternalEndpointQuery() {
|
||||||
return denyLineTableQuery;
|
return denyExternalEndpointQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDenyExternalEndpointQuery(boolean denyExternalEndpointQuery) {
|
||||||
public void setDenyLineTableQuery(boolean denyLineTableQuery) {
|
this.denyExternalEndpointQuery = denyExternalEndpointQuery;
|
||||||
this.denyLineTableQuery = denyLineTableQuery;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDenyExternalEndpointBroadcast() {
|
||||||
public boolean isDenyLineTableBroadcast() {
|
return denyExternalEndpointBroadcast;
|
||||||
return denyLineTableBroadcast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setDenyExternalEndpointBroadcast(boolean denyExternalEndpointBroadcast) {
|
||||||
public void setDenyLineTableBroadcast(boolean denyLineTableBroadcast) {
|
this.denyExternalEndpointBroadcast = denyExternalEndpointBroadcast;
|
||||||
this.denyLineTableBroadcast = denyLineTableBroadcast;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -306,146 +372,49 @@ public class KLALBControllerConfigItem extends KLALBConfigItem {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "KLALBControllerConfigItem [language=" + language + ", nogui=" + nogui + ", VirtualAddress="
|
return "KLALBControllerConfigItem{" +
|
||||||
+ VirtualAddress + ", VirtualASN=" + VirtualASN + ", DNS=" + DNS + ", TCPListen=" + TCPListen
|
"language='" + language + '\'' +
|
||||||
+ ", UDPListen=" + UDPListen + ", VirtualSocketName=" + VirtualSocketName + ", LineTable=" + LineTable
|
", nogui=" + nogui +
|
||||||
+ ", ConnectLineTable=" + ConnectLineTable + ", ntpServerTable=" + ntpServerTable
|
", VirtualAddress='" + VirtualAddress + '\'' +
|
||||||
+ ", denyLineTableQuery=" + denyLineTableQuery + ", denyLineTableBroadcast=" + denyLineTableBroadcast
|
", VirtualASN=" + VirtualASN +
|
||||||
+ ", congestionAlgorithm=" + congestionAlgorithm + ", delayUpperBound=" + delayUpperBound
|
", DNS=" + DNS +
|
||||||
+ ", delayLowerBound=" + delayLowerBound + ", nagleDelayTime=" + nagleDelayTime
|
", TCPListen=" + TCPListen +
|
||||||
+ ", linkNagleDelayTime=" + linkNagleDelayTime + ", linkConnectionsCount=" + linkConnectionsCount
|
", UDPListen=" + UDPListen +
|
||||||
+ ", TUNName=" + TUNName + ", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts + "]";
|
", VirtualSocketName='" + VirtualSocketName + '\'' +
|
||||||
|
", externalEndpoints=" + externalEndpoints +
|
||||||
|
", autoConnections=" + autoConnections +
|
||||||
|
", ntpServers=" + ntpServers +
|
||||||
|
", ExtraRoutes=" + ExtraRoutes +
|
||||||
|
", denyExternalEndpointQuery=" + denyExternalEndpointQuery +
|
||||||
|
", denyExternalEndpointBroadcast=" + denyExternalEndpointBroadcast +
|
||||||
|
", congestionAlgorithm='" + congestionAlgorithm + '\'' +
|
||||||
|
", burstLimit=" + burstLimit +
|
||||||
|
", delayUpperBound=" + delayUpperBound +
|
||||||
|
", delayLowerBound=" + delayLowerBound +
|
||||||
|
", nagleDelayTime=" + nagleDelayTime +
|
||||||
|
", linkNagleDelayTime=" + linkNagleDelayTime +
|
||||||
|
", linkConnectionsCount=" + linkConnectionsCount +
|
||||||
|
", enableTUN=" + enableTUN +
|
||||||
|
", TUNName='" + TUNName + '\'' +
|
||||||
|
", performanceStrategy='" + performanceStrategy + '\'' +
|
||||||
|
", DeviceName='" + DeviceName + '\'' +
|
||||||
|
", DeviceDescription='" + DeviceDescription + '\'' +
|
||||||
|
", NetworkInterfaceExcepts=" + NetworkInterfaceExcepts +
|
||||||
|
", webUI=" + webUI +
|
||||||
|
", webListen=" + webListen +
|
||||||
|
'}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
KLALBControllerConfigItem that = (KLALBControllerConfigItem) o;
|
||||||
|
return nogui == that.nogui && enableTUN == that.enableTUN && webUI == that.webUI && denyExternalEndpointQuery == that.denyExternalEndpointQuery && denyExternalEndpointBroadcast == that.denyExternalEndpointBroadcast && Double.compare(burstLimit, that.burstLimit) == 0 && Double.compare(delayUpperBound, that.delayUpperBound) == 0 && Double.compare(delayLowerBound, that.delayLowerBound) == 0 && nagleDelayTime == that.nagleDelayTime && linkNagleDelayTime == that.linkNagleDelayTime && linkConnectionsCount == that.linkConnectionsCount && Objects.equals(webListen, that.webListen) && Objects.equals(language, that.language) && Objects.equals(VirtualAddress, that.VirtualAddress) && Objects.equals(VirtualASN, that.VirtualASN) && Objects.equals(DNS, that.DNS) && Objects.equals(TCPListen, that.TCPListen) && Objects.equals(UDPListen, that.UDPListen) && Objects.equals(VirtualSocketName, that.VirtualSocketName) && Objects.equals(externalEndpoints, that.externalEndpoints) && Objects.equals(autoConnections, that.autoConnections) && Objects.equals(ntpServers, that.ntpServers) && Objects.equals(ExtraRoutes, that.ExtraRoutes) && Objects.equals(congestionAlgorithm, that.congestionAlgorithm) && Objects.equals(TUNName, that.TUNName) && Objects.equals(performanceStrategy, that.performanceStrategy) && Objects.equals(DeviceName, that.DeviceName) && Objects.equals(DeviceDescription, that.DeviceDescription) && Objects.equals(NetworkInterfaceExcepts, that.NetworkInterfaceExcepts);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
return Objects.hash(super.hashCode(), language, nogui, VirtualAddress, VirtualASN, DNS, TCPListen, UDPListen, VirtualSocketName, externalEndpoints, autoConnections, ntpServers, ExtraRoutes, denyExternalEndpointQuery, denyExternalEndpointBroadcast, congestionAlgorithm, burstLimit, delayUpperBound, delayLowerBound, nagleDelayTime, linkNagleDelayTime, linkConnectionsCount, enableTUN, TUNName, performanceStrategy, DeviceName, DeviceDescription, NetworkInterfaceExcepts, webUI, webListen);
|
||||||
int result = super.hashCode();
|
|
||||||
result = prime * result + ((ConnectLineTable == null) ? 0 : ConnectLineTable.hashCode());
|
|
||||||
result = prime * result + ((DNS == null) ? 0 : DNS.hashCode());
|
|
||||||
result = prime * result + ((LineTable == null) ? 0 : LineTable.hashCode());
|
|
||||||
result = prime * result + ((NetworkInterfaceExcepts == null) ? 0 : NetworkInterfaceExcepts.hashCode());
|
|
||||||
result = prime * result + ((TCPListen == null) ? 0 : TCPListen.hashCode());
|
|
||||||
result = prime * result + ((TUNName == null) ? 0 : TUNName.hashCode());
|
|
||||||
result = prime * result + ((UDPListen == null) ? 0 : UDPListen.hashCode());
|
|
||||||
result = prime * result + ((VirtualASN == null) ? 0 : VirtualASN.hashCode());
|
|
||||||
result = prime * result + ((VirtualAddress == null) ? 0 : VirtualAddress.hashCode());
|
|
||||||
result = prime * result + ((VirtualSocketName == null) ? 0 : VirtualSocketName.hashCode());
|
|
||||||
result = prime * result + ((congestionAlgorithm == null) ? 0 : congestionAlgorithm.hashCode());
|
|
||||||
long temp;
|
|
||||||
temp = Double.doubleToLongBits(delayLowerBound);
|
|
||||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
|
||||||
temp = Double.doubleToLongBits(delayUpperBound);
|
|
||||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
|
||||||
result = prime * result + (denyLineTableBroadcast ? 1231 : 1237);
|
|
||||||
result = prime * result + (denyLineTableQuery ? 1231 : 1237);
|
|
||||||
result = prime * result + ((language == null) ? 0 : language.hashCode());
|
|
||||||
result = prime * result + linkConnectionsCount;
|
|
||||||
result = prime * result + (int) (linkNagleDelayTime ^ (linkNagleDelayTime >>> 32));
|
|
||||||
result = prime * result + (int) (nagleDelayTime ^ (nagleDelayTime >>> 32));
|
|
||||||
result = prime * result + (nogui ? 1231 : 1237);
|
|
||||||
result = prime * result + ((ntpServerTable == null) ? 0 : ntpServerTable.hashCode());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj)
|
|
||||||
return true;
|
|
||||||
if (!super.equals(obj))
|
|
||||||
return false;
|
|
||||||
if (getClass() != obj.getClass())
|
|
||||||
return false;
|
|
||||||
KLALBControllerConfigItem other = (KLALBControllerConfigItem) obj;
|
|
||||||
if (ConnectLineTable == null) {
|
|
||||||
if (other.ConnectLineTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!ConnectLineTable.equals(other.ConnectLineTable))
|
|
||||||
return false;
|
|
||||||
if (DNS == null) {
|
|
||||||
if (other.DNS != null)
|
|
||||||
return false;
|
|
||||||
} else if (!DNS.equals(other.DNS))
|
|
||||||
return false;
|
|
||||||
if (LineTable == null) {
|
|
||||||
if (other.LineTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!LineTable.equals(other.LineTable))
|
|
||||||
return false;
|
|
||||||
if (NetworkInterfaceExcepts == null) {
|
|
||||||
if (other.NetworkInterfaceExcepts != null)
|
|
||||||
return false;
|
|
||||||
} else if (!NetworkInterfaceExcepts.equals(other.NetworkInterfaceExcepts))
|
|
||||||
return false;
|
|
||||||
if (TCPListen == null) {
|
|
||||||
if (other.TCPListen != null)
|
|
||||||
return false;
|
|
||||||
} else if (!TCPListen.equals(other.TCPListen))
|
|
||||||
return false;
|
|
||||||
if (TUNName == null) {
|
|
||||||
if (other.TUNName != null)
|
|
||||||
return false;
|
|
||||||
} else if (!TUNName.equals(other.TUNName))
|
|
||||||
return false;
|
|
||||||
if (UDPListen == null) {
|
|
||||||
if (other.UDPListen != null)
|
|
||||||
return false;
|
|
||||||
} else if (!UDPListen.equals(other.UDPListen))
|
|
||||||
return false;
|
|
||||||
if (VirtualASN == null) {
|
|
||||||
if (other.VirtualASN != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualASN.equals(other.VirtualASN))
|
|
||||||
return false;
|
|
||||||
if (VirtualAddress == null) {
|
|
||||||
if (other.VirtualAddress != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualAddress.equals(other.VirtualAddress))
|
|
||||||
return false;
|
|
||||||
if (VirtualSocketName == null) {
|
|
||||||
if (other.VirtualSocketName != null)
|
|
||||||
return false;
|
|
||||||
} else if (!VirtualSocketName.equals(other.VirtualSocketName))
|
|
||||||
return false;
|
|
||||||
if (congestionAlgorithm == null) {
|
|
||||||
if (other.congestionAlgorithm != null)
|
|
||||||
return false;
|
|
||||||
} else if (!congestionAlgorithm.equals(other.congestionAlgorithm))
|
|
||||||
return false;
|
|
||||||
if (Double.doubleToLongBits(delayLowerBound) != Double.doubleToLongBits(other.delayLowerBound))
|
|
||||||
return false;
|
|
||||||
if (Double.doubleToLongBits(delayUpperBound) != Double.doubleToLongBits(other.delayUpperBound))
|
|
||||||
return false;
|
|
||||||
if (denyLineTableBroadcast != other.denyLineTableBroadcast)
|
|
||||||
return false;
|
|
||||||
if (denyLineTableQuery != other.denyLineTableQuery)
|
|
||||||
return false;
|
|
||||||
if (language == null) {
|
|
||||||
if (other.language != null)
|
|
||||||
return false;
|
|
||||||
} else if (!language.equals(other.language))
|
|
||||||
return false;
|
|
||||||
if (linkConnectionsCount != other.linkConnectionsCount)
|
|
||||||
return false;
|
|
||||||
if (linkNagleDelayTime != other.linkNagleDelayTime)
|
|
||||||
return false;
|
|
||||||
if (nagleDelayTime != other.nagleDelayTime)
|
|
||||||
return false;
|
|
||||||
if (nogui != other.nogui)
|
|
||||||
return false;
|
|
||||||
if (ntpServerTable == null) {
|
|
||||||
if (other.ntpServerTable != null)
|
|
||||||
return false;
|
|
||||||
} else if (!ntpServerTable.equals(other.ntpServerTable))
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
import java.nio.channels.ServerSocketChannel;
|
import java.nio.channels.ServerSocketChannel;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -11,7 +13,7 @@ import java.util.List;
|
|||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Scanner;
|
import java.util.Scanner;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SocketChannelListener;
|
import org.kne.cloud.network.SocketChannelListener;
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
import org.kne.cloud.network.ipv6.RouteItem;
|
import org.kne.cloud.network.ipv6.RouteItem;
|
||||||
@@ -50,13 +52,15 @@ public class KLALBMain {
|
|||||||
}catch(Throwable e) {
|
}catch(Throwable e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().isWebUI()) {
|
||||||
|
kpcje.enableWebServer();
|
||||||
|
}
|
||||||
|
} catch(Throwable e) {
|
||||||
|
System.err.println("Failed to start web server: " + e.getMessage());
|
||||||
|
}
|
||||||
dtb.putTime("UI");
|
dtb.putTime("UI");
|
||||||
//dtb.print();
|
//dtb.print();
|
||||||
/*MultipurposeSocketAddress mpa=new MultipurposeSocketAddress("127.9.9.9", 49573);
|
|
||||||
kpcje.enableRemoteManagement(mpa);
|
|
||||||
System.out.println("远程管理端口已在"+mpa+"端口上开启");*/
|
|
||||||
/*if(true)
|
|
||||||
return;*/
|
|
||||||
ServerSocketChannel kpsvr=KLALBVirtualServerSocketChannel.open(kpcje.getKlalbController());
|
ServerSocketChannel kpsvr=KLALBVirtualServerSocketChannel.open(kpcje.getKlalbController());
|
||||||
kpsvr.bind(new InetSocketAddress("::0", 4564));
|
kpsvr.bind(new InetSocketAddress("::0", 4564));
|
||||||
SocketChannelListener stlr=new SocketChannelListener(kpsvr);
|
SocketChannelListener stlr=new SocketChannelListener(kpsvr);
|
||||||
@@ -82,11 +86,13 @@ public class KLALBMain {
|
|||||||
case "help":
|
case "help":
|
||||||
System.out.println(" help / ?: see help");
|
System.out.println(" help / ?: see help");
|
||||||
System.out.println(" monitor: show monitor GUI");
|
System.out.println(" monitor: show monitor GUI");
|
||||||
|
System.out.println(" web [start|stop|status <port>]: manage web dashboard");
|
||||||
System.out.println(" links-state: query link states");
|
System.out.println(" links-state: query link states");
|
||||||
System.out.println(" links-add <addr:port>: add link");
|
System.out.println(" links-add <addr:port>: add link");
|
||||||
System.out.println(" links-remove <addr:port>: remove link");
|
System.out.println(" links-remove <addr:port>: remove link");
|
||||||
System.out.println(" links-reconnect: reconnect all link");
|
System.out.println(" links-reconnect: reconnect all link");
|
||||||
System.out.println(" kltp-state: query kltp states");
|
System.out.println(" kltp-state: query kltp states");
|
||||||
|
System.out.println(" interfaces: show all network interfaces");
|
||||||
System.out.println(" route: display internal route table");
|
System.out.println(" route: display internal route table");
|
||||||
System.out.println(" kperf <addr:port>: performance benchmark");
|
System.out.println(" kperf <addr:port>: performance benchmark");
|
||||||
System.out.println(" exit: Exit");
|
System.out.println(" exit: Exit");
|
||||||
@@ -99,6 +105,42 @@ public class KLALBMain {
|
|||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "web":
|
||||||
|
if(sc.length >= 2) {
|
||||||
|
String action = sc[1].toLowerCase();
|
||||||
|
if("start".equals(action)) {
|
||||||
|
int p = 4665;
|
||||||
|
if(sc.length >= 3) {
|
||||||
|
try { p = Integer.parseInt(sc[2]); } catch (NumberFormatException ignored) {}
|
||||||
|
} else if(kpcje.getControllerConfig() != null && kpcje.getControllerConfig().getWebListen() != null) {
|
||||||
|
p = kpcje.getControllerConfig().getWebListen().getPort();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
kpcje.enableWebServer(p);
|
||||||
|
System.out.println("Web dashboard started on http://localhost:" + p);
|
||||||
|
} catch(Exception e) {
|
||||||
|
System.out.println("Failed to start web server: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if("stop".equals(action)) {
|
||||||
|
kpcje.disableWebServer();
|
||||||
|
System.out.println("Web dashboard stopped.");
|
||||||
|
} else if("status".equals(action)) {
|
||||||
|
if(kpcje.isWebServerEnabled()) {
|
||||||
|
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
|
||||||
|
} else {
|
||||||
|
System.out.println("Web dashboard is stopped.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
System.out.println("Usage: web [start|stop|status <port>]");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(kpcje.isWebServerEnabled()) {
|
||||||
|
System.out.println("Web dashboard is running on port " + kpcje.getWebServer().getPort());
|
||||||
|
} else {
|
||||||
|
System.out.println("Web dashboard is not running. Use 'web start [port]' to start.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "links-state":
|
case "links-state":
|
||||||
System.out.println("links state:");
|
System.out.println("links state:");
|
||||||
//System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动");
|
//System.out.println("状态\t上传流量\t下载流量\t上传速度\t下载速度\t上传延迟\t下载延迟\t上传抖动\t下载抖动");
|
||||||
@@ -115,7 +157,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "links-add":
|
case "links-add":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
|
List<KLALBRemoteLink>addl=kpcje.getKlalbController().addRemoteLines(mpsa);
|
||||||
if(addl.isEmpty()) {
|
if(addl.isEmpty()) {
|
||||||
//System.out.println("添加失败,线路已存在!");
|
//System.out.println("添加失败,线路已存在!");
|
||||||
@@ -135,7 +177,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "links-remove":
|
case "links-remove":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
|
List<KLALBRemoteLink>rmvl=kpcje.getKlalbController().removeRemoteLines(mpsa);
|
||||||
if(rmvl.isEmpty()) {
|
if(rmvl.isEmpty()) {
|
||||||
System.out.println("No matched link has been found.");
|
System.out.println("No matched link has been found.");
|
||||||
@@ -161,6 +203,18 @@ public class KLALBMain {
|
|||||||
case "kltp-state":
|
case "kltp-state":
|
||||||
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
|
System.out.println(kpcje.getKlalbController().getKLTPregister().toString());
|
||||||
break;
|
break;
|
||||||
|
case "interfaces":
|
||||||
|
NetworkInterfaceManager networkInterfaceManager= kpcje.getKlalbController().getNetworkInterfaceManager();
|
||||||
|
List<NetworkInterface> interfaceList=networkInterfaceManager.getAllAvaliableNetworkInterface();
|
||||||
|
System.out.println("Network interface detected:");
|
||||||
|
for(NetworkInterface networkInterface:interfaceList){
|
||||||
|
System.out.println(networkInterface.getName()+"("+networkInterface.getDisplayName()+")");
|
||||||
|
List<InetAddress> lst= networkInterfaceManager.getNetworkInterfaceAddress(networkInterface);
|
||||||
|
for(InetAddress address :lst){
|
||||||
|
System.out.println(" "+address.getHostAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "route":
|
case "route":
|
||||||
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
|
List<RouteItem> lri=new ArrayList<>( kpcje.getKlalbController().getIpv6Router().getCurrentRouteTabel());
|
||||||
Collections.sort(lri);
|
Collections.sort(lri);
|
||||||
@@ -174,7 +228,7 @@ public class KLALBMain {
|
|||||||
break;
|
break;
|
||||||
case "kperf":
|
case "kperf":
|
||||||
if(sc.length>=2) {
|
if(sc.length>=2) {
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress(sc[1]);
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress(sc[1]);
|
||||||
Kperf kp=new Kperf(mpsa);
|
Kperf kp=new Kperf(mpsa);
|
||||||
kp.startPerfing();
|
kp.startPerfing();
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.LongAdder;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
|
|
||||||
public interface KLALBPacketLink {
|
public interface KLALBPacketLink {
|
||||||
public void writeKLALBPacket(KLALBPacket kp) throws IOException;
|
public void writeKLALBPacket(KLALBPacket kp) throws IOException;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.net.UnknownHostException;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.KLALBDetectorItem;
|
import org.kne.cloud.network.KLALBDetectorItem;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ProtocolDetector;
|
import org.kne.cloud.network.ProtocolDetector;
|
||||||
import org.kne.cloud.network.ProtocolDetectorServerSocket;
|
import org.kne.cloud.network.ProtocolDetectorServerSocket;
|
||||||
import org.kne.cloud.network.ProtocolDetectorSocket;
|
import org.kne.cloud.network.ProtocolDetectorSocket;
|
||||||
@@ -30,8 +30,8 @@ public class KLALBProtocolDetectSocketListener extends SocketListener {
|
|||||||
super.open();
|
super.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBProtocolDetectSocketListener(MultipurposeSocketAddress multipurposeSocketAddress,KLALBController klalbc) throws IOException {
|
public KLALBProtocolDetectSocketListener(MultiProtocolSocketAddress multiProtocolSocketAddress, KLALBController klalbc) throws IOException {
|
||||||
super(multipurposeSocketAddress);
|
super(multiProtocolSocketAddress);
|
||||||
this.klalbController=klalbc;
|
this.klalbController=klalbc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.io.FileReader;
|
|||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.Reader;
|
import java.io.Reader;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
|
||||||
@@ -12,27 +14,54 @@ import org.kne.cloud.network.*;
|
|||||||
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
|
import org.kne.cloud.network.klalb.ui.KLALBStateGUI3;
|
||||||
import org.kne.cloud.network.klalb.ui.Language;
|
import org.kne.cloud.network.klalb.ui.Language;
|
||||||
import org.kne.cloud.network.klalb.ui.UIEnv;
|
import org.kne.cloud.network.klalb.ui.UIEnv;
|
||||||
|
import org.kne.cloud.network.klalb.web.KLALBWebServer;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonDeserializationContext;
|
||||||
|
import com.google.gson.JsonDeserializer;
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParseException;
|
||||||
|
import com.google.gson.JsonPrimitive;
|
||||||
|
import com.google.gson.JsonSerializationContext;
|
||||||
|
import com.google.gson.JsonSerializer;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
public class KLALBProxySystem {
|
public class KLALBProxySystem {
|
||||||
private Set<Proxy> proxys=new HashSet<>();
|
private Set<Proxy> proxys=new HashSet<>();
|
||||||
private KLALBController klalbController;
|
private KLALBController klalbController;
|
||||||
private KLALBRemoteManagement krm;
|
private KLALBWebServer webServer;
|
||||||
private KLALBConfig config;
|
private KLALBConfig config;
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
private File jsonFile;
|
private File jsonFile;
|
||||||
{
|
{
|
||||||
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
GsonBuilder gb=new GsonBuilder().setPrettyPrinting();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
KLALBConfigItem.registerToGsonBuilder(gb);
|
KLALBConfigItem.registerToGsonBuilder(gb);
|
||||||
|
gb.registerTypeAdapter(InetAddress.class, new JsonSerializer<InetAddress>() {
|
||||||
|
@Override
|
||||||
|
public JsonElement serialize(InetAddress src, Type typeOfSrc, JsonSerializationContext context) {
|
||||||
|
return new JsonPrimitive(src.getHostAddress());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gb.registerTypeAdapter(InetAddress.class, new JsonDeserializer<InetAddress>() {
|
||||||
|
@Override
|
||||||
|
public InetAddress deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
try {
|
||||||
|
return InetAddress.getByName(json.getAsString());
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new JsonParseException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
gson=gb.create();
|
gson=gb.create();
|
||||||
}
|
}
|
||||||
|
public Gson getGson() {
|
||||||
|
return gson;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<Proxy> getProxys() {
|
public Set<Proxy> getProxys() {
|
||||||
return proxys;
|
return proxys;
|
||||||
}
|
}
|
||||||
@@ -55,43 +84,55 @@ public class KLALBProxySystem {
|
|||||||
public KLALBProxySystem() {
|
public KLALBProxySystem() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableRemoteManagement() throws IOException {
|
public void enableWebServer() throws IOException {
|
||||||
if(krm==null) {
|
KLALBControllerConfigItem cci = getControllerConfig();
|
||||||
krm=new KLALBRemoteManagement(this);
|
MultiProtocolSocketAddress listen = cci == null || cci.getWebListen() == null
|
||||||
}else {
|
? new MultiProtocolSocketAddress("http", "0.0.0.0", 4665) : cci.getWebListen();
|
||||||
throw new IllegalStateException("Remote Management already enabled!");
|
enableWebServer(listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableWebServer(int port) throws IOException {
|
||||||
|
enableWebServer(new MultiProtocolSocketAddress("http", "0.0.0.0", port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableWebServer(MultiProtocolSocketAddress listen) throws IOException {
|
||||||
|
if (webServer == null) {
|
||||||
|
webServer = new KLALBWebServer(this, listen);
|
||||||
|
webServer.start();
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Web server already enabled!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enableRemoteManagement(MultipurposeSocketAddress bind) throws IOException {
|
public boolean isWebServerEnabled() {
|
||||||
if(krm==null) {
|
return webServer != null && webServer.isRunning();
|
||||||
krm=new KLALBRemoteManagement(this,bind);
|
|
||||||
}else {
|
|
||||||
throw new IllegalStateException("Remote Management already enabled!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRemoteManagementEnabled() {
|
public KLALBWebServer getWebServer() {
|
||||||
return krm!=null;
|
return webServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disableRemoteManagement() {
|
public void disableWebServer() {
|
||||||
if(krm!=null) {
|
if (webServer != null) {
|
||||||
krm.close();
|
webServer.stop();
|
||||||
krm=null;
|
webServer = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadConfigJson(File jsonFile) throws IOException {
|
public void loadConfigJson(File jsonFile) throws IOException {
|
||||||
this.jsonFile=jsonFile;
|
this.jsonFile=jsonFile;
|
||||||
|
if(jsonFile.exists()) {
|
||||||
FileReader fr = null;
|
FileReader fr = null;
|
||||||
try {
|
try {
|
||||||
fr=new FileReader(jsonFile);
|
fr = new FileReader(jsonFile);
|
||||||
loadConfigJson(fr);
|
loadConfigJson(fr);
|
||||||
}finally {
|
} finally {
|
||||||
if(fr!=null)
|
if (fr != null)
|
||||||
fr.close();
|
fr.close();
|
||||||
}
|
}
|
||||||
|
}else{
|
||||||
|
loadConfig(KLALBConfig.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
public void loadConfigJson(String json) {
|
public void loadConfigJson(String json) {
|
||||||
@@ -128,9 +169,9 @@ public class KLALBProxySystem {
|
|||||||
|
|
||||||
String vsne=kcci.getVirtualSocketName();
|
String vsne=kcci.getVirtualSocketName();
|
||||||
//if(vsne!=null) {
|
//if(vsne!=null) {
|
||||||
MultipurposeSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
MultiProtocolSocketAddress.getSocketTypeRegister().put(vsne, klalbController.getStreamSocketType());
|
||||||
//}
|
//}
|
||||||
MultipurposeSocketAddress tcple=kcci.getTCPListen();
|
MultiProtocolSocketAddress tcple=kcci.getTCPListen();
|
||||||
if(tcple!=null) {
|
if(tcple!=null) {
|
||||||
|
|
||||||
SocketChannelListener tcpl = null;
|
SocketChannelListener tcpl = null;
|
||||||
@@ -148,13 +189,13 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
MultipurposeSocketAddress mpsa2=new MultipurposeSocketAddress(tcple.getType(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
MultiProtocolSocketAddress mpsa2=new MultiProtocolSocketAddress(tcple.getProtocol(), tcple.getHost(),((InetSocketAddress)tcpl.getServerSocketChannel().getLocalAddress()).getPort());
|
||||||
klalbController.getListenSocketAddress().add(mpsa2);
|
klalbController.getListenSocketAddress().add(mpsa2);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MultipurposeSocketAddress udple=kcci.getUDPListen();
|
MultiProtocolSocketAddress udple=kcci.getUDPListen();
|
||||||
if(udple!=null) {
|
if(udple!=null) {
|
||||||
DatagramSocketListener udpl = null;
|
DatagramSocketListener udpl = null;
|
||||||
try {
|
try {
|
||||||
@@ -171,7 +212,7 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
MultipurposeSocketAddress mpsau2=new MultipurposeSocketAddress(udple.getType(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
MultiProtocolSocketAddress mpsau2=new MultiProtocolSocketAddress(udple.getProtocol(), udple.getHost(),((InetSocketAddress)(udpl.getDatagramServerSocket().getLocalSocketAddress())).getPort());
|
||||||
//klalbController.getListenSocketAddress().add(mpsau2);
|
//klalbController.getListenSocketAddress().add(mpsau2);
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
e1.printStackTrace();
|
e1.printStackTrace();
|
||||||
@@ -191,30 +232,24 @@ public class KLALBProxySystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private KLALBStateGUI3 kgui;
|
public void saveConfigToFile() {
|
||||||
public KLALBStateGUI3 getKLALBGUI() {
|
if (jsonFile != null && config != null) {
|
||||||
if(kgui==null) {
|
String json = gson.toJson(config);
|
||||||
kgui=new KLALBStateGUI3(klalbController);
|
try (FileWriter fw = new FileWriter(jsonFile)) {
|
||||||
kgui.loadConfig(config);
|
|
||||||
kgui.setSaveComsumer((cfg)->{
|
|
||||||
String json=gson.toJson(cfg);
|
|
||||||
if(jsonFile!=null) {
|
|
||||||
FileWriter fw = null;
|
|
||||||
try {
|
|
||||||
fw=new FileWriter(jsonFile);
|
|
||||||
fw.write(json);
|
fw.write(json);
|
||||||
}catch(IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}finally {
|
|
||||||
if(fw!=null)
|
|
||||||
try {
|
|
||||||
fw.close();
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private KLALBStateGUI3 kgui;
|
||||||
|
public KLALBStateGUI3 getKLALBGUI() {
|
||||||
|
if(kgui==null) {
|
||||||
|
kgui=new KLALBStateGUI3(klalbController);
|
||||||
|
kgui.loadConfig(config);
|
||||||
|
kgui.setSaveComsumer((cfg)->{
|
||||||
|
saveConfigToFile();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return kgui;
|
return kgui;
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package org.kne.cloud.network.klalb;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StreamCorruptedException;
|
import java.io.StreamCorruptedException;
|
||||||
|
import java.net.BindException;
|
||||||
|
import java.net.NoRouteToHostException;
|
||||||
|
import java.net.SocketException;
|
||||||
import java.net.SocketTimeoutException;
|
import java.net.SocketTimeoutException;
|
||||||
import java.nio.channels.UnresolvedAddressException;
|
import java.nio.channels.UnresolvedAddressException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
@@ -21,7 +24,7 @@ import org.kne.cloud.clock.HighAccuracyClock;
|
|||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
|
import org.kne.cloud.clock.ReliabilityBackoffTimeClock;
|
||||||
import org.kne.cloud.clock.WatchDogTimer;
|
import org.kne.cloud.clock.WatchDogTimer;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
|
import org.kne.cloud.network.congestion.BBRCongestionAlgorithm;
|
||||||
import org.kne.cloud.network.congestion.CongestionAlgorithm;
|
import org.kne.cloud.network.congestion.CongestionAlgorithm;
|
||||||
@@ -100,8 +103,8 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
private LinkStatus status=new LinkStatus();
|
private LinkStatus status=new LinkStatus();
|
||||||
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
|
private SpeedAndTrafficAndDelayMonitorDataImpl monitor;
|
||||||
private volatile KLALBPacketLink kplink;
|
private volatile KLALBPacketLink kplink;
|
||||||
private MultipurposeSocketAddress bindAddress;
|
private MultiProtocolSocketAddress bindAddress;
|
||||||
private MultipurposeSocketAddress socketAddress;
|
private MultiProtocolSocketAddress socketAddress;
|
||||||
|
|
||||||
|
|
||||||
public void setKplink(KLALBPacketLink kplink) {
|
public void setKplink(KLALBPacketLink kplink) {
|
||||||
@@ -112,11 +115,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
return kplink;
|
return kplink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getSocketAddress() {
|
public MultiProtocolSocketAddress getSocketAddress() {
|
||||||
return socketAddress;
|
return socketAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getBindAddress() {
|
public MultiProtocolSocketAddress getBindAddress() {
|
||||||
return bindAddress;
|
return bindAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,7 +137,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
return peerAddress;
|
return peerAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller, MultipurposeSocketAddress mpa, IPv6AddressGroup address) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, IPv6AddressGroup address) {
|
||||||
this(controller,mpa, null,address);
|
this(controller,mpa, null,address);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +150,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
name=kpl.toString();
|
name=kpl.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bindaddr, IPv6AddressGroup address) {
|
private KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bindaddr, IPv6AddressGroup address) {
|
||||||
this.klalbController=controller;
|
this.klalbController=controller;
|
||||||
sysclk = klalbController.getClock();
|
sysclk = klalbController.getClock();
|
||||||
this.socketAddress = mpa;
|
this.socketAddress = mpa;
|
||||||
@@ -162,11 +165,11 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa, MultipurposeSocketAddress bind) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa, MultiProtocolSocketAddress bind) {
|
||||||
this(controller,mpa, bind, null);
|
this(controller,mpa, bind, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KLALBRemoteLink(KLALBController controller,MultipurposeSocketAddress mpa) {
|
public KLALBRemoteLink(KLALBController controller, MultiProtocolSocketAddress mpa) {
|
||||||
this(controller,mpa, (IPv6AddressGroup) null);
|
this(controller,mpa, (IPv6AddressGroup) null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,7 +286,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
sendLock.unlock();
|
sendLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tlock.parkNanos(1000000L);
|
tlock.parkNanos(10000000L);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (StreamCorruptedException sce) {
|
} catch (StreamCorruptedException sce) {
|
||||||
@@ -464,7 +467,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
break;
|
break;
|
||||||
case KLALBPacket.VADDR:
|
case KLALBPacket.VADDR:
|
||||||
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
|
IPv6AddressGroup vdr = ((VADDRPacket) kpp).getVaddr();
|
||||||
|
if(vdr.getAddress().equals(klalbController.getSelf().getAddress())){
|
||||||
|
close();
|
||||||
|
}else {
|
||||||
remoteVaddr = vdr;
|
remoteVaddr = vdr;
|
||||||
sendPacket(new VADDRACKPacket());
|
sendPacket(new VADDRACKPacket());
|
||||||
if (fst.compareAndSet(true, false)) {
|
if (fst.compareAndSet(true, false)) {
|
||||||
@@ -473,7 +478,7 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
KLALBRemoteLink.this.onOnlineStateUpdate();
|
KLALBRemoteLink.this.onOnlineStateUpdate();
|
||||||
}
|
}
|
||||||
KLALBRemoteLink.this.onLocatorUpdate();
|
KLALBRemoteLink.this.onLocatorUpdate();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case KLALBPacket.VADDRACK:
|
case KLALBPacket.VADDRACK:
|
||||||
break;
|
break;
|
||||||
@@ -567,7 +572,9 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
public void run() {
|
public void run() {
|
||||||
while (!closed) {
|
while (!closed) {
|
||||||
status.updateReliability();
|
status.updateReliability();
|
||||||
|
if(status.getState()!=LinkStatus.DOWN) {
|
||||||
monitor.update();
|
monitor.update();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
Thread.sleep(1);
|
Thread.sleep(1);
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
@@ -672,7 +679,23 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
}
|
}
|
||||||
} catch (StreamCorruptedException sce) {
|
} catch (StreamCorruptedException sce) {
|
||||||
sce.printStackTrace();
|
sce.printStackTrace();
|
||||||
} catch (IOException | UnresolvedAddressException e) {
|
}catch(BindException e) {
|
||||||
|
close();
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
}catch(NoRouteToHostException e){
|
||||||
|
close();
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch(SocketException e) {
|
||||||
|
String massage = e.getMessage();
|
||||||
|
if (massage.toLowerCase().contains("unreachable")) {
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
if (debug)
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}catch (IOException | UnresolvedAddressException e) {
|
||||||
if (debug)
|
if (debug)
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -927,23 +950,25 @@ public class KLALBRemoteLink extends AbstractControlledIPv6NetworkLink implement
|
|||||||
public List<RouteItem> getRouteItems() {
|
public List<RouteItem> getRouteItems() {
|
||||||
List<RouteItem> rlist = new ArrayList<>();
|
List<RouteItem> rlist = new ArrayList<>();
|
||||||
|
|
||||||
|
if(isUp()) {
|
||||||
IPv6AddressGroup adg = addressGroup;
|
IPv6AddressGroup adg = addressGroup;
|
||||||
/*if (adg != null)
|
/*if (adg != null)
|
||||||
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
|
rlist.add(new RouteItem(new IPv6AddressGroup(adg.getAddress(), 128), adg.getAddress(), this, "Direct", 0, 0,
|
||||||
null, "D", true));*/
|
null, "D", true));*/
|
||||||
|
|
||||||
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
|
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext(); ) {
|
||||||
Neighbor addresses = (Neighbor) iteratorx.next();
|
Neighbor addresses = (Neighbor) iteratorx.next();
|
||||||
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
|
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
|
||||||
addresses.getAddress().getAddress(), this, "Direct", 0, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
addresses.getAddress().getAddress(), this, "Direct", 0, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||||
rlist.add(ri);
|
rlist.add(ri);
|
||||||
|
|
||||||
if (addresses.getLocator() != null) {
|
if (addresses.getLocator() != null) {
|
||||||
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
|
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
|
||||||
"KLALB SRv6", 13, 128,CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(),status,algorithm::getRTO), "D", false);
|
"KLALB SRv6", 13, 128, CostSupplierFactory.expectedDelaySupplier((DelayMonitorData) addresses.getMonitor(), status, algorithm::getRTO), "D", false);
|
||||||
rlist.add(ris);
|
rlist.add(ris);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rlist;
|
return rlist;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
import org.kne.cloud.network.SocketListener;
|
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
|
||||||
import org.kne.cloud.network.monitor.LinkStatus;
|
|
||||||
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParser;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
|
|
||||||
public class KLALBRemoteManagement {
|
|
||||||
SocketListener slr;
|
|
||||||
private KLALBProxySystem klalbProxySystem;
|
|
||||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem) throws IOException {
|
|
||||||
this(klalbProxySystem,new MultipurposeSocketAddress("127.9.9.9", 49573));
|
|
||||||
}
|
|
||||||
public KLALBRemoteManagement(KLALBProxySystem klalbProxySystem,MultipurposeSocketAddress listen) throws IOException {
|
|
||||||
this.klalbProxySystem=klalbProxySystem;
|
|
||||||
slr=new SocketListener(listen);
|
|
||||||
slr.setCon((srcv)->{
|
|
||||||
try {
|
|
||||||
srcv.setSoTimeout(10000);
|
|
||||||
byte[]input= srcv.getInputStream().readAllBytes();
|
|
||||||
srcv.shutdownInput();
|
|
||||||
String req=new String(input,Charset.forName("UTF-8"));
|
|
||||||
System.out.println("远程管理请求:"+req);
|
|
||||||
String rsp=processSignal(req);
|
|
||||||
System.out.println("远程管理响应:"+rsp);
|
|
||||||
srcv.getOutputStream().write(rsp.getBytes(Charset.forName("UTF-8")));
|
|
||||||
srcv.shutdownOutput();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}finally {
|
|
||||||
try {
|
|
||||||
srcv.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
public KLALBProxySystem getKlalbProxySystem() {
|
|
||||||
return klalbProxySystem;
|
|
||||||
}
|
|
||||||
private String processSignal(String req) {
|
|
||||||
JsonObject jreq= (JsonObject) new JsonParser().parse(req);
|
|
||||||
JsonObject jrsp=new JsonObject();
|
|
||||||
|
|
||||||
|
|
||||||
String reqt=jreq.getAsJsonPrimitive("REQ").getAsString();
|
|
||||||
jrsp.addProperty("RSP", reqt);
|
|
||||||
switch (reqt) {
|
|
||||||
case "GETLINES":
|
|
||||||
JsonArray lines=new JsonArray();
|
|
||||||
List<IPv6NetworkLink>lineslist= klalbProxySystem.getKlalbController().getLines();
|
|
||||||
synchronized (lineslist) {
|
|
||||||
for (Iterator<IPv6NetworkLink> iterator = lineslist.iterator(); iterator.hasNext();) {
|
|
||||||
IPv6NetworkLink link=iterator.next();
|
|
||||||
if(!(link instanceof KLALBRemoteLink)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
KLALBRemoteLink klalbRemoteLine = (KLALBRemoteLink) link;
|
|
||||||
if(klalbRemoteLine.getSocketAddress()==null)
|
|
||||||
continue;
|
|
||||||
JsonObject jklbrl=new JsonObject();
|
|
||||||
jklbrl.addProperty("ipport", klalbRemoteLine.getSocketAddress().toString());
|
|
||||||
jklbrl.addProperty("state",LinkStatus.stateToString( klalbRemoteLine.getState()));
|
|
||||||
|
|
||||||
jklbrl.addProperty("Vaddr", klalbRemoteLine.getRemoteVaddr().getAddress().toString());
|
|
||||||
|
|
||||||
jklbrl.addProperty("uploadspeed", klalbRemoteLine.getMonitor().getOutSpeed());
|
|
||||||
jklbrl.addProperty("downloadspeed", klalbRemoteLine.getMonitor().getInSpeed());
|
|
||||||
|
|
||||||
jklbrl.addProperty("uploadtraffic", klalbRemoteLine.getMonitor().getOutTraffic());
|
|
||||||
jklbrl.addProperty("downloadtraffic", klalbRemoteLine.getMonitor().getInTraffic());
|
|
||||||
|
|
||||||
jklbrl.addProperty("uploaddelay",klalbRemoteLine.getMonitor().getOutDelay() );
|
|
||||||
jklbrl.addProperty("downloaddelay", klalbRemoteLine.getMonitor().getInDelay());
|
|
||||||
|
|
||||||
jklbrl.addProperty("uploaddelaymin",klalbRemoteLine.getMonitor().getOutDelayMin() );
|
|
||||||
jklbrl.addProperty("downloaddelaymin", klalbRemoteLine.getMonitor().getInDelayMin());
|
|
||||||
lines.add(jklbrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jrsp.add("table", lines);
|
|
||||||
break;
|
|
||||||
case "ADDLINE":
|
|
||||||
String mip=jreq.getAsJsonPrimitive("ipport").getAsString();
|
|
||||||
try {
|
|
||||||
|
|
||||||
jrsp.addProperty ("Vaddr",klalbProxySystem.getKlalbController().getRemoteVaddrBySocketAddress(new MultipurposeSocketAddress(mip)).getHostAddress());
|
|
||||||
} catch (SocketTimeoutException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "GETSELFLINES":
|
|
||||||
JsonArray lines2=new JsonArray();
|
|
||||||
List<MultipurposeSocketAddress>selflineslist=klalbProxySystem.getKlalbController().getSelflineTable();
|
|
||||||
synchronized (selflineslist) {
|
|
||||||
for (Iterator<MultipurposeSocketAddress> iterator = selflineslist.iterator(); iterator.hasNext();) {
|
|
||||||
MultipurposeSocketAddress multipurposeSocketAddress = (MultipurposeSocketAddress) iterator.next();
|
|
||||||
lines2.add(new JsonPrimitive(multipurposeSocketAddress.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jrsp.add("table", lines2);
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "ADDSELFLINE":
|
|
||||||
String mips=jreq.getAsJsonPrimitive("ipport").getAsString();
|
|
||||||
List<MultipurposeSocketAddress>selflineslist2=klalbProxySystem.getKlalbController().getSelflineTable();
|
|
||||||
synchronized (selflineslist2) {
|
|
||||||
selflineslist2.add(new MultipurposeSocketAddress(mips));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "REMOVESELFLINE":
|
|
||||||
String mipsr=jreq.getAsJsonPrimitive("ipport").getAsString();
|
|
||||||
List<MultipurposeSocketAddress>selflineslist21=klalbProxySystem.getKlalbController().getSelflineTable();
|
|
||||||
synchronized (selflineslist21) {
|
|
||||||
selflineslist21.add(new MultipurposeSocketAddress(mipsr));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "GETLINKMONITOR":
|
|
||||||
jrsp.addProperty("uploadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getOutSpeed());
|
|
||||||
jrsp.addProperty("downloadspeed", klalbProxySystem.getKlalbController().getLinkMonitor().getInSpeed());
|
|
||||||
|
|
||||||
jrsp.addProperty("uploadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getOutTraffic());
|
|
||||||
jrsp.addProperty("downloadtraffic", klalbProxySystem.getKlalbController().getLinkMonitor().getInTraffic());
|
|
||||||
|
|
||||||
break;
|
|
||||||
case "OPENMONITORUI":
|
|
||||||
klalbProxySystem.getKLALBGUI().setVisible(true);
|
|
||||||
break;
|
|
||||||
/*case "GETSOCKETBRIDGE":
|
|
||||||
JsonArray bridges=new JsonArray();
|
|
||||||
Set<Proxy> pxy=klalbProxySystem.getProxys();
|
|
||||||
synchronized (pxy) {
|
|
||||||
for (Iterator<Proxy> iterator = pxy.iterator(); iterator.hasNext();) {
|
|
||||||
Proxy proxy = (Proxy) iterator.next();
|
|
||||||
bridges.add(klalbProxySystem.createJsonObjectByProxy(proxy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jrsp.add("table", bridges);
|
|
||||||
break;
|
|
||||||
case "ADDSOCKETBRIDGE":
|
|
||||||
JsonObject jpxy= jreq.getAsJsonObject("socketbridge");
|
|
||||||
Set<Proxy> pxy2=klalbProxySystem.getProxys();
|
|
||||||
synchronized (pxy2) {
|
|
||||||
try {
|
|
||||||
pxy2.add(klalbProxySystem.createProxyByJson(jpxy));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;*/
|
|
||||||
default:
|
|
||||||
System.out.println("未知请求类型:"+reqt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return jrsp.toString();
|
|
||||||
}
|
|
||||||
public void close() {
|
|
||||||
slr.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
import org.jctools.counters.CountersFactory;
|
import org.jctools.counters.CountersFactory;
|
||||||
import org.jctools.counters.FixedSizeStripedLongCounter;
|
import org.jctools.counters.FixedSizeStripedLongCounter;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
|
|
||||||
public class KLALBUtils {
|
public class KLALBUtils {
|
||||||
@@ -51,6 +51,27 @@ public class KLALBUtils {
|
|||||||
v[3] = 0x01;
|
v[3] = 0x01;
|
||||||
return IPv6Address.valueOf(v);
|
return IPv6Address.valueOf(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String parseCidr(String cidr) {
|
||||||
|
int idx = cidr.indexOf('/');
|
||||||
|
if (idx <= 0 || idx == cidr.length() - 1)
|
||||||
|
throw new IllegalArgumentException("not a cidr: " + cidr);
|
||||||
|
int prefix;
|
||||||
|
try {
|
||||||
|
prefix = Integer.parseInt(cidr.substring(idx + 1));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("bad prefix: " + cidr);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
InetAddress addr = InetAddress.getByName(cidr.substring(0, idx));
|
||||||
|
int max = (addr instanceof Inet6Address) ? 128 : 32;
|
||||||
|
if (prefix < 0 || prefix > max)
|
||||||
|
throw new IllegalArgumentException("prefix out of range: " + cidr);
|
||||||
|
return addr.getHostAddress() + "/" + prefix;
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
throw new IllegalArgumentException("bad address: " + cidr);
|
||||||
|
}
|
||||||
|
}
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
System.out.println(uuidToIP(new UUID(-1,-1)));
|
System.out.println(uuidToIP(new UUID(-1,-1)));
|
||||||
}
|
}
|
||||||
@@ -208,7 +229,7 @@ public class KLALBUtils {
|
|||||||
return col;
|
return col;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress, MultipurposeSocketAddress targetAddress,boolean buffered)
|
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress, MultiProtocolSocketAddress targetAddress, boolean buffered)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (targetAddress.isStream()) {
|
if (targetAddress.isStream()) {
|
||||||
if (targetAddress.supportNIO()) {
|
if (targetAddress.supportNIO()) {
|
||||||
@@ -235,8 +256,8 @@ public class KLALBUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static KLALBPacketLink createKLALBPacketLink(MultipurposeSocketAddress bindAddress,
|
public static KLALBPacketLink createKLALBPacketLink(MultiProtocolSocketAddress bindAddress,
|
||||||
MultipurposeSocketAddress targetAddress,boolean buffered, int timeout) throws UnknownHostException, IOException {
|
MultiProtocolSocketAddress targetAddress, boolean buffered, int timeout) throws UnknownHostException, IOException {
|
||||||
if (targetAddress.isStream()) {
|
if (targetAddress.isStream()) {
|
||||||
if (targetAddress.supportNIO()) {
|
if (targetAddress.supportNIO()) {
|
||||||
if (bindAddress != null) {
|
if (bindAddress != null) {
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.NetworkInterface;
|
||||||
|
import java.net.SocketException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class NetworkInterfaceManager {
|
||||||
|
private List<NetworkInterface> networkInterfaceExcept = new ArrayList<>();
|
||||||
|
private List<InetAddress> inetAddressesExcept = new ArrayList<>();
|
||||||
|
|
||||||
|
public List<NetworkInterface> getNetworkInterfaceExcept() {
|
||||||
|
return networkInterfaceExcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public List<InetAddress> getInetAddressesExcept() {
|
||||||
|
return inetAddressesExcept;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<NetworkInterface> getAllAvaliableNetworkInterface() throws SocketException {
|
||||||
|
List<NetworkInterface> interfaces = new ArrayList<NetworkInterface>();
|
||||||
|
Enumeration<NetworkInterface> eu = NetworkInterface.getNetworkInterfaces();
|
||||||
|
while (eu.hasMoreElements()) {
|
||||||
|
NetworkInterface networkInterface = (NetworkInterface) eu.nextElement();
|
||||||
|
|
||||||
|
if (networkInterface.getDisplayName()
|
||||||
|
.startsWith(CONST.KLALB_DECENTRALIZED_S_RV6_NETWORK)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (networkInterfaceExcept.contains(networkInterface)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (networkInterface.isUp()) {
|
||||||
|
interfaces.add(networkInterface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return interfaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetAddress> getAllNetworkInterfaceAddress() throws SocketException {
|
||||||
|
List<NetworkInterface> interfaces=getAllAvaliableNetworkInterface();
|
||||||
|
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||||
|
for (NetworkInterface nif:interfaces){
|
||||||
|
addresses.addAll( getNetworkInterfaceAddress(nif));
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<InetAddress> getNetworkInterfaceAddress(NetworkInterface networkInterface) throws SocketException {
|
||||||
|
List<InetAddress> addresses = new ArrayList<InetAddress>();
|
||||||
|
// System.out.println(networkInterface+" "+networkInterface.isUp());
|
||||||
|
Enumeration<InetAddress> ei = networkInterface.getInetAddresses();
|
||||||
|
while (ei.hasMoreElements()) {
|
||||||
|
InetAddress inetAddress = (InetAddress) ei.nextElement();
|
||||||
|
|
||||||
|
if (inetAddress.isLoopbackAddress()){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (inetAddressesExcept.contains( inetAddress)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
addresses.add(inetAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addresses;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.klalb.ui.UIEnv;
|
||||||
|
|
||||||
|
public enum PerformanceStrategy {
|
||||||
|
SINGLE_CORE("singlecore"),
|
||||||
|
MULTI_FILL("multifill"),
|
||||||
|
MULTI_SCATTER("multiscatter");
|
||||||
|
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
private PerformanceStrategy(String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 description 查找对应的枚举对象。
|
||||||
|
*
|
||||||
|
* @param description 描述文本(资源包key)
|
||||||
|
* @return 对应的枚举对象
|
||||||
|
*/
|
||||||
|
public static PerformanceStrategy fromDescription(String description) {
|
||||||
|
for (PerformanceStrategy strategy : values()) {
|
||||||
|
if (strategy.description.equals(description)) {
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,23 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Type;
|
|
||||||
import java.nio.channels.MulticastChannel;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
|
import org.kne.cloud.network.DefaultMinecraftSocketBridgeFactory;
|
||||||
import org.kne.cloud.network.DefaultSocketBridgeFactory;
|
import org.kne.cloud.network.DefaultSocketBridgeFactory;
|
||||||
import org.kne.cloud.network.HostPortMap;
|
import org.kne.cloud.network.HostPortMap;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.Proxy;
|
import org.kne.cloud.network.Proxy;
|
||||||
import org.kne.cloud.network.SocketBridgeFactory;
|
import org.kne.cloud.network.SocketBridgeFactory;
|
||||||
import org.kne.cloud.network.SocketToSocketProxy;
|
import org.kne.cloud.network.SocketToSocketProxy;
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder;
|
|
||||||
import com.google.gson.JsonDeserializationContext;
|
|
||||||
import com.google.gson.JsonDeserializer;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonParseException;
|
|
||||||
import com.google.gson.JsonPrimitive;
|
|
||||||
import com.google.gson.JsonSerializationContext;
|
|
||||||
import com.google.gson.JsonSerializer;
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
|
||||||
import com.google.gson.reflect.TypeToken;
|
|
||||||
|
|
||||||
public class SocketBridgeConfigItem extends KLALBConfigItem {
|
public class SocketBridgeConfigItem extends KLALBConfigItem {
|
||||||
private MultipurposeSocketAddress Listen;
|
private MultiProtocolSocketAddress Listen;
|
||||||
|
|
||||||
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
|
private LinkedHashMap<String, String>Bridge=new LinkedHashMap<>();
|
||||||
private HostPortMap Connect=new HostPortMap();
|
private HostPortMap Connect=new HostPortMap();
|
||||||
|
|
||||||
public SocketBridgeConfigItem( MultipurposeSocketAddress listen,
|
public SocketBridgeConfigItem( MultiProtocolSocketAddress listen,
|
||||||
LinkedHashMap<String, String> bridge, HostPortMap connect) {
|
LinkedHashMap<String, String> bridge, HostPortMap connect) {
|
||||||
super("SocketBridge");
|
super("SocketBridge");
|
||||||
Listen = listen;
|
Listen = listen;
|
||||||
@@ -85,12 +68,12 @@ public class SocketBridgeConfigItem extends KLALBConfigItem {
|
|||||||
public Proxy createProxy(KLALBController controller) throws IOException {
|
public Proxy createProxy(KLALBController controller) throws IOException {
|
||||||
HostPortMap mapp=new HostPortMap();
|
HostPortMap mapp=new HostPortMap();
|
||||||
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
|
LinkedHashMap<String, SocketBridgeFactory>mapb=new LinkedHashMap<>();
|
||||||
MultipurposeSocketAddress l=Listen;
|
MultiProtocolSocketAddress l=Listen;
|
||||||
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
|
SocketBridgeFactory bdg=getDefaultBridgeFactory(Bridge,mapb,controller);
|
||||||
MultipurposeSocketAddress r=getDefaultConnect(Connect,mapp);
|
MultiProtocolSocketAddress r=getDefaultConnect(Connect,mapp);
|
||||||
return new SocketToSocketProxy(l ,new MultipurposeSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
return new SocketToSocketProxy(l ,new MultiProtocolSocketAddress("0.0.0.0:0"), r,mapp,bdg,mapb);
|
||||||
}
|
}
|
||||||
private MultipurposeSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultipurposeSocketAddress> mapp) {
|
private MultiProtocolSocketAddress getDefaultConnect(HostPortMap connect2, LinkedHashMap<String, MultiProtocolSocketAddress> mapp) {
|
||||||
connect2.entrySet().forEach((en)->{
|
connect2.entrySet().forEach((en)->{
|
||||||
mapp.put(en.getKey(), en.getValue());
|
mapp.put(en.getKey(), en.getValue());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -116,29 +116,20 @@ public class DatagramKLALBPacketLink implements KLALBPacketLink {
|
|||||||
}*/
|
}*/
|
||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
import java.net.DatagramSocket;
|
import java.net.DatagramSocket;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket;
|
|
||||||
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
|
import org.kne.cloud.network.DatagramServerSocket.SubDatagramSocket;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.PacketRebuilder;
|
import org.kne.cloud.network.PacketRebuilder;
|
||||||
import org.kne.cloud.network.PacketSpliter;
|
import org.kne.cloud.network.PacketSpliter;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
|
||||||
import org.kne.debug.TimeDebugger;
|
|
||||||
|
|
||||||
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
private PacketSpliter pslr;
|
private PacketSpliter pslr;
|
||||||
@@ -146,7 +137,7 @@ public class SplitedDatagramKLALBPacketLink extends AbstractKLALBPacketLink impl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress("UDP",(InetSocketAddress)ds.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private DatagramSocket ds;
|
private DatagramSocket ds;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.nio.channels.SocketChannel;
|
|||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.kne.cloud.network.BufferedChannel;
|
import org.kne.cloud.network.BufferedChannel;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
import org.kne.cloud.network.NetworkPacket;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.io.KNEChannels;
|
import org.kne.io.KNEChannels;
|
||||||
@@ -45,7 +45,7 @@ public class StreamChannelKLALBPacketLink extends AbstractKLALBPacketLink implem
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
try {
|
try {
|
||||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteAddress());
|
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteAddress());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
return "?←?";
|
return "?←?";
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
package org.kne.cloud.network.klalb;
|
package org.kne.cloud.network.klalb;
|
||||||
|
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
public class StreamKLALBPacketLink extends AbstractKLALBPacketLink implements KLALBPacketLink {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultipurposeSocketAddress("TCP",(InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
return new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getLocalSocketAddress())+"←"+new MultiProtocolSocketAddress((InetSocketAddress)connectSocket.getRemoteSocketAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Socket connectSocket;
|
private Socket connectSocket;
|
||||||
|
|||||||
@@ -346,7 +346,16 @@ public class GraphPanel extends JPanel {
|
|||||||
g.setFont(UIEnv.getFont().deriveFont(10.0f));
|
g.setFont(UIEnv.getFont().deriveFont(10.0f));
|
||||||
|
|
||||||
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
|
//g.drawString(text, (int)(x+nodesize/2), (int)(y-nodesize/3));
|
||||||
g.drawString(text, (int)(x-image.getWidth(null)/2-10), (int)(y+image.getHeight(null)/2+10));
|
int imgw=(image!=null)?image.getWidth(null):(int)nodesize;
|
||||||
|
int imgh=(image!=null)?image.getHeight(null):(int)nodesize;
|
||||||
|
java.awt.FontMetrics fm=g.getFontMetrics();
|
||||||
|
int lineHeight=fm.getHeight()+2;
|
||||||
|
int textx=(int)(x-imgw/2-10);
|
||||||
|
int texty=(int)(y+imgh/2+10)+fm.getAscent();
|
||||||
|
String[] lines=text.split("\n");
|
||||||
|
for (int i = 0; i < lines.length; i++) {
|
||||||
|
g.drawString(lines[i], textx, texty+i*lineHeight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 getPositionVec2() {
|
public Vector2 getPositionVec2() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import org.jfree.chart.plot.dial.*;
|
|||||||
import org.jfree.data.general.DefaultValueDataset;
|
import org.jfree.data.general.DefaultValueDataset;
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.klalb.uitool.*;
|
import org.kne.cloud.klalb.uitool.*;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
import org.kne.cloud.network.klalb.*;
|
import org.kne.cloud.network.klalb.*;
|
||||||
@@ -76,11 +76,16 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private JTextField addressFieldSet; // 地址设置框
|
private JTextField addressFieldSet; // 地址设置框
|
||||||
private JTextField asnFieldSet; // ASN设置框
|
private JTextField asnFieldSet; // ASN设置框
|
||||||
private JTextField tunDeviceName; // 虚拟网卡名称设置框
|
private JTextField tunDeviceName; // 虚拟网卡名称设置框
|
||||||
|
private JTextField webListenSet; // Web API 监听地址设置框
|
||||||
private NetworkGraphPanel graph; // 网络图面板
|
private NetworkGraphPanel graph; // 网络图面板
|
||||||
private JTextField textFieldLocate; // 定位地址输入框
|
private JTextField textFieldLocate; // 定位地址输入框
|
||||||
private JTextField asnField2; // ASN显示框
|
private JTextField asnField2; // ASN显示框
|
||||||
private JTextArea dnsAreaSet; // DNS服务器设置区域
|
private JTextArea dnsAreaSet; // DNS服务器设置区域
|
||||||
private JComboBox comboPerformance; // 性能策略选择框
|
private JTextArea extraRoutesSet; // 额外路由设置区域
|
||||||
|
private JTextField deviceNameSet; // 设备名称设置框
|
||||||
|
private JTextArea deviceDescriptionSet; // 设备描述设置区域
|
||||||
|
private KLALBController kcontroller; // 关联的控制器
|
||||||
|
private JComboBox<PerformanceStrategyItem> comboPerformance; // 性能策略选择框
|
||||||
private JComboBox comboCongestion; // 拥塞控制算法选择框
|
private JComboBox comboCongestion; // 拥塞控制算法选择框
|
||||||
private JTextField tcpListeningSet; // TCP监听设置
|
private JTextField tcpListeningSet; // TCP监听设置
|
||||||
private JTextField udpListeningSet; // UDP监听设置
|
private JTextField udpListeningSet; // UDP监听设置
|
||||||
@@ -92,6 +97,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private JTextArea ntpServerSet; // NTP服务器设置区域
|
private JTextArea ntpServerSet; // NTP服务器设置区域
|
||||||
private JCheckBox denyBroadcast;
|
private JCheckBox denyBroadcast;
|
||||||
private JCheckBox denyQuery;
|
private JCheckBox denyQuery;
|
||||||
|
private JCheckBox webApiEnabled; // 启用 Web API 开关
|
||||||
private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板
|
private ClosableTabbedPane tabbedPane; // 可关闭的标签页面板
|
||||||
private JCheckBox nogui;
|
private JCheckBox nogui;
|
||||||
|
|
||||||
@@ -138,6 +144,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
* 初始化UI界面
|
* 初始化UI界面
|
||||||
*/
|
*/
|
||||||
private void initUI(KLALBController kc, String title) {
|
private void initUI(KLALBController kc, String title) {
|
||||||
|
kcontroller = kc;
|
||||||
// 设置窗口基本属性
|
// 设置窗口基本属性
|
||||||
setIconImage(UIEnv.getIcon());
|
setIconImage(UIEnv.getIcon());
|
||||||
setTitleColor(UIEnv.getDefaultTitleColor());
|
setTitleColor(UIEnv.getDefaultTitleColor());
|
||||||
@@ -597,7 +604,11 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||||
btnNewButton.addActionListener(e -> {
|
btnNewButton.addActionListener(e -> {
|
||||||
try {
|
try {
|
||||||
kc.addRemoteLines(new MultipurposeSocketAddress(textField.getText()));
|
MultiProtocolSocketAddress mpsa= new MultiProtocolSocketAddress(textField.getText());
|
||||||
|
Thread t=new Thread(()->{
|
||||||
|
kc.addRemoteLines(mpsa);
|
||||||
|
});
|
||||||
|
t.start();
|
||||||
} catch (RuntimeException ex) {
|
} catch (RuntimeException ex) {
|
||||||
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
|
JOptionPane.showMessageDialog(KLALBStateGUI3.this, "Input format error", "Error",
|
||||||
JOptionPane.ERROR_MESSAGE);
|
JOptionPane.ERROR_MESSAGE);
|
||||||
@@ -781,12 +792,30 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
addressFieldSet = addr6.getTextField();
|
addressFieldSet = addr6.getTextField();
|
||||||
settings.getView().add(addr6);
|
settings.getView().add(addr6);
|
||||||
|
|
||||||
|
// 设备名称设置
|
||||||
|
TextSettingItem deviceName = new TextSettingItem(UIEnv.getRsb().getString("devicename"),
|
||||||
|
CONST.itemwidth, CONST.settingheight);
|
||||||
|
deviceNameSet = deviceName.getTextField();
|
||||||
|
settings.getView().add(deviceName);
|
||||||
|
|
||||||
|
// 设备描述设置
|
||||||
|
TextAreaSettingItem deviceDescription = new TextAreaSettingItem(UIEnv.getRsb().getString("devicedescription"),
|
||||||
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
|
deviceDescriptionSet = deviceDescription.getTextArea();
|
||||||
|
settings.getView().add(deviceDescription);
|
||||||
|
|
||||||
// DNS服务器设置
|
// DNS服务器设置
|
||||||
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
|
TextAreaSettingItem dns = new TextAreaSettingItem(UIEnv.getRsb().getString("dnsserver"),
|
||||||
CONST.itemwidth, CONST.settingheight * 5);
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
dnsAreaSet = dns.getTextArea();
|
dnsAreaSet = dns.getTextArea();
|
||||||
settings.getView().add(dns);
|
settings.getView().add(dns);
|
||||||
|
|
||||||
|
// 额外路由设置
|
||||||
|
TextAreaSettingItem extraRoutes = new TextAreaSettingItem(UIEnv.getRsb().getString("extraroutes"),
|
||||||
|
CONST.itemwidth, CONST.settingheight * 5);
|
||||||
|
extraRoutesSet = extraRoutes.getTextArea();
|
||||||
|
settings.getView().add(extraRoutes);
|
||||||
|
|
||||||
// ASN设置
|
// ASN设置
|
||||||
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
|
TextSettingItem asn = new TextSettingItem(UIEnv.getRsb().getString("asnumber"),
|
||||||
CONST.itemwidth, CONST.settingheight);
|
CONST.itemwidth, CONST.settingheight);
|
||||||
@@ -878,6 +907,21 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
denyBroadcast=denyBroadcastc.getCheckBox();
|
denyBroadcast=denyBroadcastc.getCheckBox();
|
||||||
settings.getView().add(denyBroadcastc);
|
settings.getView().add(denyBroadcastc);
|
||||||
|
|
||||||
|
// Web服务设置标题
|
||||||
|
SettingItem wsi = new SettingItem(UIEnv.getRsb().getString("webapisettings"),
|
||||||
|
UIEnv.getFont().deriveFont(20.0f).deriveFont(Font.BOLD), CONST.itemwidth, CONST.settingheight);
|
||||||
|
settings.getView().add(wsi);
|
||||||
|
|
||||||
|
CheckBoxSettingItem webApic = new CheckBoxSettingItem(UIEnv.getRsb().getString("enablewebapi"),
|
||||||
|
CONST.itemwidth, CONST.settingheight);
|
||||||
|
webApiEnabled=webApic.getCheckBox();
|
||||||
|
settings.getView().add(webApic);
|
||||||
|
|
||||||
|
TextSettingItem webListen = new TextSettingItem(UIEnv.getRsb().getString("weblistenaddr"),
|
||||||
|
CONST.itemwidth, CONST.settingheight);
|
||||||
|
webListenSet = webListen.getTextField();
|
||||||
|
settings.getView().add(webListen);
|
||||||
|
|
||||||
//性能设置标题
|
//性能设置标题
|
||||||
|
|
||||||
SettingItem pshi = new SettingItem(UIEnv.getRsb().getString("performancesettings"),
|
SettingItem pshi = new SettingItem(UIEnv.getRsb().getString("performancesettings"),
|
||||||
@@ -888,8 +932,10 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
|
performanceStrategy = new ComboSettingItem(UIEnv.getRsb().getString("performancestrategy"),
|
||||||
CONST.itemwidth, CONST.settingheight);
|
CONST.itemwidth, CONST.settingheight);
|
||||||
comboPerformance = performanceStrategy.getComboBox();
|
comboPerformance = performanceStrategy.getComboBox();
|
||||||
comboPerformance.addItem("BBR");
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.SINGLE_CORE));
|
||||||
comboPerformance.addItem("Vegas2");
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_FILL));
|
||||||
|
comboPerformance.addItem(new PerformanceStrategyItem(PerformanceStrategy.MULTI_SCATTER));
|
||||||
|
comboPerformance.setSelectedIndex(1);
|
||||||
settings.getView().add(performanceStrategy);
|
settings.getView().add(performanceStrategy);
|
||||||
|
|
||||||
|
|
||||||
@@ -1004,7 +1050,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
private void saveConfig() {
|
private void saveConfig() {
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
config = new KLALBConfig();
|
config = new KLALBConfig();
|
||||||
config.add(new KLALBControllerConfigItem("KLALBController"));
|
config.add(new KLALBControllerConfigItem());
|
||||||
}
|
}
|
||||||
|
|
||||||
for (KLALBConfigItem item : config) {
|
for (KLALBConfigItem item : config) {
|
||||||
@@ -1016,6 +1062,26 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
kck.setNogui(nogui.isSelected());
|
kck.setNogui(nogui.isSelected());
|
||||||
|
|
||||||
|
// 保存设备名称
|
||||||
|
String dnametext = deviceNameSet.getText().trim();
|
||||||
|
if (dnametext.equals("")) {
|
||||||
|
kck.setDeviceName(null);
|
||||||
|
} else {
|
||||||
|
kck.setDeviceName(dnametext);
|
||||||
|
}
|
||||||
|
if (kcontroller != null && kcontroller.getIpv6Router() != null) {
|
||||||
|
kcontroller.getIpv6Router().setDeviceName(kck.getDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存设备描述
|
||||||
|
String ddesctext = deviceDescriptionSet.getText().trim();
|
||||||
|
if (ddesctext.equals("")) {
|
||||||
|
kck.setDeviceDescription(null);
|
||||||
|
} else {
|
||||||
|
kck.setDeviceDescription(ddesctext);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 保存IPv6地址
|
// 保存IPv6地址
|
||||||
String ttext = addressFieldSet.getText().trim();
|
String ttext = addressFieldSet.getText().trim();
|
||||||
if (ttext.equals("")) {
|
if (ttext.equals("")) {
|
||||||
@@ -1052,6 +1118,24 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
kck.setDNS(iaddr);
|
kck.setDNS(iaddr);
|
||||||
|
|
||||||
|
// 保存额外路由列表
|
||||||
|
String[] spltEr = extraRoutesSet.getText().split("\n");
|
||||||
|
List<String> eroutes = new ArrayList<>();
|
||||||
|
for (String str : spltEr) {
|
||||||
|
str = str.trim();
|
||||||
|
if (!str.isEmpty()) {
|
||||||
|
try {
|
||||||
|
eroutes.add(KLALBUtils.parseCidr(str));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildextraroutes"),
|
||||||
|
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kck.setExtraRoutes(eroutes);
|
||||||
|
|
||||||
// 保存ASN
|
// 保存ASN
|
||||||
String asntext = asnFieldSet.getText();
|
String asntext = asnFieldSet.getText();
|
||||||
if (asntext.equals("")) {
|
if (asntext.equals("")) {
|
||||||
@@ -1067,20 +1151,36 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String tunNameText=tunDeviceName.getText();
|
String tunNameText=tunDeviceName.getText().trim();
|
||||||
if(tunNameText.equals("")) {
|
if(tunNameText.equals("") || tunNameText.equalsIgnoreCase("null")) {
|
||||||
kck.setTUNName(null);
|
kck.setTUNName(null);
|
||||||
}else {
|
}else {
|
||||||
kck.setTUNName(tunNameText);
|
kck.setTUNName(tunNameText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存Web API设置
|
||||||
|
kck.setWebUI(webApiEnabled.isSelected());
|
||||||
|
String webListenText = webListenSet.getText().trim();
|
||||||
|
if (webListenText.equals("")) {
|
||||||
|
kck.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", 4665));
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
kck.setWebListen(new MultiProtocolSocketAddress(webListenText));
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildweblistenaddr"),
|
||||||
|
UIEnv.getRsb().getString("warning"), JOptionPane.WARNING_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 保存TCP监听设置
|
// 保存TCP监听设置
|
||||||
String tcptext = tcpListeningSet.getText();
|
String tcptext = tcpListeningSet.getText();
|
||||||
if (tcptext.equals("")) {
|
if (tcptext.equals("")) {
|
||||||
kck.setTCPListen(null);
|
kck.setTCPListen(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
kck.setTCPListen(new MultipurposeSocketAddress(tcptext));
|
kck.setTCPListen(new MultiProtocolSocketAddress(tcptext));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildtcplisten"),
|
||||||
@@ -1095,7 +1195,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
kck.setUDPListen(null);
|
kck.setUDPListen(null);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
kck.setUDPListen(new MultipurposeSocketAddress(udptext));
|
kck.setUDPListen(new MultiProtocolSocketAddress(udptext,"udp"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
|
JOptionPane.showMessageDialog(this, UIEnv.getRsb().getString("invaildudplisten"),
|
||||||
@@ -1106,13 +1206,13 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
// 保存开放线路表
|
// 保存开放线路表
|
||||||
String[] splt1 = openLineTabelSet.getText().split("\n");
|
String[] splt1 = openLineTabelSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr1 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr1 = new ArrayList<>();
|
||||||
boolean show1 = true;
|
boolean show1 = true;
|
||||||
for (String str : splt1) {
|
for (String str : splt1) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr1.add(new MultipurposeSocketAddress(str));
|
iaddr1.add(new MultiProtocolSocketAddress(str));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show1) {
|
if (show1) {
|
||||||
@@ -1123,17 +1223,17 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
kck.setLineTable(iaddr1);
|
kck.setExternalEndpoints(iaddr1);
|
||||||
|
|
||||||
// 保存自动连接线路表
|
// 保存自动连接线路表
|
||||||
String[] splt11 = connectLineTabelSet.getText().split("\n");
|
String[] splt11 = connectLineTabelSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr11 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr11 = new ArrayList<>();
|
||||||
boolean show11 = true;
|
boolean show11 = true;
|
||||||
for (String str : splt11) {
|
for (String str : splt11) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr11.add(new MultipurposeSocketAddress(str));
|
iaddr11.add(new MultiProtocolSocketAddress(str));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show11) {
|
if (show11) {
|
||||||
@@ -1148,13 +1248,13 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
// 保存NTP服务器表
|
// 保存NTP服务器表
|
||||||
String[] splt111 = ntpServerSet.getText().split("\n");
|
String[] splt111 = ntpServerSet.getText().split("\n");
|
||||||
List<MultipurposeSocketAddress> iaddr111 = new ArrayList<>();
|
List<MultiProtocolSocketAddress> iaddr111 = new ArrayList<>();
|
||||||
boolean show111 = true;
|
boolean show111 = true;
|
||||||
for (String str : splt111) {
|
for (String str : splt111) {
|
||||||
try {
|
try {
|
||||||
str = str.trim();
|
str = str.trim();
|
||||||
if (!str.isEmpty())
|
if (!str.isEmpty())
|
||||||
iaddr111.add(new MultipurposeSocketAddress(str));
|
iaddr111.add(new MultiProtocolSocketAddress(str,"ntp"));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
if (show111) {
|
if (show111) {
|
||||||
@@ -1176,9 +1276,14 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue());
|
kck.setLinkConnectionsCount(linkConnectionsCount.getSlider().getValue());
|
||||||
|
|
||||||
kck.setDenyLineTableQuery(denyQuery.isSelected());
|
kck.setDenyExternalEndpointQuery(denyQuery.isSelected());
|
||||||
|
|
||||||
kck.setDenyLineTableBroadcast(denyBroadcast.isSelected());
|
kck.setDenyExternalEndpointBroadcast(denyBroadcast.isSelected());
|
||||||
|
|
||||||
|
PerformanceStrategyItem psi=((PerformanceStrategyItem)comboPerformance.getSelectedItem());
|
||||||
|
if(psi!=null) {
|
||||||
|
kck.setPerformanceStrategy(psi.getStrategy().toString());
|
||||||
|
}
|
||||||
|
|
||||||
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
|
kck.setCongestionAlgorithm((String) congestions.getComboBox().getSelectedItem());
|
||||||
|
|
||||||
@@ -1213,6 +1318,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
tsk = new TimerTask() {
|
tsk = new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
if (isVisible()) {
|
||||||
Object[] kll = kc.getLines().toArray();
|
Object[] kll = kc.getLines().toArray();
|
||||||
|
|
||||||
// 遍历所有线路,更新或添加TPanel2
|
// 遍历所有线路,更新或添加TPanel2
|
||||||
@@ -1242,7 +1348,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
if (!found) {
|
if (!found) {
|
||||||
TPanel2 tp = new TPanel2(ent, kc);
|
TPanel2 tp = new TPanel2(ent, kc);
|
||||||
tp.setVisible(showoffline.isSelected() || (
|
tp.setVisible(showoffline.isSelected() || (
|
||||||
ent.getState()!= LinkStatus.DOWN));
|
ent.getState() != LinkStatus.DOWN));
|
||||||
ysp.getView().add(tp);
|
ysp.getView().add(tp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1266,6 +1372,7 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
ysp.updateScrool();
|
ysp.updateScrool();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
t.scheduleAtFixedRate(tsk, 1000, 1000);
|
t.scheduleAtFixedRate(tsk, 1000, 1000);
|
||||||
|
|
||||||
@@ -1440,6 +1547,14 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
|
|
||||||
nogui.setSelected( kck.isNogui());
|
nogui.setSelected( kck.isNogui());
|
||||||
|
|
||||||
|
// 加载设备名称
|
||||||
|
String dname = kck.getDeviceName();
|
||||||
|
deviceNameSet.setText(dname != null ? dname : "");
|
||||||
|
|
||||||
|
// 加载设备描述
|
||||||
|
String ddesc = kck.getDeviceDescription();
|
||||||
|
deviceDescriptionSet.setText(ddesc != null ? ddesc : "");
|
||||||
|
|
||||||
// 加载IPv6地址
|
// 加载IPv6地址
|
||||||
String vaddr = kck.getVirtualAddress();
|
String vaddr = kck.getVirtualAddress();
|
||||||
addressFieldSet.setText(vaddr != null ? vaddr : "");
|
addressFieldSet.setText(vaddr != null ? vaddr : "");
|
||||||
@@ -1448,6 +1563,10 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
List<InetAddress> dns = kck.getDNS();
|
List<InetAddress> dns = kck.getDNS();
|
||||||
dnsAreaSet.setText(listToStr(dns));
|
dnsAreaSet.setText(listToStr(dns));
|
||||||
|
|
||||||
|
// 加载额外路由
|
||||||
|
List<String> ers = kck.getExtraRoutes();
|
||||||
|
extraRoutesSet.setText(listToStr2(ers));
|
||||||
|
|
||||||
// 加载ASN
|
// 加载ASN
|
||||||
Long vasn = kck.getVirtualASN();
|
Long vasn = kck.getVirtualASN();
|
||||||
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
asnFieldSet.setText(vasn != null ? vasn.toString() : "");
|
||||||
@@ -1455,24 +1574,29 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
String tunname=kck.getTUNName();
|
String tunname=kck.getTUNName();
|
||||||
tunDeviceName.setText(tunname!=null?tunname:"");
|
tunDeviceName.setText(tunname!=null?tunname:"");
|
||||||
|
|
||||||
|
// 加载Web API设置
|
||||||
|
webApiEnabled.setSelected(kck.isWebUI());
|
||||||
|
webListenSet.setText(kck.getWebListen() != null ? kck.getWebListen().toString()
|
||||||
|
: "http://0.0.0.0:4665");
|
||||||
|
|
||||||
// 加载TCP监听
|
// 加载TCP监听
|
||||||
MultipurposeSocketAddress mpat = kck.getTCPListen();
|
MultiProtocolSocketAddress mpat = kck.getTCPListen();
|
||||||
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
|
tcpListeningSet.setText(mpat != null ? mpat.toString() : "");
|
||||||
|
|
||||||
// 加载UDP监听
|
// 加载UDP监听
|
||||||
MultipurposeSocketAddress mpau = kck.getUDPListen();
|
MultiProtocolSocketAddress mpau = kck.getUDPListen();
|
||||||
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
|
udpListeningSet.setText(mpau != null ? mpau.toString() : "");
|
||||||
|
|
||||||
// 加载开放线路表
|
// 加载开放线路表
|
||||||
List<MultipurposeSocketAddress> linet = kck.getLineTable();
|
List<MultiProtocolSocketAddress> linet = kck.getExternalEndpoints();
|
||||||
openLineTabelSet.setText(listToStr2(linet));
|
openLineTabelSet.setText(listToStr2(linet));
|
||||||
|
|
||||||
// 加载自动连接线路表
|
// 加载自动连接线路表
|
||||||
List<MultipurposeSocketAddress> clinet = kck.getConnectLineTable();
|
List<MultiProtocolSocketAddress> clinet = kck.getConnectLineTable();
|
||||||
connectLineTabelSet.setText(listToStr2(clinet));
|
connectLineTabelSet.setText(listToStr2(clinet));
|
||||||
|
|
||||||
// 加载NTP服务器表
|
// 加载NTP服务器表
|
||||||
List<MultipurposeSocketAddress> ntps = kck.getNtpServerTable();
|
List<MultiProtocolSocketAddress> ntps = kck.getNtpServerTable();
|
||||||
ntpServerSet.setText(listToStr2(ntps));
|
ntpServerSet.setText(listToStr2(ntps));
|
||||||
|
|
||||||
// 加载网络接口排除列表
|
// 加载网络接口排除列表
|
||||||
@@ -1493,9 +1617,20 @@ public class KLALBStateGUI3 extends XFrame {
|
|||||||
}
|
}
|
||||||
linkConnectionsCount.getSlider().setValue(conc);
|
linkConnectionsCount.getSlider().setValue(conc);
|
||||||
|
|
||||||
denyQuery.setSelected( kck.isDenyLineTableQuery());
|
denyQuery.setSelected( kck.isDenyExternalEndpointQuery());
|
||||||
|
|
||||||
denyBroadcast.setSelected( kck.isDenyLineTableBroadcast());
|
denyBroadcast.setSelected( kck.isDenyExternalEndpointBroadcast());
|
||||||
|
|
||||||
|
String stategy= kck.getPerformanceStrategy();
|
||||||
|
PerformanceStrategy pfs=PerformanceStrategy.fromDescription(stategy);
|
||||||
|
if(pfs!=null) {
|
||||||
|
for (int i=0;i<comboPerformance.getItemCount();i++){
|
||||||
|
if(((PerformanceStrategyItem)comboPerformance.getModel().getElementAt(i)).getStrategy().equals(pfs)) {
|
||||||
|
comboPerformance.setSelectedIndex(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String con=kck.getCongestionAlgorithm();
|
String con=kck.getCongestionAlgorithm();
|
||||||
if(con==null) {
|
if(con==null) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import javax.swing.border.LineBorder;
|
|||||||
|
|
||||||
import org.jfree.chart.plot.dial.DialTextAnnotation;
|
import org.jfree.chart.plot.dial.DialTextAnnotation;
|
||||||
import org.jfree.data.general.DefaultValueDataset;
|
import org.jfree.data.general.DefaultValueDataset;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.klalb.CONST;
|
import org.kne.cloud.network.klalb.CONST;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
import org.kne.cloud.network.monitor.SpeedAndTrafficAndDelayMonitorDataImpl;
|
||||||
@@ -143,9 +143,9 @@ public class KperfGUI extends XFrame{
|
|||||||
if(kperf==null) {
|
if(kperf==null) {
|
||||||
try {
|
try {
|
||||||
String tar=targetAddressField.getText();
|
String tar=targetAddressField.getText();
|
||||||
MultipurposeSocketAddress msa=new MultipurposeSocketAddress(tar);
|
MultiProtocolSocketAddress msa=new MultiProtocolSocketAddress(tar);
|
||||||
String sour=sourceAddressField.getText();
|
String sour=sourceAddressField.getText();
|
||||||
MultipurposeSocketAddress msas=new MultipurposeSocketAddress(sour);
|
MultiProtocolSocketAddress msas=new MultiProtocolSocketAddress(sour);
|
||||||
kperf=new Kperf(msa,msas);
|
kperf=new Kperf(msa,msas);
|
||||||
kperf.setReports(reports);
|
kperf.setReports(reports);
|
||||||
kperf.startPerfing();
|
kperf.startPerfing();
|
||||||
@@ -650,10 +650,10 @@ public class KperfGUI extends XFrame{
|
|||||||
startButton.setText(UIEnv.getRsb().getString("stoptest"));
|
startButton.setText(UIEnv.getRsb().getString("stoptest"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void setTarget(MultipurposeSocketAddress target) {
|
public void setTarget(MultiProtocolSocketAddress target) {
|
||||||
targetAddressField.setText(target.toString());
|
targetAddressField.setText(target.toString());
|
||||||
}
|
}
|
||||||
public void setTarget(MultipurposeSocketAddress target,MultipurposeSocketAddress source) {
|
public void setTarget(MultiProtocolSocketAddress target, MultiProtocolSocketAddress source) {
|
||||||
targetAddressField.setText(target.toString());
|
targetAddressField.setText(target.toString());
|
||||||
sourceAddressField.setText(source.toString());
|
sourceAddressField.setText(source.toString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,48 +1,25 @@
|
|||||||
package org.kne.cloud.network.klalb.ui;
|
package org.kne.cloud.network.klalb.ui;
|
||||||
|
|
||||||
import java.awt.BasicStroke;
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.FontMetrics;
|
|
||||||
import java.awt.Graphics;
|
|
||||||
import java.awt.Graphics2D;
|
|
||||||
import java.awt.Image;
|
import java.awt.Image;
|
||||||
import java.awt.Rectangle;
|
|
||||||
import java.awt.Shape;
|
|
||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.awt.event.MouseListener;
|
import java.awt.event.MouseListener;
|
||||||
import java.awt.geom.Dimension2D;
|
|
||||||
import java.awt.geom.GeneralPath;
|
|
||||||
import java.awt.geom.Point2D;
|
|
||||||
import java.awt.image.ImageObserver;
|
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.text.AttributedCharacterIterator;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
import javax.swing.border.LineBorder;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
||||||
|
|
||||||
public class NetworkGraphPanel extends GraphPanel {
|
public class NetworkGraphPanel extends GraphPanel {
|
||||||
@@ -70,7 +47,7 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
|
public InetGraphNode(IPv6Address address, Color color, double x, double y, boolean ismarked) {
|
||||||
super(getText(address), color, x, y, ismarked);
|
super(getNodeText(address), color, x, y, ismarked);
|
||||||
this.address=address;
|
this.address=address;
|
||||||
initign();
|
initign();
|
||||||
}
|
}
|
||||||
@@ -105,8 +82,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
KperfGUI kpfg=new KperfGUI();
|
KperfGUI kpfg=new KperfGUI();
|
||||||
kpfg.setVisible(true);
|
kpfg.setVisible(true);
|
||||||
MultipurposeSocketAddress source=new MultipurposeSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
MultiProtocolSocketAddress source=new MultiProtocolSocketAddress("KLALB_Stream",controller.getIpv6Router().getLocator().getAddress().toString(),0);
|
||||||
MultipurposeSocketAddress target=new MultipurposeSocketAddress("KLALB_Stream", address.toString(), 4564);
|
MultiProtocolSocketAddress target=new MultiProtocolSocketAddress("KLALB_Stream", address.toString(), 4564);
|
||||||
kpfg.setTarget(target,source);
|
kpfg.setTarget(target,source);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -160,17 +137,33 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
return address2.toCompressedString();
|
return address2.toCompressedString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateLabel() {
|
||||||
|
setText(getNodeText(address));
|
||||||
|
}
|
||||||
|
|
||||||
public IPv6Address getAddress() {
|
public IPv6Address getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAddress(IPv6Address address) {
|
public void setAddress(IPv6Address address) {
|
||||||
this.address = address;
|
this.address = address;
|
||||||
super.setText(getText(address));
|
super.setText(getNodeText(address));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点标签:显示广播得知的设备名称(若有)+IP地址
|
||||||
|
*/
|
||||||
|
private String getNodeText(IPv6Address address) {
|
||||||
|
StringBuilder sb=new StringBuilder();
|
||||||
|
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
|
||||||
|
if(dname!=null)
|
||||||
|
sb.append(dname).append('\n');
|
||||||
|
sb.append(InetGraphNode.getText(address));
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
private double nsPerPixel=1000L;
|
private double nsPerPixel=1000L;
|
||||||
private class InetGraphEdgeGroup extends GraphEdgeGroup{
|
private class InetGraphEdgeGroup extends GraphEdgeGroup{
|
||||||
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
|
public InetGraphEdgeGroup(GraphNode nodeA, GraphNode nodeB) {
|
||||||
@@ -201,6 +194,8 @@ public class NetworkGraphPanel extends GraphPanel {
|
|||||||
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
IPv6Address inet6Address = (IPv6Address) iterator.next();
|
||||||
if(!addr.containsKey(inet6Address)) {
|
if(!addr.containsKey(inet6Address)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
|
}else {
|
||||||
|
((InetGraphNode)getNodes().get(inet6Address)).updateLabel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//System.out.println("------------------------------------");
|
//System.out.println("------------------------------------");
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import java.awt.Image;
|
|||||||
import java.awt.Toolkit;
|
import java.awt.Toolkit;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.Icon;
|
import javax.swing.Icon;
|
||||||
import javax.swing.ImageIcon;
|
import javax.swing.ImageIcon;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
import javax.swing.JButton;
|
import javax.swing.JButton;
|
||||||
import javax.swing.JPanel;
|
import javax.swing.JPanel;
|
||||||
import javax.swing.JPopupMenu;
|
import javax.swing.JPopupMenu;
|
||||||
@@ -22,7 +21,7 @@ import javax.swing.event.ListSelectionEvent;
|
|||||||
import javax.swing.event.ListSelectionListener;
|
import javax.swing.event.ListSelectionListener;
|
||||||
|
|
||||||
import org.kne.cloud.klalb.uitool.XDefaultListModel;
|
import org.kne.cloud.klalb.uitool.XDefaultListModel;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6Address;
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||||
@@ -30,7 +29,6 @@ import org.kne.cloud.network.srv6.KLALBRoutingProtocolAPIClient;
|
|||||||
import javax.swing.JTabbedPane;
|
import javax.swing.JTabbedPane;
|
||||||
import javax.swing.JList;
|
import javax.swing.JList;
|
||||||
import javax.swing.JMenuItem;
|
import javax.swing.JMenuItem;
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
|
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
@@ -43,7 +41,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
private KLALBController controller;
|
private KLALBController controller;
|
||||||
private Image image;
|
private Image image;
|
||||||
|
|
||||||
private XDefaultListModel<MultipurposeSocketAddress> listModel=new XDefaultListModel<>();
|
private XDefaultListModel<MultiProtocolSocketAddress> listModel=new XDefaultListModel<>();
|
||||||
public KLALBController getController() {
|
public KLALBController getController() {
|
||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
@@ -69,11 +67,28 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
panel.setLayout(new BorderLayout(0, 0));
|
panel.setLayout(new BorderLayout(0, 0));
|
||||||
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
|
tabbedPane.addTab(UIEnv.getRsb().getString("overview"), null, panel, null);
|
||||||
|
|
||||||
|
JTextArea overviewArea = new JTextArea();
|
||||||
|
overviewArea.setEditable(false);
|
||||||
|
overviewArea.setLineWrap(true);
|
||||||
|
overviewArea.setWrapStyleWord(true);
|
||||||
|
overviewArea.setFont(UIEnv.getFont().deriveFont(14.0f));
|
||||||
|
overviewArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
|
||||||
|
String dname=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
|
||||||
|
String ddesc=null;
|
||||||
|
if(address.equals(controller.getIpv6Router().getLocator().getAddress())&&controller.getConfigItem()!=null) {
|
||||||
|
ddesc=controller.getConfigItem().getDeviceDescription();
|
||||||
|
if(ddesc!=null&&ddesc.isEmpty()) {
|
||||||
|
ddesc=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
overviewArea.setText(buildOverviewText(dname, ddesc));
|
||||||
|
panel.add(new JScrollPane(overviewArea), BorderLayout.CENTER);
|
||||||
|
|
||||||
JPanel panel_1 = new JPanel();
|
JPanel panel_1 = new JPanel();
|
||||||
panel_1.setLayout(new BorderLayout(0, 0));
|
panel_1.setLayout(new BorderLayout(0, 0));
|
||||||
panel_1.add(scrollPane);
|
panel_1.add(scrollPane);
|
||||||
|
|
||||||
JList<MultipurposeSocketAddress> list = new JList<MultipurposeSocketAddress>(listModel);
|
JList<MultiProtocolSocketAddress> list = new JList<MultiProtocolSocketAddress>(listModel);
|
||||||
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
|
||||||
scrollPane.setViewportView(list);
|
scrollPane.setViewportView(list);
|
||||||
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
|
tabbedPane.addTab(UIEnv.getRsb().getString("openlinetable"), null, panel_1, null);
|
||||||
@@ -81,7 +96,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
JButton btnNewButton = new JButton(UIEnv.getRsb().getString("addline"));
|
||||||
btnNewButton.addActionListener(new ActionListener() {
|
btnNewButton.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
controller.addRemoteLines(select);
|
controller.addRemoteLines(select);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -93,7 +108,7 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void valueChanged(ListSelectionEvent e) {
|
public void valueChanged(ListSelectionEvent e) {
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
btnNewButton.setEnabled( !select.isEmpty()) ;
|
btnNewButton.setEnabled( !select.isEmpty()) ;
|
||||||
copy.setEnabled(!select.isEmpty());
|
copy.setEnabled(!select.isEmpty());
|
||||||
}
|
}
|
||||||
@@ -103,9 +118,9 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
StringBuilder sb=new StringBuilder();
|
StringBuilder sb=new StringBuilder();
|
||||||
List<MultipurposeSocketAddress> select=list.getSelectedValuesList();
|
List<MultiProtocolSocketAddress> select=list.getSelectedValuesList();
|
||||||
for (MultipurposeSocketAddress multipurposeSocketAddress : select) {
|
for (MultiProtocolSocketAddress multiProtocolSocketAddress : select) {
|
||||||
sb.append(multipurposeSocketAddress);
|
sb.append(multiProtocolSocketAddress);
|
||||||
sb.append('\n');
|
sb.append('\n');
|
||||||
}
|
}
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(sb.toString()), null);
|
||||||
@@ -150,16 +165,38 @@ public class NodeInformationPanel extends JPanel {
|
|||||||
|
|
||||||
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
|
client=new KLALBRoutingProtocolAPIClient(controller.getIpv6Router().getKlalbRouteProtol());
|
||||||
try {
|
try {
|
||||||
client.requestOpenLines(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (result)->{
|
client.requestNodeInfoFull(new InetSocketAddress( address.toInet6Address(), KLALBRoutingProtocol.DEFAULT_PORT), (info)->{
|
||||||
listModel.clear();
|
listModel.clear();
|
||||||
for (MultipurposeSocketAddress multipurposeSocketAddress : result) {
|
for (MultiProtocolSocketAddress multiProtocolSocketAddress : info.getOpenLines()) {
|
||||||
listModel.addElement(multipurposeSocketAddress);
|
listModel.addElement(multiProtocolSocketAddress);
|
||||||
}
|
}
|
||||||
|
// 用对端返回的设备名称/描述更新概览(旧版本节点无该字段时保留原显示)
|
||||||
|
String dn=info.getDeviceName();
|
||||||
|
if(dn==null||dn.isEmpty()) {
|
||||||
|
dn=controller.getIpv6Router().getKlalbRouteProtol().getDeviceName(address);
|
||||||
|
}
|
||||||
|
String dd=info.getDeviceDescription();
|
||||||
|
if(dd!=null&&dd.isEmpty()) {
|
||||||
|
dd=null;
|
||||||
|
}
|
||||||
|
overviewArea.setText(buildOverviewText(dn, dd));
|
||||||
});
|
});
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String buildOverviewText(String dname,String ddesc) {
|
||||||
|
StringBuilder sb=new StringBuilder();
|
||||||
|
sb.append(UIEnv.getRsb().getString("ipv6addr")).append(": ").append(address.toCompressedString()).append('\n');
|
||||||
|
if(dname!=null&&!dname.isEmpty()) {
|
||||||
|
sb.append('\n').append(UIEnv.getRsb().getString("devicename")).append(": ").append(dname).append('\n');
|
||||||
|
}
|
||||||
|
if(ddesc!=null&&!ddesc.isEmpty()) {
|
||||||
|
sb.append('\n').append(UIEnv.getRsb().getString("devicedescription")).append(":\n").append(ddesc).append('\n');
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
public Icon getIcon() {
|
public Icon getIcon() {
|
||||||
return new ImageIcon(image);
|
return new ImageIcon(image);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.kne.cloud.network.klalb.ui;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
|
||||||
|
public class PerformanceStrategyItem {
|
||||||
|
private PerformanceStrategy strategy;
|
||||||
|
|
||||||
|
public PerformanceStrategyItem(PerformanceStrategy strategy) {
|
||||||
|
this.strategy = strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceStrategy getStrategy() {
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return UIEnv.getRsb().getString(strategy.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import java.awt.Dimension;
|
|||||||
import javax.swing.SwingConstants;
|
import javax.swing.SwingConstants;
|
||||||
import javax.swing.border.LineBorder;
|
import javax.swing.border.LineBorder;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||||
import org.kne.cloud.network.klalb.CONST;
|
import org.kne.cloud.network.klalb.CONST;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
@@ -219,7 +219,7 @@ public class TPanel2 extends JPanel {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
MultipurposeSocketAddress mtar=tunnel.getSocketAddress();
|
MultiProtocolSocketAddress mtar=tunnel.getSocketAddress();
|
||||||
if(mtar!=null) {
|
if(mtar!=null) {
|
||||||
kc.removeRemoteLines(mtar);
|
kc.removeRemoteLines(mtar);
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -0,0 +1,898 @@
|
|||||||
|
package org.kne.cloud.network.klalb.web;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.*;
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
import org.kne.cloud.network.ipv6.IPv6Address;
|
||||||
|
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
||||||
|
import org.kne.cloud.network.ipv6.IPv6NetworkLink;
|
||||||
|
import org.kne.cloud.network.ipv6.RouteItem;
|
||||||
|
import org.kne.cloud.network.klalb.*;
|
||||||
|
import org.kne.cloud.network.monitor.LinkStatus;
|
||||||
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol;
|
||||||
|
import org.kne.cloud.network.srv6.KLALBRoutingProtocol.LinkDirection;
|
||||||
|
import org.kne.cloud.network.srv6.NeighborInfo;
|
||||||
|
import org.kne.cloud.network.srv6.RouterInfo;
|
||||||
|
import org.kne.cloud.network.srv6.SRv6Router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KLALB Web Server providing REST API, SSE live event stream,
|
||||||
|
* and static dashboard hosting.
|
||||||
|
*/
|
||||||
|
public class KLALBWebServer {
|
||||||
|
private final KLALBProxySystem proxySystem;
|
||||||
|
private final MultiProtocolSocketAddress listen;
|
||||||
|
private HttpServer server;
|
||||||
|
private final Gson gson;
|
||||||
|
private final ScheduledExecutorService sseExecutor = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||||
|
Thread t = new Thread(r, "KLALB-Web-SSE");
|
||||||
|
t.setDaemon(true);
|
||||||
|
return t;
|
||||||
|
});
|
||||||
|
private final Set<HttpExchange> sseClients = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||||
|
private final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
public KLALBWebServer(KLALBProxySystem proxySystem, int port) {
|
||||||
|
this(proxySystem, new MultiProtocolSocketAddress("http", "0.0.0.0", port));
|
||||||
|
}
|
||||||
|
|
||||||
|
public KLALBWebServer(KLALBProxySystem proxySystem, MultiProtocolSocketAddress listen) {
|
||||||
|
this.proxySystem = proxySystem;
|
||||||
|
this.listen = listen;
|
||||||
|
this.gson = proxySystem.getGson();
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void start() throws IOException {
|
||||||
|
if (running.get()) return;
|
||||||
|
|
||||||
|
InetSocketAddress socketAddress = "0.0.0.0".equals(listen.getHost())
|
||||||
|
? new InetSocketAddress(listen.getPort()) : listen.getSocketAddress();
|
||||||
|
server = HttpServer.create(socketAddress, 0);
|
||||||
|
server.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
|
||||||
|
|
||||||
|
// API Contexts
|
||||||
|
server.createContext("/api/status", this::handleStatus);
|
||||||
|
server.createContext("/api/events", this::handleEvents);
|
||||||
|
server.createContext("/api/links", this::handleLinks);
|
||||||
|
server.createContext("/api/links/action", this::handleLinkAction);
|
||||||
|
server.createContext("/api/links/reconnect", this::handleReconnectAll);
|
||||||
|
server.createContext("/api/routes", this::handleRoutes);
|
||||||
|
server.createContext("/api/nodes", this::handleNodes);
|
||||||
|
server.createContext("/api/interfaces", this::handleInterfaces);
|
||||||
|
server.createContext("/api/config", this::handleConfig);
|
||||||
|
|
||||||
|
// Static Files / SPA Fallback Handler
|
||||||
|
server.createContext("/", this::handleStatic);
|
||||||
|
|
||||||
|
server.start();
|
||||||
|
running.set(true);
|
||||||
|
startSseBroadcaster();
|
||||||
|
|
||||||
|
System.out.println("KLALB Web Dashboard started at " + listen);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void stop() {
|
||||||
|
if (!running.get()) return;
|
||||||
|
running.set(false);
|
||||||
|
sseExecutor.shutdownNow();
|
||||||
|
for (HttpExchange client : sseClients) {
|
||||||
|
try {
|
||||||
|
client.close();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
sseClients.clear();
|
||||||
|
if (server != null) {
|
||||||
|
server.stop(1);
|
||||||
|
server = null;
|
||||||
|
}
|
||||||
|
System.out.println("KLALB Web Dashboard stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return running.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPort() {
|
||||||
|
return listen.getPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setCorsHeaders(HttpExchange exchange) {
|
||||||
|
Headers headers = exchange.getResponseHeaders();
|
||||||
|
headers.set("Access-Control-Allow-Origin", "*");
|
||||||
|
headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
||||||
|
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleCorsPreflight(HttpExchange exchange) throws IOException {
|
||||||
|
setCorsHeaders(exchange);
|
||||||
|
if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
exchange.sendResponseHeaders(204, -1);
|
||||||
|
exchange.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendJsonResponse(HttpExchange exchange, int statusCode, Object data) throws IOException {
|
||||||
|
setCorsHeaders(exchange);
|
||||||
|
String json;
|
||||||
|
if (data instanceof JsonElement) {
|
||||||
|
// gson-2.1 的 toJson(Object) 会按运行时类型 JsonObject 反射序列化出内部的 members 字段,
|
||||||
|
// 必须走 JsonElement 重载(或 toString)直接输出 JSON 树
|
||||||
|
json = ((JsonElement) data).toString();
|
||||||
|
} else {
|
||||||
|
json = gson.toJson(data);
|
||||||
|
}
|
||||||
|
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
|
||||||
|
exchange.sendResponseHeaders(statusCode, bytes.length);
|
||||||
|
try (OutputStream os = exchange.getResponseBody()) {
|
||||||
|
os.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendError(HttpExchange exchange, int statusCode, String message) throws IOException {
|
||||||
|
JsonObject error = new JsonObject();
|
||||||
|
error.addProperty("error", message);
|
||||||
|
sendJsonResponse(exchange, statusCode, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readRequestBody(HttpExchange exchange) throws IOException {
|
||||||
|
try (InputStream is = exchange.getRequestBody()) {
|
||||||
|
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// API Handlers
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
private JsonObject getStatusJson() {
|
||||||
|
JsonObject root = new JsonObject();
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
if (kc == null) {
|
||||||
|
root.addProperty("status", "not_ready");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.addProperty("version", CONST.klalbver);
|
||||||
|
root.addProperty("title", CONST.klalb);
|
||||||
|
|
||||||
|
IPv6AddressGroup self = kc.getSelf();
|
||||||
|
root.addProperty("address", self != null ? self.getAddress().toString() : "");
|
||||||
|
|
||||||
|
SRv6Router router = kc.getIpv6Router();
|
||||||
|
if (router != null && router.getKlalbRouteProtol() != null) {
|
||||||
|
root.addProperty("onlineDevices", router.getKlalbRouteProtol().getDevicesFound());
|
||||||
|
root.addProperty("deviceName", router.getDeviceName() != null ? router.getDeviceName() : "");
|
||||||
|
} else {
|
||||||
|
root.addProperty("onlineDevices", 0);
|
||||||
|
root.addProperty("deviceName", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
KLALBControllerConfigItem configItem = proxySystem.getControllerConfig();
|
||||||
|
if (configItem != null) {
|
||||||
|
root.addProperty("deviceDescription", configItem.getDeviceDescription() != null ? configItem.getDeviceDescription() : "");
|
||||||
|
root.addProperty("enableTUN", configItem.isEnableTUN());
|
||||||
|
root.addProperty("tunName", configItem.isEnableTUN() && configItem.getTUNName() != null ? configItem.getTUNName() : "");
|
||||||
|
root.addProperty("congestionAlgorithm", configItem.getCongestionAlgorithm() != null ? configItem.getCongestionAlgorithm() : "");
|
||||||
|
root.addProperty("performanceStrategy", configItem.getPerformanceStrategy() != null ? configItem.getPerformanceStrategy() : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock
|
||||||
|
HighAccuracyClock hac = kc.getClock();
|
||||||
|
if (hac != null) {
|
||||||
|
long delta = hac.getFrequency() - 1000000000;
|
||||||
|
double ppm = delta / 1000.0;
|
||||||
|
root.addProperty("timeStr", hac.toString2());
|
||||||
|
root.addProperty("timePpm", ppm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
JsonObject metrics = new JsonObject();
|
||||||
|
if (kc.getLinkMonitor() != null) {
|
||||||
|
metrics.addProperty("upSpeed", kc.getLinkMonitor().getOutSpeed());
|
||||||
|
metrics.addProperty("downSpeed", kc.getLinkMonitor().getInSpeed());
|
||||||
|
metrics.addProperty("upSpeedMax", kc.getLinkMonitor().getOutSpeedMax());
|
||||||
|
metrics.addProperty("downSpeedMax", kc.getLinkMonitor().getInSpeedMax());
|
||||||
|
metrics.addProperty("upPPS", kc.getLinkMonitor().getOutPPS());
|
||||||
|
metrics.addProperty("downPPS", kc.getLinkMonitor().getInPPS());
|
||||||
|
metrics.addProperty("upPPSMax", kc.getLinkMonitor().getOutPPSMax());
|
||||||
|
metrics.addProperty("downPPSMax", kc.getLinkMonitor().getInPPSMax());
|
||||||
|
metrics.addProperty("upTraffic", kc.getLinkMonitor().getOutTraffic());
|
||||||
|
metrics.addProperty("downTraffic", kc.getLinkMonitor().getInTraffic());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (kc.getDatatMonitor() != null) {
|
||||||
|
metrics.addProperty("dataUpSpeed", kc.getDatatMonitor().getOutSpeed());
|
||||||
|
metrics.addProperty("dataDownSpeed", kc.getDatatMonitor().getInSpeed());
|
||||||
|
metrics.addProperty("dataUpPPS", kc.getDatatMonitor().getOutPPS());
|
||||||
|
metrics.addProperty("dataDownPPS", kc.getDatatMonitor().getInPPS());
|
||||||
|
metrics.addProperty("dataUpTraffic", kc.getDatatMonitor().getOutTraffic());
|
||||||
|
metrics.addProperty("dataDownTraffic", kc.getDatatMonitor().getInTraffic());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (router != null) {
|
||||||
|
metrics.addProperty("backplaneDelay", router.getBackplaneTime());
|
||||||
|
metrics.addProperty("backplanePPS", router.getBackplanePPS());
|
||||||
|
metrics.addProperty("backplanePPSMax", router.getBackplanePPSMax());
|
||||||
|
metrics.addProperty("backplaneECNRate", router.getECNRate());
|
||||||
|
metrics.addProperty("backplaneLossRate", router.getLossRate());
|
||||||
|
}
|
||||||
|
root.add("metrics", metrics);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleStatus(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendJsonResponse(exchange, 200, getStatusJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonArray getLinksJson() {
|
||||||
|
JsonArray array = new JsonArray();
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
if (kc == null) return array;
|
||||||
|
|
||||||
|
List<IPv6NetworkLink> linesList = kc.getLines();
|
||||||
|
synchronized (linesList) {
|
||||||
|
for (IPv6NetworkLink link : linesList) {
|
||||||
|
if (!(link instanceof KLALBRemoteLink)) continue;
|
||||||
|
KLALBRemoteLink remoteLink = (KLALBRemoteLink) link;
|
||||||
|
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("name", remoteLink.getName());
|
||||||
|
obj.addProperty("state", LinkStatus.stateToString(remoteLink.getState()));
|
||||||
|
obj.addProperty("stateCode", remoteLink.getState());
|
||||||
|
|
||||||
|
MultiProtocolSocketAddress mpsa = remoteLink.getSocketAddress();
|
||||||
|
obj.addProperty("socketAddress", mpsa != null ? mpsa.toString() : "");
|
||||||
|
|
||||||
|
IPv6AddressGroup vaddr = remoteLink.getRemoteVaddr();
|
||||||
|
obj.addProperty("vaddr", vaddr != null ? vaddr.getAddress().toString() : "");
|
||||||
|
|
||||||
|
if (remoteLink.getMonitor() != null) {
|
||||||
|
obj.addProperty("upSpeed", remoteLink.getMonitor().getOutSpeed());
|
||||||
|
obj.addProperty("downSpeed", remoteLink.getMonitor().getInSpeed());
|
||||||
|
obj.addProperty("upPPS", remoteLink.getMonitor().getOutPPS());
|
||||||
|
obj.addProperty("downPPS", remoteLink.getMonitor().getInPPS());
|
||||||
|
obj.addProperty("upTraffic", remoteLink.getMonitor().getOutTraffic());
|
||||||
|
obj.addProperty("downTraffic", remoteLink.getMonitor().getInTraffic());
|
||||||
|
obj.addProperty("upDelay", remoteLink.getMonitor().getOutDelay());
|
||||||
|
obj.addProperty("downDelay", remoteLink.getMonitor().getInDelay());
|
||||||
|
obj.addProperty("upDelayMin", remoteLink.getMonitor().getOutDelayMin());
|
||||||
|
obj.addProperty("downDelayMin", remoteLink.getMonitor().getInDelayMin());
|
||||||
|
obj.addProperty("upJitter", remoteLink.getMonitor().getOutJitter());
|
||||||
|
obj.addProperty("downJitter", remoteLink.getMonitor().getInJitter());
|
||||||
|
}
|
||||||
|
array.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLinks(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
String method = exchange.getRequestMethod();
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
|
||||||
|
if ("GET".equalsIgnoreCase(method)) {
|
||||||
|
sendJsonResponse(exchange, 200, getLinksJson());
|
||||||
|
} else if ("POST".equalsIgnoreCase(method)) {
|
||||||
|
String body = readRequestBody(exchange);
|
||||||
|
try {
|
||||||
|
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
|
||||||
|
String address = req.get("address").getAsString().trim();
|
||||||
|
if (address.isEmpty()) {
|
||||||
|
sendError(exchange, 400, "Address is required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(address);
|
||||||
|
List<KLALBRemoteLink> added = kc.addRemoteLines(mpsa);
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
resp.addProperty("success", !added.isEmpty());
|
||||||
|
resp.addProperty("count", added.size());
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(exchange, 400, "Failed to add link: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else if ("DELETE".equalsIgnoreCase(method)) {
|
||||||
|
String query = exchange.getRequestURI().getQuery();
|
||||||
|
String address = null;
|
||||||
|
if (query != null && query.startsWith("address=")) {
|
||||||
|
address = URLDecoder.decode(query.substring(8), StandardCharsets.UTF_8);
|
||||||
|
} else {
|
||||||
|
String body = readRequestBody(exchange);
|
||||||
|
if (!body.isEmpty()) {
|
||||||
|
try {
|
||||||
|
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
|
||||||
|
if (req.has("address")) {
|
||||||
|
address = req.get("address").getAsString();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address == null || address.trim().isEmpty()) {
|
||||||
|
sendError(exchange, 400, "Address parameter required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(address.trim());
|
||||||
|
List<KLALBRemoteLink> removed = kc.removeRemoteLines(mpsa);
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
resp.addProperty("success", !removed.isEmpty());
|
||||||
|
resp.addProperty("count", removed.size());
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(exchange, 400, "Failed to remove link: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleLinkAction(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String body = readRequestBody(exchange);
|
||||||
|
try {
|
||||||
|
JsonObject req = new JsonParser().parse(body).getAsJsonObject();
|
||||||
|
String action = req.get("action").getAsString();
|
||||||
|
String address = req.has("address") ? req.get("address").getAsString() : null;
|
||||||
|
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
KLALBRemoteLink target = null;
|
||||||
|
if (address != null) {
|
||||||
|
for (IPv6NetworkLink link : kc.getLines()) {
|
||||||
|
if (link instanceof KLALBRemoteLink rl) {
|
||||||
|
if (rl.getName().equalsIgnoreCase(address) ||
|
||||||
|
(rl.getSocketAddress() != null && rl.getSocketAddress().toString().equalsIgnoreCase(address))) {
|
||||||
|
target = rl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
if (target != null) {
|
||||||
|
switch (action) {
|
||||||
|
case "reconnect" -> {
|
||||||
|
target.reconnectImmediately();
|
||||||
|
resp.addProperty("success", true);
|
||||||
|
}
|
||||||
|
case "disconnect" -> {
|
||||||
|
target.dislink();
|
||||||
|
resp.addProperty("success", true);
|
||||||
|
}
|
||||||
|
case "remove" -> {
|
||||||
|
MultiProtocolSocketAddress mtar = target.getSocketAddress();
|
||||||
|
if (mtar != null) {
|
||||||
|
kc.removeRemoteLines(mtar);
|
||||||
|
} else {
|
||||||
|
target.close();
|
||||||
|
}
|
||||||
|
resp.addProperty("success", true);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
sendError(exchange, 400, "Unknown action: " + action);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 404, "Target link not found: " + address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(exchange, 400, "Failed to execute link action: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleReconnectAll(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
if (kc != null) {
|
||||||
|
kc.reconnectImmediately();
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
resp.addProperty("success", true);
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 500, "Controller not ready");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRoutes(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
JsonArray routesArray = new JsonArray();
|
||||||
|
if (kc != null && kc.getIpv6Router() != null) {
|
||||||
|
List<RouteItem> list = new ArrayList<>(kc.getIpv6Router().getCurrentRouteTabel());
|
||||||
|
Collections.sort(list);
|
||||||
|
for (RouteItem item : list) {
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("destination", item.getDestination() != null ? item.getDestination().toString() : "");
|
||||||
|
obj.addProperty("protocol", item.getProto());
|
||||||
|
obj.addProperty("preference", item.getPre());
|
||||||
|
obj.addProperty("cost", item.getCost());
|
||||||
|
obj.addProperty("flag", item.getFlag());
|
||||||
|
obj.addProperty("nexthop", item.getNexthop() != null ? item.getNexthop().toString() : "");
|
||||||
|
obj.addProperty("interface", item.getDestlink() != null ? item.getDestlink().getName() : "");
|
||||||
|
routesArray.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendJsonResponse(exchange, 200, routesArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleNodes(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
JsonObject result = new JsonObject();
|
||||||
|
JsonArray nodesArray = new JsonArray();
|
||||||
|
JsonArray edgesArray = new JsonArray();
|
||||||
|
|
||||||
|
if (kc != null && kc.getIpv6Router() != null && kc.getIpv6Router().getKlalbRouteProtol() != null) {
|
||||||
|
KLALBRoutingProtocol rproto = kc.getIpv6Router().getKlalbRouteProtol();
|
||||||
|
Map<IPv6Address, Long> addrs = rproto.getAddresses();
|
||||||
|
IPv6Address selfAddr = kc.getIpv6Router().getLocator().getAddress();
|
||||||
|
|
||||||
|
if (addrs != null) {
|
||||||
|
for (IPv6Address addr : addrs.keySet()) {
|
||||||
|
JsonObject nodeObj = new JsonObject();
|
||||||
|
nodeObj.addProperty("id", addr.toString());
|
||||||
|
nodeObj.addProperty("address", addr.toString());
|
||||||
|
nodeObj.addProperty("compressedAddress", addr.toCompressedString());
|
||||||
|
nodeObj.addProperty("isSelf", addr.equals(selfAddr));
|
||||||
|
String dname = rproto.getDeviceName(addr);
|
||||||
|
nodeObj.addProperty("deviceName", dname != null ? dname : "");
|
||||||
|
nodesArray.add(nodeObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<IPv6Address, RouterInfo> netmap = rproto.getNetmap();
|
||||||
|
if (netmap != null) {
|
||||||
|
Set<String> seenEdges = new HashSet<>();
|
||||||
|
for (Map.Entry<IPv6Address, RouterInfo> entry : netmap.entrySet()) {
|
||||||
|
IPv6Address from = entry.getKey();
|
||||||
|
RouterInfo info = entry.getValue();
|
||||||
|
if (info != null && info.getNeighborAddresses() != null) {
|
||||||
|
for (NeighborInfo nb : info.getNeighborAddresses()) {
|
||||||
|
if (nb != null && nb.getLocator() != null) {
|
||||||
|
String to = nb.getLocator().getAddress().toString();
|
||||||
|
String edgeKey = from.toString().compareTo(to) < 0
|
||||||
|
? from.toString() + "->" + to
|
||||||
|
: to + "->" + from.toString();
|
||||||
|
if (!seenEdges.contains(edgeKey)) {
|
||||||
|
seenEdges.add(edgeKey);
|
||||||
|
JsonObject edgeObj = new JsonObject();
|
||||||
|
edgeObj.addProperty("source", from.toString());
|
||||||
|
edgeObj.addProperty("target", to);
|
||||||
|
edgeObj.addProperty("delay", nb.getUploadDelay());
|
||||||
|
edgeObj.addProperty("cost", nb.getUploadDelayMin());
|
||||||
|
edgesArray.add(edgeObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.add("nodes", nodesArray);
|
||||||
|
result.add("edges", edgesArray);
|
||||||
|
sendJsonResponse(exchange, 200, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleInterfaces(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KLALBController kc = proxySystem.getKlalbController();
|
||||||
|
JsonArray ifacesArray = new JsonArray();
|
||||||
|
if (kc != null && kc.getNetworkInterfaceManager() != null) {
|
||||||
|
List<NetworkInterface> ifaces = kc.getNetworkInterfaceManager().getAllAvaliableNetworkInterface();
|
||||||
|
for (NetworkInterface nif : ifaces) {
|
||||||
|
JsonObject obj = new JsonObject();
|
||||||
|
obj.addProperty("name", nif.getName());
|
||||||
|
obj.addProperty("displayName", nif.getDisplayName());
|
||||||
|
JsonArray ips = new JsonArray();
|
||||||
|
List<InetAddress> addrs = kc.getNetworkInterfaceManager().getNetworkInterfaceAddress(nif);
|
||||||
|
for (InetAddress a : addrs) {
|
||||||
|
ips.add(new JsonPrimitive(a.getHostAddress()));
|
||||||
|
}
|
||||||
|
obj.add("addresses", ips);
|
||||||
|
ifacesArray.add(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sendJsonResponse(exchange, 200, ifacesArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConfig(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
String method = exchange.getRequestMethod();
|
||||||
|
|
||||||
|
if ("GET".equalsIgnoreCase(method)) {
|
||||||
|
KLALBControllerConfigItem config = proxySystem.getControllerConfig();
|
||||||
|
if (config != null) {
|
||||||
|
sendJsonResponse(exchange, 200, config);
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 404, "Configuration not found");
|
||||||
|
}
|
||||||
|
} else if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) {
|
||||||
|
String body = readRequestBody(exchange);
|
||||||
|
try {
|
||||||
|
JsonObject json = new JsonParser().parse(body).getAsJsonObject();
|
||||||
|
KLALBControllerConfigItem current = proxySystem.getControllerConfig();
|
||||||
|
if (current != null) {
|
||||||
|
if (json.has("deviceName") && !json.get("deviceName").isJsonNull()) {
|
||||||
|
current.setDeviceName(json.get("deviceName").getAsString());
|
||||||
|
} else if (json.has("DeviceName") && !json.get("DeviceName").isJsonNull()) {
|
||||||
|
current.setDeviceName(json.get("DeviceName").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("deviceDescription") && !json.get("deviceDescription").isJsonNull()) {
|
||||||
|
current.setDeviceDescription(json.get("deviceDescription").getAsString());
|
||||||
|
} else if (json.has("DeviceDescription") && !json.get("DeviceDescription").isJsonNull()) {
|
||||||
|
current.setDeviceDescription(json.get("DeviceDescription").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("language") && !json.get("language").isJsonNull()) {
|
||||||
|
current.setLanguage(json.get("language").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("virtualAddress") && !json.get("virtualAddress").isJsonNull()) {
|
||||||
|
current.setVirtualAddress(json.get("virtualAddress").getAsString());
|
||||||
|
} else if (json.has("VirtualAddress") && !json.get("VirtualAddress").isJsonNull()) {
|
||||||
|
current.setVirtualAddress(json.get("VirtualAddress").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("virtualASN") && !json.get("virtualASN").isJsonNull()) {
|
||||||
|
current.setVirtualASN(json.get("virtualASN").getAsLong());
|
||||||
|
} else if (json.has("VirtualASN") && !json.get("VirtualASN").isJsonNull()) {
|
||||||
|
current.setVirtualASN(json.get("VirtualASN").getAsLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("virtualSocketName") && !json.get("virtualSocketName").isJsonNull()) {
|
||||||
|
current.setVirtualSocketName(json.get("virtualSocketName").getAsString());
|
||||||
|
} else if (json.has("VirtualSocketName") && !json.get("VirtualSocketName").isJsonNull()) {
|
||||||
|
current.setVirtualSocketName(json.get("VirtualSocketName").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("enableTUN")) {
|
||||||
|
current.setEnableTUN(json.get("enableTUN").getAsBoolean());
|
||||||
|
} else if (json.has("EnableTUN")) {
|
||||||
|
current.setEnableTUN(json.get("EnableTUN").getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("tunName") && !json.get("tunName").isJsonNull()) {
|
||||||
|
current.setTUNName(json.get("tunName").getAsString());
|
||||||
|
} else if (json.has("TUNName") && !json.get("TUNName").isJsonNull()) {
|
||||||
|
current.setTUNName(json.get("TUNName").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("webUI")) {
|
||||||
|
current.setWebUI(json.get("webUI").getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("webListen") && !json.get("webListen").isJsonNull()) {
|
||||||
|
current.setWebListen(new MultiProtocolSocketAddress(json.get("webListen").getAsString()));
|
||||||
|
} else if (json.has("webPort")) {
|
||||||
|
current.setWebListen(new MultiProtocolSocketAddress("http", "0.0.0.0", json.get("webPort").getAsInt()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("nogui")) {
|
||||||
|
current.setNogui(json.get("nogui").getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("tcpListen") && !json.get("tcpListen").isJsonNull()) {
|
||||||
|
current.setTCPListen(new MultiProtocolSocketAddress(json.get("tcpListen").getAsString()));
|
||||||
|
} else if (json.has("TCPListen") && !json.get("TCPListen").isJsonNull()) {
|
||||||
|
current.setTCPListen(new MultiProtocolSocketAddress(json.get("TCPListen").getAsString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("udpListen") && !json.get("udpListen").isJsonNull()) {
|
||||||
|
current.setUDPListen(new MultiProtocolSocketAddress(json.get("udpListen").getAsString()));
|
||||||
|
} else if (json.has("UDPListen") && !json.get("UDPListen").isJsonNull()) {
|
||||||
|
current.setUDPListen(new MultiProtocolSocketAddress(json.get("UDPListen").getAsString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("externalEndpoints") || json.has("ExternalEndpoints") || json.has("openConnections") || json.has("OpenConnections") || json.has("lineTable") || json.has("LineTable")) {
|
||||||
|
JsonArray arr = json.has("externalEndpoints") ? json.getAsJsonArray("externalEndpoints")
|
||||||
|
: json.has("ExternalEndpoints") ? json.getAsJsonArray("ExternalEndpoints")
|
||||||
|
: json.has("openConnections") ? json.getAsJsonArray("openConnections")
|
||||||
|
: json.has("OpenConnections") ? json.getAsJsonArray("OpenConnections")
|
||||||
|
: json.has("lineTable") ? json.getAsJsonArray("lineTable") : json.getAsJsonArray("LineTable");
|
||||||
|
List<MultiProtocolSocketAddress> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
list.add(new MultiProtocolSocketAddress(el.getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setExternalEndpoints(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("autoConnections") || json.has("AutoConnections") || json.has("connectLineTable") || json.has("ConnectLineTable")) {
|
||||||
|
JsonArray arr = json.has("autoConnections") ? json.getAsJsonArray("autoConnections")
|
||||||
|
: json.has("AutoConnections") ? json.getAsJsonArray("AutoConnections")
|
||||||
|
: json.has("connectLineTable") ? json.getAsJsonArray("connectLineTable") : json.getAsJsonArray("ConnectLineTable");
|
||||||
|
List<MultiProtocolSocketAddress> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
list.add(new MultiProtocolSocketAddress(el.getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setAutoConnections(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("ntpServers") || json.has("NtpServers") || json.has("ntpServerTable")) {
|
||||||
|
JsonArray arr = json.has("ntpServers") ? json.getAsJsonArray("ntpServers")
|
||||||
|
: json.has("NtpServers") ? json.getAsJsonArray("NtpServers")
|
||||||
|
: json.getAsJsonArray("ntpServerTable");
|
||||||
|
List<MultiProtocolSocketAddress> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
list.add(new MultiProtocolSocketAddress(el.getAsString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setNtpServers(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("dns") || json.has("DNS")) {
|
||||||
|
JsonArray arr = json.has("dns") ? json.getAsJsonArray("dns") : json.getAsJsonArray("DNS");
|
||||||
|
List<InetAddress> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
try {
|
||||||
|
list.add(InetAddress.getByName(el.getAsString()));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setDNS(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("extraRoutes") || json.has("ExtraRoutes")) {
|
||||||
|
JsonArray arr = json.has("extraRoutes") ? json.getAsJsonArray("extraRoutes") : json.getAsJsonArray("ExtraRoutes");
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
list.add(el.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setExtraRoutes(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("networkInterfaceExcepts") || json.has("NetworkInterfaceExcepts")) {
|
||||||
|
JsonArray arr = json.has("networkInterfaceExcepts") ? json.getAsJsonArray("networkInterfaceExcepts") : json.getAsJsonArray("NetworkInterfaceExcepts");
|
||||||
|
List<String> list = new ArrayList<>();
|
||||||
|
for (JsonElement el : arr) {
|
||||||
|
if (el.isJsonPrimitive()) {
|
||||||
|
list.add(el.getAsString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
current.setNetworkInterfaceExcepts(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("congestionAlgorithm") && !json.get("congestionAlgorithm").isJsonNull()) {
|
||||||
|
current.setCongestionAlgorithm(json.get("congestionAlgorithm").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("performanceStrategy") && !json.get("performanceStrategy").isJsonNull()) {
|
||||||
|
current.setPerformanceStrategy(json.get("performanceStrategy").getAsString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("linkConnectionsCount")) {
|
||||||
|
current.setLinkConnectionsCount(json.get("linkConnectionsCount").getAsInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("burstLimit")) {
|
||||||
|
current.setBurstLimit(json.get("burstLimit").getAsDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("delayUpperBound")) {
|
||||||
|
current.setDelayUpperBound(json.get("delayUpperBound").getAsDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("delayLowerBound")) {
|
||||||
|
current.setDelayLowerBound(json.get("delayLowerBound").getAsDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("nagleDelayTime")) {
|
||||||
|
current.setNagleDelayTime(json.get("nagleDelayTime").getAsLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("linkNagleDelayTime")) {
|
||||||
|
current.setLinkNagleDelayTime(json.get("linkNagleDelayTime").getAsLong());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("denyExternalEndpointQuery")) {
|
||||||
|
current.setDenyExternalEndpointQuery(json.get("denyExternalEndpointQuery").getAsBoolean());
|
||||||
|
} else if (json.has("denyConnectionQuery")) {
|
||||||
|
current.setDenyExternalEndpointQuery(json.get("denyConnectionQuery").getAsBoolean());
|
||||||
|
} else if (json.has("denyLineTableQuery")) {
|
||||||
|
current.setDenyExternalEndpointQuery(json.get("denyLineTableQuery").getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json.has("denyExternalEndpointBroadcast")) {
|
||||||
|
current.setDenyExternalEndpointBroadcast(json.get("denyExternalEndpointBroadcast").getAsBoolean());
|
||||||
|
} else if (json.has("denyConnectionBroadcast")) {
|
||||||
|
current.setDenyExternalEndpointBroadcast(json.get("denyConnectionBroadcast").getAsBoolean());
|
||||||
|
} else if (json.has("denyLineTableBroadcast")) {
|
||||||
|
current.setDenyExternalEndpointBroadcast(json.get("denyLineTableBroadcast").getAsBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger GUI save consumer or save directly
|
||||||
|
if (proxySystem.getKLALBGUI() != null && proxySystem.getKLALBGUI().getSaveComsumer() != null) {
|
||||||
|
proxySystem.getKLALBGUI().getSaveComsumer().accept(proxySystem.getConfig());
|
||||||
|
} else {
|
||||||
|
proxySystem.saveConfigToFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonObject resp = new JsonObject();
|
||||||
|
resp.addProperty("success", true);
|
||||||
|
resp.addProperty("message", "Configuration updated successfully");
|
||||||
|
sendJsonResponse(exchange, 200, resp);
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 500, "Current configuration is null");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
sendError(exchange, 400, "Failed to update configuration: " + e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// SSE Handler & Broadcaster
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
private void handleEvents(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
if (!"GET".equalsIgnoreCase(exchange.getRequestMethod())) {
|
||||||
|
sendError(exchange, 405, "Method not allowed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Headers headers = exchange.getResponseHeaders();
|
||||||
|
headers.set("Content-Type", "text/event-stream; charset=utf-8");
|
||||||
|
headers.set("Cache-Control", "no-cache, no-transform");
|
||||||
|
headers.set("Connection", "keep-alive");
|
||||||
|
headers.set("Access-Control-Allow-Origin", "*");
|
||||||
|
|
||||||
|
exchange.sendResponseHeaders(200, 0);
|
||||||
|
sseClients.add(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startSseBroadcaster() {
|
||||||
|
sseExecutor.scheduleAtFixedRate(() -> {
|
||||||
|
if (sseClients.isEmpty()) return;
|
||||||
|
|
||||||
|
JsonObject eventData = new JsonObject();
|
||||||
|
eventData.add("status", getStatusJson());
|
||||||
|
eventData.add("links", getLinksJson());
|
||||||
|
String eventStr = "data: " + eventData.toString() + "\n\n";
|
||||||
|
byte[] bytes = eventStr.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
Iterator<HttpExchange> it = sseClients.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
HttpExchange ex = it.next();
|
||||||
|
try {
|
||||||
|
OutputStream os = ex.getResponseBody();
|
||||||
|
os.write(bytes);
|
||||||
|
os.flush();
|
||||||
|
} catch (Exception e) {
|
||||||
|
try {
|
||||||
|
ex.close();
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 200, 200, TimeUnit.MILLISECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// Static Resource / SPA Handler
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
|
||||||
|
private void handleStatic(HttpExchange exchange) throws IOException {
|
||||||
|
if (handleCorsPreflight(exchange)) return;
|
||||||
|
|
||||||
|
String path = exchange.getRequestURI().getPath();
|
||||||
|
if (path.startsWith("/api/")) {
|
||||||
|
sendError(exchange, 404, "API endpoint not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look for dashboard/dist
|
||||||
|
File distDir = new File("dashboard/dist");
|
||||||
|
File targetFile = null;
|
||||||
|
|
||||||
|
if (distDir.exists() && distDir.isDirectory()) {
|
||||||
|
String relPath = path.equals("/") ? "index.html" : path.substring(1);
|
||||||
|
targetFile = new File(distDir, relPath);
|
||||||
|
if (!targetFile.exists() || targetFile.isDirectory()) {
|
||||||
|
targetFile = new File(distDir, "index.html");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetFile != null && targetFile.exists() && targetFile.isFile()) {
|
||||||
|
String mime = getMimeType(targetFile.getName());
|
||||||
|
byte[] content = Files.readAllBytes(targetFile.toPath());
|
||||||
|
setCorsHeaders(exchange);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", mime);
|
||||||
|
exchange.sendResponseHeaders(200, content.length);
|
||||||
|
try (OutputStream os = exchange.getResponseBody()) {
|
||||||
|
os.write(content);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback welcome message if frontend not built yet
|
||||||
|
String html = "<!DOCTYPE html><html><head><meta charset='utf-8'><title>KLALB Web Dashboard</title>" +
|
||||||
|
"<style>body{font-family:system-ui,sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#09090b;color:#fafafa}" +
|
||||||
|
".card{background:#18181b;padding:2rem;border-radius:0.75rem;border:1px solid #27272a;max-width:480px;text-align:center}" +
|
||||||
|
"h1{margin:0 0 0.5rem;font-size:1.5rem}p{color:#a1a1aa;margin:0 0 1rem;font-size:0.875rem}code{background:#27272a;padding:0.2rem 0.4rem;border-radius:0.25rem}</style></head>" +
|
||||||
|
"<body><div class='card'><h1>KLALB Web Dashboard API Active</h1>" +
|
||||||
|
"<p>The backend API is ready. Start the frontend dev server or build the dashboard: <br><code>cd dashboard && pnpm build</code></p>" +
|
||||||
|
"<p><a href='/api/status' style='color:#38bdf8'>View /api/status</a></p></div></body></html>";
|
||||||
|
byte[] bytes = html.getBytes(StandardCharsets.UTF_8);
|
||||||
|
setCorsHeaders(exchange);
|
||||||
|
exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
|
||||||
|
exchange.sendResponseHeaders(200, bytes.length);
|
||||||
|
try (OutputStream os = exchange.getResponseBody()) {
|
||||||
|
os.write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getMimeType(String filename) {
|
||||||
|
String lower = filename.toLowerCase();
|
||||||
|
if (lower.endsWith(".html")) return "text/html; charset=utf-8";
|
||||||
|
if (lower.endsWith(".js") || lower.endsWith(".mjs")) return "application/javascript; charset=utf-8";
|
||||||
|
if (lower.endsWith(".css")) return "text/css; charset=utf-8";
|
||||||
|
if (lower.endsWith(".json")) return "application/json; charset=utf-8";
|
||||||
|
if (lower.endsWith(".svg")) return "image/svg+xml";
|
||||||
|
if (lower.endsWith(".png")) return "image/png";
|
||||||
|
if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
|
||||||
|
if (lower.endsWith(".ico")) return "image/x-icon";
|
||||||
|
if (lower.endsWith(".woff2")) return "font/woff2";
|
||||||
|
if (lower.endsWith(".woff")) return "font/woff";
|
||||||
|
if (lower.endsWith(".ttf")) return "font/ttf";
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -258,7 +258,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
|||||||
case KLTPPacket.KLTP_TYPE_DATA:
|
case KLTPPacket.KLTP_TYPE_DATA:
|
||||||
//ackSequenceBatcher.putMessage(kseq);
|
//ackSequenceBatcher.putMessage(kseq);
|
||||||
recvMap.put(kltp.getSequence(), kltp);
|
recvMap.put(kltp.getSequence(), kltp);
|
||||||
controller.getIpv6Router().runPacketSendTask(()->{
|
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||||
pack.setCE(u.isCE());
|
pack.setCE(u.isCE());
|
||||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||||
@@ -267,7 +267,7 @@ public class KLTPInputStream extends InputStream implements KLTPPacketConsumer,
|
|||||||
case KLTPPacket.KLTP_TYPE_DATAFIN:
|
case KLTPPacket.KLTP_TYPE_DATAFIN:
|
||||||
//ackSequenceBatcher.putMessage(kseq2);
|
//ackSequenceBatcher.putMessage(kseq2);
|
||||||
recvMap.put(kltp.getSequence(), kltp);
|
recvMap.put(kltp.getSequence(), kltp);
|
||||||
controller.getIpv6Router().runPacketSendTask(()->{
|
controller.getIpv6Router().enqueuePacketSendTask(()->{
|
||||||
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
KLTPPacket pack=new KLTPPacket(streamUUID,KLTPPacket.KLTP_TYPE_ACK,kltp.getSequence(),0);
|
||||||
pack.setCE(u.isCE());
|
pack.setCE(u.isCE());
|
||||||
return controller.createPacketToAddress(remoteaddr,0,pack);
|
return controller.createPacketToAddress(remoteaddr,0,pack);
|
||||||
|
|||||||
@@ -1,23 +1,22 @@
|
|||||||
package org.kne.cloud.network.minecraft;
|
package org.kne.cloud.network.minecraft;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||||
import org.kne.cloud.network.scanner.PortRange;
|
import org.kne.cloud.network.scanner.PortRange;
|
||||||
import org.kne.cloud.network.scanner.ScanRange;
|
import org.kne.cloud.network.scanner.ScanRange;
|
||||||
import org.kne.cloud.network.scanner.TCPNetworkScanner;
|
import org.kne.cloud.network.scanner.TCPNetworkScanner;
|
||||||
|
|
||||||
public class MinecraftScanner extends TCPNetworkScanner{
|
public class MinecraftScanner extends TCPNetworkScanner{
|
||||||
private BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
private BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger>minecraftConsumer;
|
||||||
|
|
||||||
public BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
public BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> getMinecraftConsumer() {
|
||||||
return minecraftConsumer;
|
return minecraftConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setMinecraftConsumer(BiConsumer<MultipurposeSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
public void setMinecraftConsumer(BiConsumer<MultiProtocolSocketAddress, MinecraftServerPinger> minecraftConsumer) {
|
||||||
this.minecraftConsumer = minecraftConsumer;
|
this.minecraftConsumer = minecraftConsumer;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import javax.naming.directory.Attributes;
|
|||||||
import javax.naming.directory.DirContext;
|
import javax.naming.directory.DirContext;
|
||||||
import javax.naming.directory.InitialDirContext;
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class MinecraftServerAddress extends MultipurposeSocketAddress
|
public class MinecraftServerAddress extends MultiProtocolSocketAddress
|
||||||
{
|
{
|
||||||
|
|
||||||
private String originAddr;
|
private String originAddr;
|
||||||
|
|||||||
@@ -3,14 +3,8 @@ package org.kne.cloud.network.minecraft;
|
|||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectOutput;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.io.StringReader;
|
|
||||||
import java.net.Inet6Address;
|
import java.net.Inet6Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@@ -19,13 +13,11 @@ import java.util.Base64;
|
|||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import com.google.gson.JsonParser;
|
import com.google.gson.JsonParser;
|
||||||
import com.google.gson.stream.JsonReader;
|
|
||||||
import com.google.gson.stream.JsonToken;
|
|
||||||
|
|
||||||
public class MinecraftServerPinger implements Serializable{
|
public class MinecraftServerPinger implements Serializable{
|
||||||
/**
|
/**
|
||||||
@@ -34,7 +26,7 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
private transient Socket socket;
|
private transient Socket socket;
|
||||||
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
|
private transient MinecraftProtocolContext mpc=new MinecraftProtocolContext();
|
||||||
private MultipurposeSocketAddress targetaddr;
|
private MultiProtocolSocketAddress targetaddr;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -53,11 +45,11 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
private String icon;
|
private String icon;
|
||||||
private transient BufferedImage image;
|
private transient BufferedImage image;
|
||||||
|
|
||||||
public MinecraftServerPinger(MultipurposeSocketAddress target) throws UnknownHostException, IOException {
|
public MinecraftServerPinger(MultiProtocolSocketAddress target) throws UnknownHostException, IOException {
|
||||||
this(target,target.connectSocket());
|
this(target,target.connectSocket());
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftServerPinger(MultipurposeSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
public MinecraftServerPinger(MultiProtocolSocketAddress target, Socket Asocket) throws UnknownHostException, IOException{
|
||||||
targetaddr=target;
|
targetaddr=target;
|
||||||
socket=Asocket;
|
socket=Asocket;
|
||||||
socket.setTcpNoDelay(true);
|
socket.setTcpNoDelay(true);
|
||||||
@@ -302,7 +294,7 @@ public class MinecraftServerPinger implements Serializable{
|
|||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getTargetaddr() {
|
public MultiProtocolSocketAddress getTargetaddr() {
|
||||||
return targetaddr;
|
return targetaddr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package org.kne.cloud.network.minecraft;
|
|||||||
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.scanner.InetAddressRange;
|
import org.kne.cloud.network.scanner.InetAddressRange;
|
||||||
import org.kne.cloud.network.scanner.PortRange;
|
import org.kne.cloud.network.scanner.PortRange;
|
||||||
import org.kne.cloud.network.scanner.ScanRange;
|
import org.kne.cloud.network.scanner.ScanRange;
|
||||||
@@ -13,7 +13,7 @@ public class MinecraftServerScanRange extends ScanRange {
|
|||||||
super(addressRange, portRange);
|
super(addressRange, portRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftServerScanRange(MultipurposeSocketAddress begin, MultipurposeSocketAddress end)
|
public MinecraftServerScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end)
|
||||||
throws UnknownHostException {
|
throws UnknownHostException {
|
||||||
super(begin, end);
|
super(begin, end);
|
||||||
}
|
}
|
||||||
@@ -25,8 +25,8 @@ public class MinecraftServerScanRange extends ScanRange {
|
|||||||
@Override
|
@Override
|
||||||
protected void setMsaString(String range) throws UnknownHostException {
|
protected void setMsaString(String range) throws UnknownHostException {
|
||||||
String[]sp=range.split("~");
|
String[]sp=range.split("~");
|
||||||
MultipurposeSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
MultiProtocolSocketAddress begin=MinecraftServerAddress.findAddress (sp[0]);
|
||||||
MultipurposeSocketAddress end;
|
MultiProtocolSocketAddress end;
|
||||||
if(sp.length>1) {
|
if(sp.length>1) {
|
||||||
end=MinecraftServerAddress.findAddress (sp[1]);
|
end=MinecraftServerAddress.findAddress (sp[1]);
|
||||||
}else {
|
}else {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.UUID;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
|
import org.kne.concurrent.LazyEvaluator;
|
||||||
|
|
||||||
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
|
public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements SpeedAndTrafficMonitorData {
|
||||||
|
|
||||||
@@ -16,11 +17,12 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private final long TIME_WINDOW=2000000000L;
|
||||||
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
|
public SpeedAndTrafficMonitorDataImpl(HighAccuracyClock clock) {
|
||||||
super();
|
super();
|
||||||
this.clock=clock;
|
this.clock=clock;
|
||||||
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, 5000000000L);
|
uploadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"UploadMonitor", 100, TIME_WINDOW);
|
||||||
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, 5000000000L);
|
downloadBandwidth=new ArrayListTimestampMonitor<UUID>(clock,"DownloadMonitor", 100, TIME_WINDOW);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,12 +88,13 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> outSpeedEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculateBandwidth(timewindow);});
|
||||||
public long getOutSpeed() {
|
public long getOutSpeed() {
|
||||||
return uploadBandwidth.calculateBandwidth(timewindow);
|
return outSpeedEvaluator.get();
|
||||||
}
|
}
|
||||||
|
private LazyEvaluator<Long> inSpeedEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculateBandwidth(timewindow);});
|
||||||
public long getInSpeed() {
|
public long getInSpeed() {
|
||||||
return downloadBandwidth.calculateBandwidth(timewindow);
|
return inSpeedEvaluator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -117,13 +120,15 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> outPPSEvaluator=new LazyEvaluator<Long>(()->{return uploadBandwidth.calculatePacketRate(timewindow);});
|
||||||
public long getOutPPS() {
|
public long getOutPPS() {
|
||||||
return uploadBandwidth.calculatePacketRate(timewindow);
|
return outPPSEvaluator.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LazyEvaluator<Long> inPPSEvaluator=new LazyEvaluator<Long>(()->{return downloadBandwidth.calculatePacketRate(timewindow);});
|
||||||
|
|
||||||
public long getInPPS() {
|
public long getInPPS() {
|
||||||
return downloadBandwidth.calculatePacketRate(timewindow);
|
return inPPSEvaluator.get();
|
||||||
}
|
}
|
||||||
private long inSpeed;
|
private long inSpeed;
|
||||||
private long outSpeed;
|
private long outSpeed;
|
||||||
@@ -159,9 +164,18 @@ public class SpeedAndTrafficMonitorDataImpl extends MonitorDataImpl implements S
|
|||||||
public void update() {
|
public void update() {
|
||||||
upSampler.update();
|
upSampler.update();
|
||||||
downSampler.update();
|
downSampler.update();
|
||||||
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)upSampler.getPacketCountSinceLastSnapshot() ,(int)upSampler.getByteCountSinceLastSnapshot());
|
long uppacketcount=upSampler.getPacketCountSinceLastSnapshot();
|
||||||
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(),(int)downSampler.getPacketCountSinceLastSnapshot() ,(int) downSampler.getByteCountSinceLastSnapshot());
|
if(uppacketcount>0) {
|
||||||
|
uploadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) uppacketcount, (int) upSampler.getByteCountSinceLastSnapshot());
|
||||||
|
outSpeedEvaluator.markDirty();
|
||||||
|
outPPSEvaluator.markDirty();
|
||||||
|
}
|
||||||
|
long downpacketcount=downSampler.getPacketCountSinceLastSnapshot();
|
||||||
|
if(downpacketcount>0) {
|
||||||
|
downloadBandwidth.recordPacket(KLALBUtils.createGlobalUUID(), (int) downpacketcount, (int) downSampler.getByteCountSinceLastSnapshot());
|
||||||
|
inSpeedEvaluator.markDirty();
|
||||||
|
inPPSEvaluator.markDirty();
|
||||||
|
}
|
||||||
long time=System.nanoTime();
|
long time=System.nanoTime();
|
||||||
if(time-stime>timewindowmax) {
|
if(time-stime>timewindowmax) {
|
||||||
stime=time;
|
stime=time;
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||||
import org.kne.math.Long128;
|
import org.kne.math.Long128;
|
||||||
|
|
||||||
@@ -110,13 +110,13 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
return clock;
|
return clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultipurposeSocketAddress, List<NTPv4Packet>>();
|
private ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>> recvmap = new ConcurrentHashMap<MultiProtocolSocketAddress, List<NTPv4Packet>>();
|
||||||
|
|
||||||
protected void clearPackets() {
|
protected void clearPackets() {
|
||||||
recvmap.clear();
|
recvmap.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void putPacket(NTPv4Packet nv4, MultipurposeSocketAddress inetSocketAddress) {
|
protected void putPacket(NTPv4Packet nv4, MultiProtocolSocketAddress inetSocketAddress) {
|
||||||
checkIP();
|
checkIP();
|
||||||
List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
|
List<NTPv4Packet> newv = new Vector<NTPv4Packet>();
|
||||||
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
|
List<NTPv4Packet> oldv = recvmap.putIfAbsent(inetSocketAddress, newv);
|
||||||
@@ -133,10 +133,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void checkIP() {
|
private void checkIP() {
|
||||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||||
.hasNext();) {
|
.hasNext();) {
|
||||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||||
.next();
|
.next();
|
||||||
AtomicBoolean ab = new AtomicBoolean(false);
|
AtomicBoolean ab = new AtomicBoolean(false);
|
||||||
ios.forEach((x) -> {
|
ios.forEach((x) -> {
|
||||||
@@ -153,7 +153,7 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class PeerInfo implements Comparable<PeerInfo> {
|
private class PeerInfo implements Comparable<PeerInfo> {
|
||||||
private MultipurposeSocketAddress address;
|
private MultiProtocolSocketAddress address;
|
||||||
private int leapIndicator = 3;
|
private int leapIndicator = 3;
|
||||||
private int stratum = 16;
|
private int stratum = 16;
|
||||||
private int referenceIdentifier;
|
private int referenceIdentifier;
|
||||||
@@ -265,10 +265,10 @@ public class NTPContext implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private List<PeerInfo> mergeResponses() {
|
private List<PeerInfo> mergeResponses() {
|
||||||
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
|
List<PeerInfo> peerInfo = new ArrayList<PeerInfo>();
|
||||||
Set<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
Set<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> ens = recvmap.entrySet();
|
||||||
for (Iterator<Entry<MultipurposeSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
for (Iterator<Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>> iterator = ens.iterator(); iterator
|
||||||
.hasNext();) {
|
.hasNext();) {
|
||||||
Entry<MultipurposeSocketAddress, List<NTPv4Packet>> entry = (Entry<MultipurposeSocketAddress, List<NTPv4Packet>>) iterator
|
Entry<MultiProtocolSocketAddress, List<NTPv4Packet>> entry = (Entry<MultiProtocolSocketAddress, List<NTPv4Packet>>) iterator
|
||||||
.next();
|
.next();
|
||||||
|
|
||||||
List<NTPv4Packet> newv = entry.getValue();
|
List<NTPv4Packet> newv = entry.getValue();
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package org.kne.cloud.network.ntp;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.UDPSocketType;
|
||||||
|
|
||||||
|
public class NTPSocketType extends UDPSocketType {
|
||||||
|
private static final NTPSocketType INSTANCE = new NTPSocketType();
|
||||||
|
|
||||||
|
private NTPSocketType() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NTPSocketType getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getDefaultPort() {
|
||||||
|
return 123;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,7 +20,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.clock.NTPTimestamps;
|
import org.kne.cloud.clock.NTPTimestamps;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.io.KNEChannels;
|
import org.kne.io.KNEChannels;
|
||||||
import org.kne.math.Long128;
|
import org.kne.math.Long128;
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
private static final long EPHEMERAL_TIMEOUT = 60000000000L;
|
private static final long EPHEMERAL_TIMEOUT = 60000000000L;
|
||||||
|
|
||||||
public static class NTPPeer {
|
public static class NTPPeer {
|
||||||
private MultipurposeSocketAddress address;
|
private MultiProtocolSocketAddress address;
|
||||||
private boolean isEphemeral;// ephemeral
|
private boolean isEphemeral;// ephemeral
|
||||||
private volatile long ephemeralUpdateTime = System.nanoTime();
|
private volatile long ephemeralUpdateTime = System.nanoTime();
|
||||||
private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
|
private volatile long pollInterval = DEFAULT_POLL_INTERVAL;
|
||||||
@@ -70,11 +71,11 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
this.pollInterval = pollInterval;
|
this.pollInterval = pollInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
public NTPPeer(MultipurposeSocketAddress address, int requestMode) {
|
public NTPPeer(MultiProtocolSocketAddress address, int requestMode) {
|
||||||
this(address, requestMode, false);
|
this(address, requestMode, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected NTPPeer(MultipurposeSocketAddress address, int requestMode, boolean isEphemeral) {
|
protected NTPPeer(MultiProtocolSocketAddress address, int requestMode, boolean isEphemeral) {
|
||||||
super();
|
super();
|
||||||
checkRequestMode(requestMode);
|
checkRequestMode(requestMode);
|
||||||
this.address = address;
|
this.address = address;
|
||||||
@@ -90,10 +91,10 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public NTPPeer(String hostport, int requestMode) {
|
public NTPPeer(String hostport, int requestMode) {
|
||||||
this(new MultipurposeSocketAddress(hostport), requestMode);
|
this(new MultiProtocolSocketAddress(hostport), requestMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MultipurposeSocketAddress getAddress() {
|
public MultiProtocolSocketAddress getAddress() {
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,17 +154,28 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
|
|
||||||
private NTPContext context;
|
private NTPContext context;
|
||||||
private DatagramSocket dgs;
|
private DatagramSocket dgs;
|
||||||
private MultipurposeSocketAddress bind;
|
private MultiProtocolSocketAddress bind;
|
||||||
|
|
||||||
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
public NTPv4Protocol(NTPContext context) throws UnknownHostException, IOException {
|
||||||
this(context, new MultipurposeSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
this(context, new MultiProtocolSocketAddress("UDP", "::0", NTP_DEFAULT_PORT));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NTPv4Protocol(NTPContext context, MultipurposeSocketAddress bind) throws UnknownHostException, IOException {
|
public NTPv4Protocol(NTPContext context, MultiProtocolSocketAddress bind) throws UnknownHostException, IOException {
|
||||||
this.bind = bind;
|
this.bind = bind;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
dgs = bind.listenDatagramSocket();
|
dgs = bind.listenDatagramSocket();
|
||||||
context.registerIO(this);
|
context.registerIO(this);
|
||||||
|
Thread t4= ThreadTool.makeVThread("NTPv4 Packet Cleaner",()->{
|
||||||
|
while(isClosed()){
|
||||||
|
removeTimeoutTimestamp();
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
t4.start();
|
||||||
Thread tr = new Thread(recv);
|
Thread tr = new Thread(recv);
|
||||||
tr.setName("NTPv4 Receive Thread");
|
tr.setName("NTPv4 Receive Thread");
|
||||||
tr.start();
|
tr.start();
|
||||||
@@ -219,7 +231,6 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
|
private void putOriginTimestamp(NTPv4Packet nv4, NTPPeer peer) {
|
||||||
checkLock.lock();
|
checkLock.lock();
|
||||||
try {
|
try {
|
||||||
removeTimeoutTimestamp();
|
|
||||||
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
checkList.add(new CheckListItem(nv4.getTransmitTimestamp128(), peer));
|
||||||
} finally {
|
} finally {
|
||||||
checkLock.unlock();
|
checkLock.unlock();
|
||||||
@@ -227,7 +238,9 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeTimeoutTimestamp() {
|
private void removeTimeoutTimestamp() {
|
||||||
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext();) {
|
checkLock.lock();
|
||||||
|
try {
|
||||||
|
for (Iterator<CheckListItem> iterator = checkList.iterator(); iterator.hasNext(); ) {
|
||||||
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
CheckListItem bigInteger = (CheckListItem) iterator.next();
|
||||||
if (bigInteger.checkTimeout()) {
|
if (bigInteger.checkTimeout()) {
|
||||||
if (showPacket)
|
if (showPacket)
|
||||||
@@ -235,6 +248,9 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}finally{
|
||||||
|
checkLock.unlock();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException {
|
private void sendNTPPacket(NTPv4Packet nv4, SocketAddress target) throws IOException {
|
||||||
@@ -266,7 +282,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
NTPPeer findPeer(MultipurposeSocketAddress addr) {
|
NTPPeer findPeer(MultiProtocolSocketAddress addr) {
|
||||||
Object[] objs = peers.toArray();
|
Object[] objs = peers.toArray();
|
||||||
for (int i = 0; i < objs.length; i++) {
|
for (int i = 0; i < objs.length; i++) {
|
||||||
NTPPeer np = (NTPPeer) objs[i];
|
NTPPeer np = (NTPPeer) objs[i];
|
||||||
@@ -291,7 +307,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
while (!dgs.isClosed()) {
|
while (!dgs.isClosed()) {
|
||||||
AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
|
AtomicReference<InetSocketAddress> isa = new AtomicReference<>();
|
||||||
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
NTPv4Packet nv4 = receiveNTPPacket(isa);
|
||||||
MultipurposeSocketAddress mpsafrom = new MultipurposeSocketAddress(bind.getType(), isa.get());
|
MultiProtocolSocketAddress mpsafrom = new MultiProtocolSocketAddress(bind.getProtocol(), isa.get());
|
||||||
switch (nv4.getMode()) {
|
switch (nv4.getMode()) {
|
||||||
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
|
case NTPv4Packet.NTP_SYMMETRIC_ACTIVE:
|
||||||
NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
|
NTPv4Packet nv4r = new NTPv4Packet(context.getClock());
|
||||||
@@ -393,7 +409,7 @@ public class NTPv4Protocol implements Closeable, AutoCloseable {
|
|||||||
HighAccuracyClock hac = new HighAccuracyClock();
|
HighAccuracyClock hac = new HighAccuracyClock();
|
||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
|
nvc.getPeers().add(new NTPPeer("{UDP}time.windows.com:123", NTPv4Packet.NTP_CLIENT));
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
|
||||||
|
|
||||||
public class TestNTP1 {
|
public class TestNTP1 {
|
||||||
public static void main(String[] args) throws UnknownHostException, IOException {
|
public static void main(String[] args) throws UnknownHostException, IOException {
|
||||||
@@ -13,7 +12,7 @@ public class TestNTP1 {
|
|||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
context.syncToSystem();
|
context.syncToSystem();
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:123"));// 106.55.184.199
|
||||||
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
//nvc.getPeers().add(new NTPPeer("{UDP}106.55.184.199:123", NTPv4Packet.NTP_CLIENT));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.io.IOException;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
import org.kne.cloud.network.ntp.NTPv4Protocol.NTPPeer;
|
||||||
|
|
||||||
public class TestNTP2 {
|
public class TestNTP2 {
|
||||||
@@ -13,7 +13,7 @@ public class TestNTP2 {
|
|||||||
NTPContext context = new NTPContext(hac);
|
NTPContext context = new NTPContext(hac);
|
||||||
context.syncToSystem();
|
context.syncToSystem();
|
||||||
System.out.println(context);
|
System.out.println(context);
|
||||||
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultipurposeSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
NTPv4Protocol nvc = new NTPv4Protocol(context, new MultiProtocolSocketAddress("{UDP}0.0.0.0:0"));// 106.55.184.199
|
||||||
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
nvc.getPeers().add(new NTPPeer("{UDP}127.0.0.1:123", NTPv4Packet.NTP_SYMMETRIC_ACTIVE));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||||||
|
|
||||||
import org.kne.cloud.clock.AdjustedNanoClock;
|
import org.kne.cloud.clock.AdjustedNanoClock;
|
||||||
import org.kne.cloud.clock.HighAccuracyClock;
|
import org.kne.cloud.clock.HighAccuracyClock;
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SpeedLimiter;
|
import org.kne.cloud.network.SpeedLimiter;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.klalb.KLALBPacket;
|
import org.kne.cloud.network.klalb.KLALBPacket;
|
||||||
@@ -32,8 +32,8 @@ public class Kperf implements Runnable{
|
|||||||
|
|
||||||
private static final boolean showpacket = false;
|
private static final boolean showpacket = false;
|
||||||
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
|
private volatile AdjustedNanoClock adjnc = new AdjustedNanoClock();
|
||||||
private MultipurposeSocketAddress targetAddress;
|
private MultiProtocolSocketAddress targetAddress;
|
||||||
private MultipurposeSocketAddress bindAddress;
|
private MultiProtocolSocketAddress bindAddress;
|
||||||
private volatile boolean connected=false;
|
private volatile boolean connected=false;
|
||||||
private volatile KLALBPacketLink link;
|
private volatile KLALBPacketLink link;
|
||||||
|
|
||||||
@@ -43,13 +43,13 @@ public class Kperf implements Runnable{
|
|||||||
private volatile boolean closed = false;
|
private volatile boolean closed = false;
|
||||||
|
|
||||||
private volatile ThreadParker tlock=new ThreadParker();
|
private volatile ThreadParker tlock=new ThreadParker();
|
||||||
public Kperf(MultipurposeSocketAddress targetAddress,MultipurposeSocketAddress bindAddress) {
|
public Kperf(MultiProtocolSocketAddress targetAddress, MultiProtocolSocketAddress bindAddress) {
|
||||||
Objects.requireNonNull(targetAddress);
|
Objects.requireNonNull(targetAddress);
|
||||||
this.targetAddress=targetAddress;
|
this.targetAddress=targetAddress;
|
||||||
this.bindAddress=bindAddress;
|
this.bindAddress=bindAddress;
|
||||||
}
|
}
|
||||||
public Kperf(MultipurposeSocketAddress targetAddress) {
|
public Kperf(MultiProtocolSocketAddress targetAddress) {
|
||||||
this(targetAddress,new MultipurposeSocketAddress("[::0]:0"));
|
this(targetAddress,new MultiProtocolSocketAddress("[::0]:0"));
|
||||||
}
|
}
|
||||||
public Kperf(KLALBPacketLink link) {
|
public Kperf(KLALBPacketLink link) {
|
||||||
Objects.requireNonNull(link);
|
Objects.requireNonNull(link);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.SocketChannelListener;
|
import org.kne.cloud.network.SocketChannelListener;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
@@ -58,7 +58,7 @@ for (int i = 0; i < nodes; i++) {
|
|||||||
for (int j = 0; j < 1; j++) {
|
for (int j = 0; j < 1; j++) {
|
||||||
int ind=r.nextInt(size);
|
int ind=r.nextInt(size);
|
||||||
NodeEntry ne=nodeList.get(ind);
|
NodeEntry ne=nodeList.get(ind);
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||||
kct.addRemoteLines(mpsa);
|
kct.addRemoteLines(mpsa);
|
||||||
System.out.println("connect:" +ne.getKc().getSelf().getAddress());
|
System.out.println("connect:" +ne.getKc().getSelf().getAddress());
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ for (int i = 0; i < nodes; i++) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
NodeEntry ne=nodeList.get(0);
|
NodeEntry ne=nodeList.get(0);
|
||||||
MultipurposeSocketAddress mpsa=new MultipurposeSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
MultiProtocolSocketAddress mpsa=new MultiProtocolSocketAddress((InetSocketAddress) ne.getScl().getServerSocketChannel().getLocalAddress());
|
||||||
klalbController.addRemoteLines(mpsa);
|
klalbController.addRemoteLines(mpsa);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import java.io.Serializable;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
public class ScanRange implements Serializable {
|
public class ScanRange implements Serializable {
|
||||||
public ScanRange(String range) throws UnknownHostException {
|
public ScanRange(String range) throws UnknownHostException {
|
||||||
@@ -12,21 +12,21 @@ public class ScanRange implements Serializable {
|
|||||||
}
|
}
|
||||||
protected void setMsaString(String range) throws UnknownHostException {
|
protected void setMsaString(String range) throws UnknownHostException {
|
||||||
String[]sp=range.split("~");
|
String[]sp=range.split("~");
|
||||||
MultipurposeSocketAddress begin=new MultipurposeSocketAddress(sp[0]);
|
MultiProtocolSocketAddress begin=new MultiProtocolSocketAddress(sp[0]);
|
||||||
MultipurposeSocketAddress end;
|
MultiProtocolSocketAddress end;
|
||||||
if(sp.length>1) {
|
if(sp.length>1) {
|
||||||
end=new MultipurposeSocketAddress(sp[1]);
|
end=new MultiProtocolSocketAddress(sp[1]);
|
||||||
}else {
|
}else {
|
||||||
end=new MultipurposeSocketAddress(sp[0]);
|
end=new MultiProtocolSocketAddress(sp[0]);
|
||||||
}
|
}
|
||||||
setMsa(begin, end);
|
setMsa(begin, end);
|
||||||
}
|
}
|
||||||
public ScanRange(MultipurposeSocketAddress begin,MultipurposeSocketAddress end) throws UnknownHostException {
|
public ScanRange(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||||
setMsa(begin,end);
|
setMsa(begin,end);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setMsa(MultipurposeSocketAddress begin, MultipurposeSocketAddress end) throws UnknownHostException {
|
protected void setMsa(MultiProtocolSocketAddress begin, MultiProtocolSocketAddress end) throws UnknownHostException {
|
||||||
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
|
this.addressRange=new InetAddressRange(begin.getHost(), end.getHost());
|
||||||
this.portRange=new PortRange(begin.getPort(), end.getPort());
|
this.portRange=new PortRange(begin.getPort(), end.getPort());
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ public class ScanRange implements Serializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return new MultipurposeSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultipurposeSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
return new MultiProtocolSocketAddress(addressRange.getBeginHost(), portRange.getBegin())+"~"+new MultiProtocolSocketAddress(addressRange.getEndHost(), portRange.getEnd());
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import java.net.UnknownHostException;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
@@ -20,11 +19,11 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.ThreadTool;
|
import org.kne.cloud.network.ThreadTool;
|
||||||
|
|
||||||
public class TCPNetworkScanner implements Runnable {
|
public class TCPNetworkScanner implements Runnable {
|
||||||
private BiConsumer<MultipurposeSocketAddress,Socket> consumer;
|
private BiConsumer<MultiProtocolSocketAddress,Socket> consumer;
|
||||||
private Consumer<Float>processConsumer;
|
private Consumer<Float>processConsumer;
|
||||||
|
|
||||||
public Consumer<Float> getProcessConsumer() {
|
public Consumer<Float> getProcessConsumer() {
|
||||||
@@ -44,11 +43,11 @@ public class TCPNetworkScanner implements Runnable {
|
|||||||
return ranges;
|
return ranges;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BiConsumer<MultipurposeSocketAddress,Socket> getConsumer() {
|
public BiConsumer<MultiProtocolSocketAddress,Socket> getConsumer() {
|
||||||
return consumer;
|
return consumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setConsumer(BiConsumer<MultipurposeSocketAddress,Socket> consumer) {
|
public void setConsumer(BiConsumer<MultiProtocolSocketAddress,Socket> consumer) {
|
||||||
this.consumer = consumer;
|
this.consumer = consumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,13 +127,13 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
|||||||
exc.execute(()->{
|
exc.execute(()->{
|
||||||
|
|
||||||
|
|
||||||
MultipurposeSocketAddress mpsa;
|
MultiProtocolSocketAddress mpsa;
|
||||||
if (currAddress.equals(inetAddressRange.getBegin())) {
|
if (currAddress.equals(inetAddressRange.getBegin())) {
|
||||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getBeginHost(), curport0);
|
||||||
}else if (currAddress.equals(inetAddressRange.getEnd())) {
|
}else if (currAddress.equals(inetAddressRange.getEnd())) {
|
||||||
mpsa = new MultipurposeSocketAddress(inetAddressRange.getEndHost(), curport0);
|
mpsa = new MultiProtocolSocketAddress(inetAddressRange.getEndHost(), curport0);
|
||||||
} else {
|
} else {
|
||||||
mpsa = new MultipurposeSocketAddress(currAddress.getHostAddress(),
|
mpsa = new MultiProtocolSocketAddress(currAddress.getHostAddress(),
|
||||||
curport0);
|
curport0);
|
||||||
}
|
}
|
||||||
Socket s = null;
|
Socket s = null;
|
||||||
@@ -228,7 +227,7 @@ ThreadPoolExecutor excd=(ThreadPoolExecutor) Executors.newFixedThreadPool(8);
|
|||||||
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
|
System.arraycopy( new BigInteger(curr).add(BigInteger.valueOf(1)).toByteArray(), 0, curr, 0, curr.length); ;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void acceptMultipurposeSocketAddress(MultipurposeSocketAddress scanned, Socket s) {
|
protected void acceptMultipurposeSocketAddress(MultiProtocolSocketAddress scanned, Socket s) {
|
||||||
|
|
||||||
consumer.accept(scanned,s);
|
consumer.accept(scanned,s);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,14 @@
|
|||||||
package org.kne.cloud.network.srv6;
|
package org.kne.cloud.network.srv6;
|
||||||
|
|
||||||
import java.io.DataInputStream;
|
|
||||||
import java.io.DataOutputStream;
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.Externalizable;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.ObjectInput;
|
|
||||||
import java.io.ObjectOutput;
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.net.Inet6Address;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.channels.Channels;
|
|
||||||
import java.nio.channels.ReadableByteChannel;
|
import java.nio.channels.ReadableByteChannel;
|
||||||
import java.nio.channels.WritableByteChannel;
|
import java.nio.channels.WritableByteChannel;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.NetworkPacket;
|
|
||||||
import org.kne.cloud.network.ipv6.IPv6AddressGroup;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -53,7 +38,7 @@ public class JsonDataPacket extends KLALBRoutingProtocolPacket implements Serial
|
|||||||
private static Gson gson;
|
private static Gson gson;
|
||||||
static{
|
static{
|
||||||
GsonBuilder gb=new GsonBuilder();
|
GsonBuilder gb=new GsonBuilder();
|
||||||
MultipurposeSocketAddress.registerToGsonBuilder(gb);
|
MultiProtocolSocketAddress.registerToGsonBuilder(gb);
|
||||||
gson=gb.create();
|
gson=gb.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package org.kne.cloud.network.srv6;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 节点信息(开放线路 + 设备名称 + 设备描述),由路由协议 JSON API 查询获得。
|
||||||
|
*/
|
||||||
|
public class KLALBNodeInformation {
|
||||||
|
private List<MultiProtocolSocketAddress> openLines;
|
||||||
|
private String deviceName;
|
||||||
|
private String deviceDescription;
|
||||||
|
|
||||||
|
public KLALBNodeInformation(List<MultiProtocolSocketAddress> openLines, String deviceName,
|
||||||
|
String deviceDescription) {
|
||||||
|
super();
|
||||||
|
this.openLines = openLines;
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
this.deviceDescription = deviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<MultiProtocolSocketAddress> getOpenLines() {
|
||||||
|
return openLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOpenLines(List<MultiProtocolSocketAddress> openLines) {
|
||||||
|
this.openLines = openLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDeviceDescription() {
|
||||||
|
return deviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceDescription(String deviceDescription) {
|
||||||
|
this.deviceDescription = deviceDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "KLALBNodeInformation [openLines=" + openLines + ", deviceName=" + deviceName
|
||||||
|
+ ", deviceDescription=" + deviceDescription + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -44,6 +44,10 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
private RouterInfo selfRouterInfo;
|
private RouterInfo selfRouterInfo;
|
||||||
private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>();
|
private Map<IPv6Address, RouterInfo> netmap=new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public Map<IPv6Address, RouterInfo> getNetmap() {
|
||||||
|
return netmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private volatile Map<IPv6Address,Long>addresses;
|
private volatile Map<IPv6Address,Long>addresses;
|
||||||
|
|
||||||
@@ -545,6 +549,7 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
List<IPv6NetworkLink>links=router.getLinkTabel();
|
List<IPv6NetworkLink>links=router.getLinkTabel();
|
||||||
Object[] nls=links.toArray();
|
Object[] nls=links.toArray();
|
||||||
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN());
|
RouterInfo ri=new RouterInfo(System.currentTimeMillis(),router.getLocator(),router.getASN());
|
||||||
|
ri.setDeviceName(router.getDeviceName());
|
||||||
for(int i=0;i<nls.length;i++) {
|
for(int i=0;i<nls.length;i++) {
|
||||||
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
|
IPv6NetworkLink nl=(IPv6NetworkLink) nls[i];
|
||||||
if((!nl.isLoopBack())&&nl.isUp()) {
|
if((!nl.isLoopBack())&&nl.isUp()) {
|
||||||
@@ -595,6 +600,19 @@ public class KLALBRoutingProtocol extends Thread{
|
|||||||
public Map<IPv6Address, Long> getAddresses() {
|
public Map<IPv6Address, Long> getAddresses() {
|
||||||
return addresses;
|
return addresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询某地址广播的设备名称(未知返回null)
|
||||||
|
*/
|
||||||
|
public String getDeviceName(IPv6Address address) {
|
||||||
|
RouterInfo ri=netmap.get(address);
|
||||||
|
if(ri!=null) {
|
||||||
|
String dn=ri.getDeviceName();
|
||||||
|
if(dn!=null&&!dn.isEmpty())
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
public Map<IPv6Address, List<LinkDirection>> getPaths() {
|
public Map<IPv6Address, List<LinkDirection>> getPaths() {
|
||||||
return paths;
|
return paths;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import java.util.UUID;
|
|||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
import org.kne.cloud.network.MultiProtocolSocketAddress;
|
||||||
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
|
import org.kne.cloud.network.congestion.NOCongestionAlgorithm;
|
||||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
||||||
import org.kne.opencl64.Releaser;
|
import org.kne.opencl64.Releaser;
|
||||||
@@ -29,20 +29,27 @@ public class KLALBRoutingProtocolAPIClient {
|
|||||||
JsonDataPacket relate = null;
|
JsonDataPacket relate = null;
|
||||||
//System.out.println(ruid + " " + window.getSendmap());
|
//System.out.println(ruid + " " + window.getSendmap());
|
||||||
switch (dataobj.getType()) {
|
switch (dataobj.getType()) {
|
||||||
case KLALBRoutingProtocolJsonData.OPEN_LINES_RESP:
|
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP:
|
||||||
|
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP:
|
||||||
|
|
||||||
if ((relate = window.ack(ruid)) != null) {
|
if ((relate = window.ack(ruid)) != null) {
|
||||||
List<?> connects = (List<?>) dataobj.getData();
|
List<?> connects = (List<?>) dataobj.getData();
|
||||||
List<MultipurposeSocketAddress> connectsm = new ArrayList<MultipurposeSocketAddress>(connects.size());
|
List<MultiProtocolSocketAddress> connectsm = new ArrayList<MultiProtocolSocketAddress>(
|
||||||
|
connects == null ? 0 : connects.size());
|
||||||
|
if (connects != null) {
|
||||||
for (Object open : connects) {
|
for (Object open : connects) {
|
||||||
if (open instanceof MultipurposeSocketAddress) {
|
if (open instanceof MultiProtocolSocketAddress) {
|
||||||
connectsm.add((MultipurposeSocketAddress) open);
|
connectsm.add((MultiProtocolSocketAddress) open);
|
||||||
} else {
|
} else {
|
||||||
connectsm.add(new MultipurposeSocketAddress((String) open));
|
connectsm.add(new MultiProtocolSocketAddress((String) open));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
((Consumer<List<MultipurposeSocketAddress>>) relate.getUserCallback()).accept(connectsm);
|
}
|
||||||
|
// 组装节点信息(线路 + 设备名称 + 设备描述,精简模式下线路与描述为 null)
|
||||||
|
KLALBNodeInformation info = new KLALBNodeInformation(connectsm, dataobj.getDeviceName(),
|
||||||
|
dataobj.getDeviceDescription());
|
||||||
|
((Consumer<KLALBNodeInformation>) relate.getUserCallback()).accept(info);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -56,11 +63,26 @@ public class KLALBRoutingProtocolAPIClient {
|
|||||||
clr.register(this, releaser);
|
clr.register(this, releaser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void requestOpenLines(SocketAddress addr, Consumer<List<MultipurposeSocketAddress>> callback)
|
/**
|
||||||
|
* 精简查询:仅获取对端设备名称(开销最小,适用于未查看节点详情的场景)。
|
||||||
|
*/
|
||||||
|
public void requestNodeInfoTiny(SocketAddress addr, Consumer<KLALBNodeInformation> callback)
|
||||||
|
throws IOException {
|
||||||
|
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ, addr, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完整查询:获取对端开放线路 + 设备名称 + 设备描述(查看节点信息时使用)。
|
||||||
|
*/
|
||||||
|
public void requestNodeInfoFull(SocketAddress addr, Consumer<KLALBNodeInformation> callback)
|
||||||
|
throws IOException {
|
||||||
|
sendNodeInfoRequest(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ, addr, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendNodeInfoRequest(String type, SocketAddress addr, Consumer<KLALBNodeInformation> callback)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
UUID suid = UUID.randomUUID();
|
UUID suid = UUID.randomUUID();
|
||||||
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(
|
KLALBRoutingProtocolJsonData json = new KLALBRoutingProtocolJsonData(type, suid, null);
|
||||||
KLALBRoutingProtocolJsonData.OPEN_LINES_REQ, suid, null);
|
|
||||||
JsonDataPacket packet = new JsonDataPacket(json);
|
JsonDataPacket packet = new JsonDataPacket(json);
|
||||||
packet.setUserCallback(callback);
|
packet.setUserCallback(callback);
|
||||||
window.put(suid, packet);
|
window.put(suid, packet);
|
||||||
|
|||||||
@@ -4,13 +4,8 @@ import java.io.IOException;
|
|||||||
import java.lang.ref.Cleaner;
|
import java.lang.ref.Cleaner;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import org.kne.cloud.network.MultipurposeSocketAddress;
|
|
||||||
import org.kne.cloud.network.ThreadTool;
|
|
||||||
import org.kne.cloud.network.congestion.SendPacketSlidingWindow;
|
|
||||||
import org.kne.cloud.network.klalb.KLALBController;
|
import org.kne.cloud.network.klalb.KLALBController;
|
||||||
import org.kne.opencl64.Releaser;
|
import org.kne.opencl64.Releaser;
|
||||||
|
|
||||||
@@ -26,11 +21,21 @@ public class KLALBRoutingProtocolAPIServer {
|
|||||||
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData();
|
KLALBRoutingProtocolJsonData dataobj= data.getDecodedData();
|
||||||
InetSocketAddress addrs=(InetSocketAddress) addr;
|
InetSocketAddress addrs=(InetSocketAddress) addr;
|
||||||
switch(dataobj.getType()){
|
switch(dataobj.getType()){
|
||||||
case KLALBRoutingProtocolJsonData.OPEN_LINES_REQ:
|
case KLALBRoutingProtocolJsonData.NODE_INFO_TINY_REQ:
|
||||||
if(controller.getConfigItem()==null||(!controller.getConfigItem().isDenyLineTableQuery())) {
|
// 精简查询:仅返回设备名称(设备名称随路由信息广播公开,不受 denyExternalEndpointQuery 限制)
|
||||||
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.OPEN_LINES_RESP,dataobj.getUuid(),controller.getSelflineTable());
|
KLALBRoutingProtocolJsonData tinyjson=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_TINY_RESP,dataobj.getUuid(),null);
|
||||||
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr);
|
tinyjson.setDeviceName(controller.getIpv6Router().getDeviceName());
|
||||||
|
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(tinyjson),addr);
|
||||||
|
break;
|
||||||
|
case KLALBRoutingProtocolJsonData.NODE_INFO_FULL_REQ:
|
||||||
|
// 完整查询:设备名称与描述总是正常响应;denyExternalEndpointQuery 仅隐藏外部端点列表
|
||||||
|
boolean denyEndpoints=controller.getConfigItem()!=null&&controller.getConfigItem().isDenyExternalEndpointQuery();
|
||||||
|
KLALBRoutingProtocolJsonData json=new KLALBRoutingProtocolJsonData(KLALBRoutingProtocolJsonData.NODE_INFO_FULL_RESP,dataobj.getUuid(),denyEndpoints?null:controller.getExternalEndpoints());
|
||||||
|
json.setDeviceName(controller.getIpv6Router().getDeviceName());// 附带本机设备名称
|
||||||
|
if(controller.getConfigItem()!=null) {
|
||||||
|
json.setDeviceDescription(controller.getConfigItem().getDeviceDescription());// 附带本机设备描述
|
||||||
}
|
}
|
||||||
|
routingProtocol.sendJsonPacketToAddress(new JsonDataPacket(json),addr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|||||||
@@ -3,11 +3,27 @@ package org.kne.cloud.network.srv6;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class KLALBRoutingProtocolJsonData {
|
public class KLALBRoutingProtocolJsonData {
|
||||||
public static final String OPEN_LINES_REQ="openlinesreq";
|
public static final String NODE_INFO_TINY_REQ="nodeinfotinyreq";// 精简查询:仅设备名称
|
||||||
public static final String OPEN_LINES_RESP="openlinesresp";
|
public static final String NODE_INFO_TINY_RESP="nodeinfotinyresp";
|
||||||
|
public static final String NODE_INFO_FULL_REQ="nodeinfofullreq";// 完整查询:开放线路+设备名称+设备描述
|
||||||
|
public static final String NODE_INFO_FULL_RESP="nodeinfofullresp";
|
||||||
private String type;
|
private String type;
|
||||||
private UUID uuid;
|
private UUID uuid;
|
||||||
private Object data;
|
private Object data;
|
||||||
|
private String deviceName;// 对端设备名称
|
||||||
|
private String deviceDescription;// 对端设备描述
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
public String getDeviceDescription() {
|
||||||
|
return deviceDescription;
|
||||||
|
}
|
||||||
|
public void setDeviceDescription(String deviceDescription) {
|
||||||
|
this.deviceDescription = deviceDescription;
|
||||||
|
}
|
||||||
public String getType() {
|
public String getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
@@ -24,6 +40,8 @@ public class KLALBRoutingProtocolJsonData {
|
|||||||
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
result = prime * result + ((data == null) ? 0 : data.hashCode());
|
||||||
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
result = prime * result + ((type == null) ? 0 : type.hashCode());
|
||||||
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
|
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
|
||||||
|
result = prime * result + ((deviceName == null) ? 0 : deviceName.hashCode());
|
||||||
|
result = prime * result + ((deviceDescription == null) ? 0 : deviceDescription.hashCode());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
@@ -50,6 +68,16 @@ public class KLALBRoutingProtocolJsonData {
|
|||||||
return false;
|
return false;
|
||||||
} else if (!uuid.equals(other.uuid))
|
} else if (!uuid.equals(other.uuid))
|
||||||
return false;
|
return false;
|
||||||
|
if (deviceName == null) {
|
||||||
|
if (other.deviceName != null)
|
||||||
|
return false;
|
||||||
|
} else if (!deviceName.equals(other.deviceName))
|
||||||
|
return false;
|
||||||
|
if (deviceDescription == null) {
|
||||||
|
if (other.deviceDescription != null)
|
||||||
|
return false;
|
||||||
|
} else if (!deviceDescription.equals(other.deviceDescription))
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
|
public KLALBRoutingProtocolJsonData(String type, UUID uuid, Object data) {
|
||||||
@@ -60,7 +88,8 @@ public class KLALBRoutingProtocolJsonData {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + "]";
|
return "KLALBRoutingProtocolJsonData [type=" + type + ", uuid=" + uuid + ", data=" + data + ", deviceName="
|
||||||
|
+ deviceName + ", deviceDescription=" + deviceDescription + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class RouterInfo implements Serializable{
|
|||||||
return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses);
|
return Objects.equals(locator, other.locator) && Objects.equals(neighborAddresses, other.neighborAddresses);
|
||||||
}
|
}
|
||||||
private List<NeighborInfo> neighborAddresses=new ArrayList<>();
|
private List<NeighborInfo> neighborAddresses=new ArrayList<>();
|
||||||
|
private String deviceName=""; // 设备名称(广播时携带,描述不广播)
|
||||||
private long createTime;
|
private long createTime;
|
||||||
|
|
||||||
|
|
||||||
@@ -90,6 +91,14 @@ public class RouterInfo implements Serializable{
|
|||||||
return neighborAddresses;
|
return neighborAddresses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
|
public RouterInfo( long createTime,IPv6AddressGroup locator,long asn) {
|
||||||
this.createTime = createTime;
|
this.createTime = createTime;
|
||||||
this.locator=locator;
|
this.locator=locator;
|
||||||
@@ -106,6 +115,7 @@ public class RouterInfo implements Serializable{
|
|||||||
for( NeighborInfo neighborInfo :neighborAddresses) {
|
for( NeighborInfo neighborInfo :neighborAddresses) {
|
||||||
neighborInfo.writeToStream(out);
|
neighborInfo.writeToStream(out);
|
||||||
}
|
}
|
||||||
|
out.writeUTF(deviceName==null?"":deviceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void writeToChannel(WritableByteChannel dto) throws IOException {
|
protected void writeToChannel(WritableByteChannel dto) throws IOException {
|
||||||
@@ -126,6 +136,7 @@ public class RouterInfo implements Serializable{
|
|||||||
neighborAddresses.add(nif);
|
neighborAddresses.add(nif);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
deviceName=in.readUTF();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import org.kne.cloud.network.ipv6.PostcardEntry;
|
|||||||
import org.kne.cloud.network.ipv6.RouteItem;
|
import org.kne.cloud.network.ipv6.RouteItem;
|
||||||
import org.kne.cloud.network.klalb.KLALBRemoteLink;
|
import org.kne.cloud.network.klalb.KLALBRemoteLink;
|
||||||
import org.kne.cloud.network.klalb.KLALBUtils;
|
import org.kne.cloud.network.klalb.KLALBUtils;
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
import org.kne.cloud.network.klalb.ui.PerformanceStrategyItem;
|
||||||
import org.kne.concurrent.HighPerformanceExecutor2;
|
import org.kne.concurrent.HighPerformanceExecutor2;
|
||||||
import org.kne.concurrent.TimeoutConcurrentHashMap;
|
import org.kne.concurrent.TimeoutConcurrentHashMap;
|
||||||
import org.pcap4j.packet.IllegalRawDataException;
|
import org.pcap4j.packet.IllegalRawDataException;
|
||||||
@@ -62,6 +64,8 @@ public class SRv6Router {
|
|||||||
|
|
||||||
private static final int IPv6_BITS = 128; // IPv6地址位数
|
private static final int IPv6_BITS = 128; // IPv6地址位数
|
||||||
|
|
||||||
|
private PerformanceStrategy performanceStrategy=PerformanceStrategy.MULTI_FILL;
|
||||||
|
|
||||||
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
|
/*private TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator> flowIDmap = new TimeoutConcurrentHashMap<FlowSession, PacketIDGenerator>(
|
||||||
60000000000L);
|
60000000000L);
|
||||||
*/
|
*/
|
||||||
@@ -161,6 +165,14 @@ public class SRv6Router {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public void setPerformanceStrategy(PerformanceStrategy performanceStrategy) {
|
||||||
|
this.performanceStrategy=performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceStrategy getPerformanceStrategy(){
|
||||||
|
return performanceStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
// 链路状态监听器实现
|
// 链路状态监听器实现
|
||||||
private class LinkListener implements IPv6LinkStateListener {
|
private class LinkListener implements IPv6LinkStateListener {
|
||||||
|
|
||||||
@@ -282,6 +294,15 @@ public class SRv6Router {
|
|||||||
|
|
||||||
private IPv6AddressGroup locator; // SRv6定位器
|
private IPv6AddressGroup locator; // SRv6定位器
|
||||||
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
|
private long asn = new SecureRandom().nextLong(1, Long.MAX_VALUE); // 自治系统号
|
||||||
|
private volatile String deviceName; // 设备名称(随路由信息广播)
|
||||||
|
|
||||||
|
public String getDeviceName() {
|
||||||
|
return deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceName(String deviceName) {
|
||||||
|
this.deviceName = deviceName;
|
||||||
|
}
|
||||||
|
|
||||||
public IPv6AddressGroup getLocator() {
|
public IPv6AddressGroup getLocator() {
|
||||||
return locator;
|
return locator;
|
||||||
@@ -765,43 +786,43 @@ public class SRv6Router {
|
|||||||
|
|
||||||
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
|
//private static HighPerformanceExecutor2 defaultExecutor = new HighPerformanceExecutor2(4, Thread.ofVirtual().name("SRv6 Routing").factory());
|
||||||
|
|
||||||
public static HighPerformanceExecutor2 getDefaultExecutor() {
|
/*public static HighPerformanceExecutor2 getDefaultExecutor() {
|
||||||
return defaultExecutor;
|
return defaultExecutor;
|
||||||
}
|
}*/
|
||||||
|
|
||||||
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
|
// private ArrayBlockingQueue<Supplier<IPv6Packet>> packetQueue=new
|
||||||
// ArrayBlockingQueue<>(10000);
|
// ArrayBlockingQueue<>(10000);
|
||||||
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
|
public void enqueuePacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||||
// packetQueue.add(object);
|
// packetQueue.add(object);
|
||||||
|
switch(performanceStrategy) {
|
||||||
|
case SINGLE_CORE:
|
||||||
|
runPacketSendTask0(sendTask, false);
|
||||||
|
break;
|
||||||
|
case MULTI_FILL:
|
||||||
|
case MULTI_SCATTER:
|
||||||
defaultExecutor.executeWithCongestionReport((state) -> {
|
defaultExecutor.executeWithCongestionReport((state) -> {
|
||||||
|
runPacketSendTask0(sendTask, state);
|
||||||
|
}, 20,performanceStrategy);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void runPacketSendTask0(Supplier<IPv6Packet> sendTask,boolean ecn) {
|
||||||
try {
|
try {
|
||||||
long start=System.nanoTime();
|
long start=System.nanoTime();
|
||||||
IPv6Packet packet=sendTask.get();
|
IPv6Packet packet=sendTask.get();
|
||||||
if(state) {
|
if(ecn) {
|
||||||
packet.markCE();
|
packet.markCE();
|
||||||
}
|
}
|
||||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
||||||
long time=System.nanoTime()-start;
|
long time=System.nanoTime()-start;
|
||||||
backplaneCount(time,state);
|
backplaneCount(time,ecn);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
},20);
|
|
||||||
}
|
}
|
||||||
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
|
public void runPacketSendTask(Supplier<IPv6Packet> sendTask) {
|
||||||
try {
|
runPacketSendTask0(sendTask,false);
|
||||||
boolean state=false;
|
|
||||||
long start=System.nanoTime();
|
|
||||||
IPv6Packet packet=sendTask.get();
|
|
||||||
if(state) {
|
|
||||||
packet.markCE();
|
|
||||||
}
|
|
||||||
insertSRHandRoutePacket(null, packet); // 插入SRH并路由
|
|
||||||
long time=System.nanoTime()-start;
|
|
||||||
backplaneCount(time,state);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onReceive(IPv6NetworkLink link,IPv6Packet t){
|
public void onReceive(IPv6NetworkLink link,IPv6Packet t){
|
||||||
@@ -824,7 +845,7 @@ public class SRv6Router {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
},20);
|
},20,performanceStrategy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void enqueuePacket(IPv6Packet packet) {
|
public void enqueuePacket(IPv6Packet packet) {
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
package org.kne.concurrent;
|
package org.kne.concurrent;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.LinkedBlockingDeque;
|
|
||||||
import java.util.concurrent.ThreadFactory;
|
import java.util.concurrent.ThreadFactory;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.locks.LockSupport;
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import org.jctools.queues.MpscArrayQueue;
|
import org.jctools.queues.MpscArrayQueue;
|
||||||
|
import org.kne.cloud.network.klalb.PerformanceStrategy;
|
||||||
|
|
||||||
public class HighPerformanceExecutor2 implements Executor {
|
public class HighPerformanceExecutor2 implements Executor {
|
||||||
|
|
||||||
@@ -33,6 +25,8 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
threads[i]=t;
|
threads[i]=t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static class ThreadElement implements Runnable{
|
private static class ThreadElement implements Runnable{
|
||||||
private MpscArrayQueue<Runnable> queue=new MpscArrayQueue<Runnable>(2048);
|
private MpscArrayQueue<Runnable> queue=new MpscArrayQueue<Runnable>(2048);
|
||||||
private AtomicInteger size=new AtomicInteger();
|
private AtomicInteger size=new AtomicInteger();
|
||||||
@@ -80,7 +74,7 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
public void execute(Runnable command) {
|
public void execute(Runnable command) {
|
||||||
if(execute0((x)->{command.run();},1000)) {
|
if(execute0((x)->{command.run();},1000, PerformanceStrategy.MULTI_FILL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
@@ -89,42 +83,47 @@ public class HighPerformanceExecutor2 implements Executor {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*private long vl=0;
|
private long vl=0;
|
||||||
private boolean execute0(Consumer<Boolean> command,int limit) {
|
|
||||||
|
private boolean execute0(Consumer<Boolean> command, int limit, PerformanceStrategy strategy) {
|
||||||
|
if(strategy.equals(PerformanceStrategy.MULTI_SCATTER)){
|
||||||
long ord=vl++;
|
long ord=vl++;
|
||||||
ThreadElement te= threads[(int) (ord%threads.length)];
|
ThreadElement te= threads[(int) (ord%threads.length)];
|
||||||
boolean b=te.size()>limit;
|
boolean b=te.size()>limit;
|
||||||
return te.putTask( ()->{command.accept(b);});
|
return te.putTask( ()->{command.accept(b);});
|
||||||
}*/
|
}else {
|
||||||
|
for (int i = 0; i < threads.length; i++) {
|
||||||
private boolean execute0(Consumer<Boolean> command,int limit) {
|
ThreadElement te = threads[i];
|
||||||
for(int i=0;i<threads.length;i++) {
|
boolean b = te.size() > limit;
|
||||||
ThreadElement te= threads[i];
|
if (!b) {
|
||||||
boolean b=te.size()>limit;
|
if (te.putTask(() -> {
|
||||||
if(!b) {
|
command.accept(false);
|
||||||
if(te.putTask( ()->{command.accept(false);})){
|
})) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i=0;i<threads.length;i++) {
|
for (int i = 0; i < threads.length; i++) {
|
||||||
ThreadElement te= threads[i];
|
ThreadElement te = threads[i];
|
||||||
if(te.putTask( ()->{command.accept(true);})){
|
if (te.putTask(() -> {
|
||||||
|
command.accept(true);
|
||||||
|
})) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void executeWithCongestionReport(Consumer<Boolean> command) {
|
public void executeWithCongestionReport(Consumer<Boolean> command) {
|
||||||
if(execute0(command,1000)) {
|
if(execute0(command,1000, PerformanceStrategy.MULTI_FILL)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
}
|
}
|
||||||
public void executeWithCongestionReport(Consumer<Boolean> command,int limit) {
|
public void executeWithCongestionReport(Consumer<Boolean> command, int limit, PerformanceStrategy strategy) {
|
||||||
if(execute0(command,limit)) {
|
if(execute0(command,limit,strategy)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System.out.println("loss!");
|
System.out.println("loss!");
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package org.kne.concurrent;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 惰性计算器:只在输入变化且输出被需要时重算。
|
||||||
|
*
|
||||||
|
* 使用场景:路由表重算、统计信息聚合、UI 数据更新等
|
||||||
|
*
|
||||||
|
* @param <T> 输出类型
|
||||||
|
*/
|
||||||
|
public class LazyEvaluator<T> {
|
||||||
|
|
||||||
|
private final Supplier<T> recomputeFn; // 重算函数(可能较重)
|
||||||
|
private final AtomicBoolean dirty = new AtomicBoolean(true); // true表示需要重算
|
||||||
|
private volatile T cachedResult; // 缓存结果(volatile保证可见性)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param recomputeFn 当真正需要重算时,调用此函数生成新值
|
||||||
|
*/
|
||||||
|
public LazyEvaluator(Supplier<T> recomputeFn) {
|
||||||
|
this.recomputeFn = recomputeFn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标记输入已变化(每次变化都调用)
|
||||||
|
* CAS 保证多线程下只有一个线程真正置脏,避免无谓的竞争
|
||||||
|
*/
|
||||||
|
public boolean markDirty() {
|
||||||
|
return dirty.compareAndSet(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前值(如果 dirty 且被需要,则触发重算)
|
||||||
|
* 这个方法由需要输出的地方(如 UI 线程、路由引擎)调用
|
||||||
|
*/
|
||||||
|
public T get() {
|
||||||
|
if (dirty.get()) {
|
||||||
|
synchronized (this) {
|
||||||
|
// double-check:防止多个线程同时进入重算
|
||||||
|
if (dirty.get()) {
|
||||||
|
cachedResult = recomputeFn.get();
|
||||||
|
dirty.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前缓存的值(不触发重算)
|
||||||
|
* 适合那些“如果已经算好就直接用,否则也不强求”的场景
|
||||||
|
*/
|
||||||
|
public T getCached() {
|
||||||
|
return cachedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前是否需要重算
|
||||||
|
*/
|
||||||
|
public boolean isDirty() {
|
||||||
|
return dirty.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user